From 2292c0122047faf17fae675bf53a7b81a9e8de6e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 15 Oct 2024 11:01:56 +0400 Subject: [PATCH 001/109] community controller --- libs/common/src/constants/controller-route.ts | 30 ++ libs/common/src/database/database.module.ts | 2 + libs/common/src/dto/base.response.dto.ts | 23 ++ libs/common/src/dto/pagination.request.dto.ts | 87 +++++ .../common/src/dto/pagination.response.dto.ts | 62 +++ libs/common/src/dto/success.response.dto.ts | 29 ++ libs/common/src/models/typeOrmCustom.model.ts | 145 +++++++ .../modules/community/dtos/community.dto.ts | 19 + .../src/modules/community/dtos/index.ts | 1 + .../community/entities/community.entity.ts | 38 ++ .../src/modules/community/entities/index.ts | 1 + .../repositories/community.repository.ts | 10 + .../modules/community/repositories/index.ts | 1 + libs/common/src/type/optional.type.ts | 2 + libs/common/src/util/parseToDate.ts | 4 + .../is-page-request-param.validator.ts | 21 ++ .../is-size-request-param.validator.ts | 21 ++ .../src/validators/is-sort-param.validator.ts | 54 +++ .../controllers/community.controller.ts | 134 ++++--- src/community/dtos/add.community.dto.ts | 25 +- src/community/dtos/get.community.dto.ts | 13 + src/community/dtos/update.community.dto.ts | 4 +- src/community/services/community.service.ts | 355 ++++++++---------- 23 files changed, 822 insertions(+), 259 deletions(-) create mode 100644 libs/common/src/dto/base.response.dto.ts create mode 100644 libs/common/src/dto/pagination.request.dto.ts create mode 100644 libs/common/src/dto/pagination.response.dto.ts create mode 100644 libs/common/src/dto/success.response.dto.ts create mode 100644 libs/common/src/models/typeOrmCustom.model.ts create mode 100644 libs/common/src/modules/community/dtos/community.dto.ts create mode 100644 libs/common/src/modules/community/dtos/index.ts create mode 100644 libs/common/src/modules/community/entities/community.entity.ts create mode 100644 libs/common/src/modules/community/entities/index.ts create mode 100644 libs/common/src/modules/community/repositories/community.repository.ts create mode 100644 libs/common/src/modules/community/repositories/index.ts create mode 100644 libs/common/src/type/optional.type.ts create mode 100644 libs/common/src/util/parseToDate.ts create mode 100644 libs/common/src/validators/is-page-request-param.validator.ts create mode 100644 libs/common/src/validators/is-size-request-param.validator.ts create mode 100644 libs/common/src/validators/is-sort-param.validator.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index a1e43ee..5e643c8 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -8,4 +8,34 @@ export class ControllerRoute { 'Retrieve the list of all regions registered in Syncrow.'; }; }; + + static COMMUNITY = class { + public static readonly ROUTE = 'communities'; + static ACTIONS = class { + public static readonly GET_COMMUNITY_BY_ID_SUMMARY = + 'Get community by community id'; + + public static readonly GET_COMMUNITY_BY_ID_DESCRIPTION = + 'Get community by community id - ( [a-zA-Z0-9]{10} )'; + + public static readonly LIST_COMMUNITY_SUMMARY = 'Get list of community'; + + public static readonly LIST_COMMUNITY_DESCRIPTION = + 'Return a list of community'; + public static readonly UPDATE_COMMUNITY_SUMMARY = 'Update community'; + + public static readonly UPDATE_COMMUNITY_DESCRIPTION = + 'Update community in the database and return updated community'; + + public static readonly DELETE_COMMUNITY_SUMMARY = 'Delete community'; + + public static readonly DELETE_COMMUNITY_DESCRIPTION = + 'Delete community matching by community id'; + + public static readonly CREATE_COMMUNITY_SUMMARY = 'Create community'; + + public static readonly CREATE_COMMUNITY_DESCRIPTION = + 'Create community in the database and return in model'; + }; + }; } diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index ae01e55..deaa021 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -19,6 +19,7 @@ import { DeviceNotificationEntity } from '../modules/device/entities'; import { RegionEntity } from '../modules/region/entities'; import { TimeZoneEntity } from '../modules/timezone/entities'; import { VisitorPasswordEntity } from '../modules/visitor-password/entities'; +import { CommunityEntity } from '../modules/community/entities'; @Module({ imports: [ @@ -41,6 +42,7 @@ import { VisitorPasswordEntity } from '../modules/visitor-password/entities'; DeviceUserPermissionEntity, DeviceEntity, PermissionTypeEntity, + CommunityEntity, SpaceEntity, SpaceTypeEntity, UserSpaceEntity, diff --git a/libs/common/src/dto/base.response.dto.ts b/libs/common/src/dto/base.response.dto.ts new file mode 100644 index 0000000..c51d38c --- /dev/null +++ b/libs/common/src/dto/base.response.dto.ts @@ -0,0 +1,23 @@ +import { WithOptional } from '../type/optional.type'; + +export class BaseResponseDto { + statusCode?: number; + + message: string; + + error?: string; + + data?: any; + + success?: boolean; + + static wrap({ + data, + statusCode = 200, + message = 'Success', + success = true, + error = undefined, + }: WithOptional) { + return { data, statusCode, success, message, error }; + } +} diff --git a/libs/common/src/dto/pagination.request.dto.ts b/libs/common/src/dto/pagination.request.dto.ts new file mode 100644 index 0000000..4eff373 --- /dev/null +++ b/libs/common/src/dto/pagination.request.dto.ts @@ -0,0 +1,87 @@ +import { IsDate, IsOptional } from 'class-validator'; +import { IsPageRequestParam } from '../validators/is-page-request-param.validator'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsSizeRequestParam } from '../validators/is-size-request-param.validator'; +import { IsSortParam } from '../validators/is-sort-param.validator'; +import { Transform } from 'class-transformer'; +import { parseToDate } from '../util/parseToDate'; + +export class PaginationRequestGetListDto { + @IsOptional() + @IsPageRequestParam({ + message: 'Page must be bigger than 0', + }) + @ApiProperty({ + name: 'page', + required: false, + description: 'Page request', + }) + page?: number; + + @IsOptional() + @IsSizeRequestParam({ + message: 'Size must not be negative', + }) + @ApiProperty({ + name: 'size', + required: false, + description: 'Size request', + }) + size?: number; + + @IsOptional() + @IsSortParam({ + message: + 'Incorrect sorting condition format. Should be like this format propertyId:asc,createdDate:desc', + }) + @ApiProperty({ + name: 'sort', + required: false, + description: 'Sort condition', + }) + sort?: string; + + @IsOptional() + @ApiProperty({ + name: 'name', + required: false, + description: 'Name to be filtered', + }) + name?: string; + + @IsOptional() + @ApiProperty({ + name: 'include', + required: false, + description: 'Fields to include', + }) + include?: string; + + @ApiProperty({ + name: 'from', + required: false, + type: Number, + description: `Start time in UNIX timestamp format to filter`, + example: 1674172800000, + }) + @IsOptional() + @Transform(({ value }) => parseToDate(value)) + @IsDate({ + message: `From must be in UNIX timestamp format in order to parse to Date instance`, + }) + from?: Date; + + @ApiProperty({ + name: 'to', + required: false, + type: Number, + description: `End time in UNIX timestamp format to filter`, + example: 1674259200000, + }) + @IsOptional() + @Transform(({ value }) => parseToDate(value)) + @IsDate({ + message: `To must be in UNIX timestamp format in order to parse to Date instance`, + }) + to?: Date; +} diff --git a/libs/common/src/dto/pagination.response.dto.ts b/libs/common/src/dto/pagination.response.dto.ts new file mode 100644 index 0000000..5a0e4cf --- /dev/null +++ b/libs/common/src/dto/pagination.response.dto.ts @@ -0,0 +1,62 @@ +import { BaseResponseDto } from './base.response.dto'; + +export interface PageResponseDto { + // Original paging information from the request ( or default ) + page: number; + size: number; + + // Useful for display (N Records found) + totalItem: number; + + // Use for display N Pages ( 0... N ) + totalPage: number; + + // Has next is false when cursor is at last page + hasNext: boolean; + + // Has previous is false when cursor is at first page + hasPrevious: boolean; +} + +export class PageResponse implements BaseResponseDto, PageResponseDto { + code?: number; + + message: string; + + data: Array; + + page: number; + + size: number; + + totalItem: number; + + totalPage: number; + + hasNext: boolean; + + hasPrevious: boolean; + + constructor( + baseResponseDto: BaseResponseDto, + pageResponseDto: PageResponseDto, + ) { + if (baseResponseDto.statusCode) { + this.code = baseResponseDto.statusCode; + } else { + this.code = 200; + } + + if (baseResponseDto.data) { + this.data = baseResponseDto.data; + } + + this.message = baseResponseDto.message; + this.page = pageResponseDto.page; + this.size = pageResponseDto.size; + this.totalItem = pageResponseDto.totalItem; + this.totalPage = pageResponseDto.totalPage; + this.hasNext = pageResponseDto.hasNext; + this.hasPrevious = pageResponseDto.hasPrevious; + } +} diff --git a/libs/common/src/dto/success.response.dto.ts b/libs/common/src/dto/success.response.dto.ts new file mode 100644 index 0000000..f21bf78 --- /dev/null +++ b/libs/common/src/dto/success.response.dto.ts @@ -0,0 +1,29 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { BaseResponseDto } from './base.response.dto'; + +export class SuccessResponseDto implements BaseResponseDto { + @ApiProperty({ + example: 200, + }) + statusCode: number; + + data: Type; + + @ApiProperty({ + example: 'Success message', + }) + message: string; + + @ApiProperty({ + example: true, + description: 'Indicates that the operation was successful', + }) + success: boolean; + + constructor(input: BaseResponseDto) { + if (input.statusCode) this.statusCode = input.statusCode; + else this.statusCode = 200; + if (input.data) this.data = input.data; + this.success = true; + } +} diff --git a/libs/common/src/models/typeOrmCustom.model.ts b/libs/common/src/models/typeOrmCustom.model.ts new file mode 100644 index 0000000..b1e102a --- /dev/null +++ b/libs/common/src/models/typeOrmCustom.model.ts @@ -0,0 +1,145 @@ +import { FindManyOptions, Repository } from 'typeorm'; +import { InternalServerErrorException } from '@nestjs/common'; +import { BaseResponseDto } from '../dto/base.response.dto'; +import { PageResponseDto } from '../dto/pagination.response.dto'; +import { buildTypeORMSortQuery } from '../util/buildTypeORMSortQuery'; +import { getPaginationResponseDto } from '../util/getPaginationResponseDto'; +import { buildTypeORMWhereClause } from '../util/buildTypeORMWhereClause'; +import { buildTypeORMIncludeQuery } from '../util/buildTypeORMIncludeQuery'; + +export interface TypeORMCustomModelFindAllQuery { + page: number | undefined; + size: number | undefined; + sort?: string; + modelName?: string; + include?: string; + where?: { [key: string]: unknown }; + select?: string[]; + includeDisable?: boolean | string; +} +interface CustomFindAllQuery { + page?: number; + size?: number; + [key: string]: any; +} + +interface FindAllQueryWithDefaults extends CustomFindAllQuery { + page: number; + size: number; +} + +function getDefaultQueryOptions( + query: Partial, +): FindManyOptions & FindAllQueryWithDefaults { + const { page, size, includeDisable, modelName, ...rest } = query; + + // Set default if undefined or null + const returnPage = page ? Number(page) : 1; + const returnSize = size ? Number(size) : 10; + const returnIncludeDisable = + includeDisable === true || includeDisable === 'true'; + + // Return query with defaults and ensure modelName is passed through + return { + skip: (returnPage - 1) * returnSize, + take: returnSize, + where: { + ...rest, + }, + page: returnPage, + size: returnSize, + includeDisable: returnIncludeDisable, + modelName: modelName || query.modelName, // Ensure modelName is passed through + }; +} + +export interface TypeORMCustomModelFindAllQueryWithDefault + extends TypeORMCustomModelFindAllQuery { + page: number; + size: number; +} + +export type TypeORMCustomModelFindAllResponse = { + baseResponseDto: BaseResponseDto; + paginationResponseDto: PageResponseDto; +}; + +export function TypeORMCustomModel(repository: Repository) { + return Object.assign(repository, { + findAll: async function ( + query: Partial, + ): Promise { + // Extract values from the query + const { + page = 1, + size = 10, + sort, + modelName, + include, + where, + select, + } = getDefaultQueryOptions(query); + + // Ensure modelName is set before proceeding + if (!modelName) { + console.error( + 'modelName is missing after getDefaultQueryOptions:', + query, + ); + throw new InternalServerErrorException( + `[TypeORMCustomModel] Cannot findAll with unknown modelName`, + ); + } + + const skip = (page - 1) * size; + const order = buildTypeORMSortQuery(sort); + const relations = buildTypeORMIncludeQuery(modelName, include); + + // Use the where clause directly, without wrapping it under 'where' + const whereClause = buildTypeORMWhereClause({ where }); + console.log('Where clause after building:', whereClause); + + // Ensure the whereClause is passed directly to findAndCount + const [data, count] = await repository.findAndCount({ + where: whereClause.where || whereClause, // Don't wrap this under another 'where' + take: size, + skip: skip, + order: order, + select: select, + relations: relations, + }); + + const paginationResponseDto = getPaginationResponseDto(count, page, size); + const baseResponseDto: BaseResponseDto = { + data, + message: getResponseMessage(modelName, { where: whereClause }), + }; + + return { baseResponseDto, paginationResponseDto }; + }, + }); +} + +function getResponseMessage( + modelName: string, + query?: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + where?: any; + }, +): string { + if (!query) { + return `Success get list ${modelName}`; + } + + const { where } = query; + if (modelName === 'user' && where && where?.community) { + const { + some: { communityId }, + } = where.community; + if (typeof communityId === 'string') { + return `Success get list ${modelName} belong to community`; + } + } + + return `Success get list ${modelName}`; +} diff --git a/libs/common/src/modules/community/dtos/community.dto.ts b/libs/common/src/modules/community/dtos/community.dto.ts new file mode 100644 index 0000000..ebafb7e --- /dev/null +++ b/libs/common/src/modules/community/dtos/community.dto.ts @@ -0,0 +1,19 @@ +import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; + +export class CommunityDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public name: string; + + @IsString() + @IsOptional() + public description?: string; + + @IsUUID() + @IsNotEmpty() + public regionId: string; +} diff --git a/libs/common/src/modules/community/dtos/index.ts b/libs/common/src/modules/community/dtos/index.ts new file mode 100644 index 0000000..e030f3a --- /dev/null +++ b/libs/common/src/modules/community/dtos/index.ts @@ -0,0 +1 @@ +export * from './community.dto'; diff --git a/libs/common/src/modules/community/entities/community.entity.ts b/libs/common/src/modules/community/entities/community.entity.ts new file mode 100644 index 0000000..1474f05 --- /dev/null +++ b/libs/common/src/modules/community/entities/community.entity.ts @@ -0,0 +1,38 @@ +import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { CommunityDto } from '../dtos'; +import { RegionEntity } from '../../region/entities'; +import { SpaceEntity } from '../../space/entities'; +import { RoleEntity } from '../../role/entities'; + +@Entity({ name: 'community' }) +@Unique(['name']) +export class CommunityEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', + nullable: false, + }) + public uuid: string; + + @Column({ + length: 255, + nullable: false, + }) + name: string; + + @Column({ length: 255, nullable: true }) + description: string; + + @ManyToOne(() => RegionEntity, (region) => region.communities, { + nullable: false, + onDelete: 'CASCADE', + }) + region: RegionEntity; + + @OneToMany(() => SpaceEntity, (space) => space.community) + spaces: SpaceEntity[]; + + @OneToMany(() => RoleEntity, (role) => role.community) + roles: RoleEntity[]; +} diff --git a/libs/common/src/modules/community/entities/index.ts b/libs/common/src/modules/community/entities/index.ts new file mode 100644 index 0000000..61f1d4c --- /dev/null +++ b/libs/common/src/modules/community/entities/index.ts @@ -0,0 +1 @@ +export * from './community.entity'; diff --git a/libs/common/src/modules/community/repositories/community.repository.ts b/libs/common/src/modules/community/repositories/community.repository.ts new file mode 100644 index 0000000..137ef2e --- /dev/null +++ b/libs/common/src/modules/community/repositories/community.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { CommunityEntity } from '../entities'; + +@Injectable() +export class CommunityRepository extends Repository { + constructor(private dataSource: DataSource) { + super(CommunityEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/community/repositories/index.ts b/libs/common/src/modules/community/repositories/index.ts new file mode 100644 index 0000000..fb4a11d --- /dev/null +++ b/libs/common/src/modules/community/repositories/index.ts @@ -0,0 +1 @@ +export * from './community.repository'; diff --git a/libs/common/src/type/optional.type.ts b/libs/common/src/type/optional.type.ts new file mode 100644 index 0000000..cf62d1c --- /dev/null +++ b/libs/common/src/type/optional.type.ts @@ -0,0 +1,2 @@ +export type WithOptional = Omit & Partial; +export type WithRequired = Omit & Required; diff --git a/libs/common/src/util/parseToDate.ts b/libs/common/src/util/parseToDate.ts new file mode 100644 index 0000000..aa642a1 --- /dev/null +++ b/libs/common/src/util/parseToDate.ts @@ -0,0 +1,4 @@ +export function parseToDate(value: unknown): Date { + const valueInNumber = Number(value); + return new Date(valueInNumber); +} diff --git a/libs/common/src/validators/is-page-request-param.validator.ts b/libs/common/src/validators/is-page-request-param.validator.ts new file mode 100644 index 0000000..a6459e4 --- /dev/null +++ b/libs/common/src/validators/is-page-request-param.validator.ts @@ -0,0 +1,21 @@ +import { ValidateBy, ValidationOptions } from 'class-validator'; + +export function IsPageRequestParam( + validationOptions?: ValidationOptions, +): PropertyDecorator { + return ValidateBy( + { + name: 'IsPageRequestParam', + validator: { + validate(value) { + return IsPageParam(value); // you can return a Promise here as well, if you want to make async validation + }, + }, + }, + validationOptions, + ); +} + +function IsPageParam(value: any): boolean { + return !isNaN(Number(value)) && value > 0; +} diff --git a/libs/common/src/validators/is-size-request-param.validator.ts b/libs/common/src/validators/is-size-request-param.validator.ts new file mode 100644 index 0000000..4928a1b --- /dev/null +++ b/libs/common/src/validators/is-size-request-param.validator.ts @@ -0,0 +1,21 @@ +import { ValidateBy, ValidationOptions } from 'class-validator'; + +export function IsSizeRequestParam( + validationOptions?: ValidationOptions, +): PropertyDecorator { + return ValidateBy( + { + name: 'IsSizeRequestParam', + validator: { + validate(value) { + return IsSizeParam(value); // you can return a Promise here as well, if you want to make async validation + }, + }, + }, + validationOptions, + ); +} + +function IsSizeParam(value: any): boolean { + return !isNaN(Number(value)) && value > -1; +} diff --git a/libs/common/src/validators/is-sort-param.validator.ts b/libs/common/src/validators/is-sort-param.validator.ts new file mode 100644 index 0000000..dceee2e --- /dev/null +++ b/libs/common/src/validators/is-sort-param.validator.ts @@ -0,0 +1,54 @@ +import { ValidateBy, ValidationOptions } from 'class-validator'; + +export function IsSortParam( + validationOptions?: ValidationOptions, + allowedFieldName?: string[], +): PropertyDecorator { + return ValidateBy( + { + name: 'IsSortParam', + validator: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types + validate(value) { + return IsValidMultipleSortParam(value, allowedFieldName); // you can return a Promise here as well, if you want to make async validation + }, + }, + }, + validationOptions, + ); +} + +function IsValidMultipleSortParam( + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types + value: any, + allowedFieldName?: string[], +): boolean { + if (typeof value !== 'string') { + return false; + } + + const conditions: string[] = value.split(','); + const isValid: boolean = conditions.every((condition) => { + const combination: string[] = condition.split(':'); + if (combination.length !== 2) { + return false; + } + const field = combination[0].trim(); + const direction = combination[1].trim(); + + if (!field) { + return false; + } + + if (allowedFieldName?.length && !allowedFieldName.includes(field)) { + return false; + } + + if (!['asc', 'desc'].includes(direction.toLowerCase())) { + return false; + } + + return true; + }); + return isValid; +} diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index 356c90c..d635a80 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -2,7 +2,9 @@ import { CommunityService } from '../services/community.service'; import { Body, Controller, + Delete, Get, + HttpException, HttpStatus, Param, Post, @@ -10,23 +12,25 @@ import { Query, UseGuards, } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { AddCommunityDto, AddUserCommunityDto, } from '../dtos/add.community.dto'; -import { GetCommunityChildDto } from '../dtos/get.community.dto'; +import { GetCommunityParams } from '../dtos/get.community.dto'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; +// import { CheckUserCommunityGuard } from 'src/guards/user.community.guard'; import { AdminRoleGuard } from 'src/guards/admin.role.guard'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; -import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; -import { SpaceType } from '@app/common/constants/space-type.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; // import { CommunityPermissionGuard } from 'src/guards/community.permission.guard'; @ApiTags('Community Module') @Controller({ - version: EnableDisableStatusEnum.ENABLED, - path: SpaceType.COMMUNITY, + version: '1', + path: ControllerRoute.COMMUNITY.ROUTE, }) export class CommunityController { constructor(private readonly communityService: CommunityService) {} @@ -34,73 +38,103 @@ export class CommunityController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post() - async addCommunity(@Body() addCommunityDto: AddCommunityDto) { - const community = await this.communityService.addCommunity(addCommunityDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Community added successfully', - data: community, - }; + @ApiOperation({ + summary: ControllerRoute.COMMUNITY.ACTIONS.CREATE_COMMUNITY_SUMMARY, + description: ControllerRoute.COMMUNITY.ACTIONS.CREATE_COMMUNITY_DESCRIPTION, + }) + async createCommunity( + @Body() addCommunityDto: AddCommunityDto, + ): Promise { + return await this.communityService.createCommunity(addCommunityDto); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get(':communityUuid') - async getCommunityByUuid(@Param('communityUuid') communityUuid: string) { - const community = - await this.communityService.getCommunityByUuid(communityUuid); - return community; + @ApiOperation({ + summary: ControllerRoute.COMMUNITY.ACTIONS.GET_COMMUNITY_BY_ID_SUMMARY, + description: + ControllerRoute.COMMUNITY.ACTIONS.GET_COMMUNITY_BY_ID_DESCRIPTION, + }) + @Get('/:communityId') + async getCommunityByUuid( + @Param() params: GetCommunityParams, + ): Promise { + return await this.communityService.getCommunityById(params.communityId); } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.COMMUNITY.ACTIONS.LIST_COMMUNITY_SUMMARY, + description: ControllerRoute.COMMUNITY.ACTIONS.LIST_COMMUNITY_DESCRIPTION, + }) @Get() - async getCommunities() { - const communities = await this.communityService.getCommunities(); - return communities; + async getCommunities( + @Query() query: PaginationRequestGetListDto, + ): Promise { + return this.communityService.getCommunities(query); } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get('child/:communityUuid') - async getCommunityChildByUuid( - @Param('communityUuid') communityUuid: string, - @Query() query: GetCommunityChildDto, + @ApiOperation({ + summary: ControllerRoute.COMMUNITY.ACTIONS.UPDATE_COMMUNITY_SUMMARY, + description: ControllerRoute.COMMUNITY.ACTIONS.UPDATE_COMMUNITY_DESCRIPTION, + }) + @Put('/:communityId') + async updateCommunity( + @Param() param: GetCommunityParams, + @Body() updateCommunityDto: UpdateCommunityNameDto, ) { - const community = await this.communityService.getCommunityChildByUuid( - communityUuid, - query, + return this.communityService.updateCommunity( + param.communityId, + updateCommunityDto, ); - return community; + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Delete('/:communityId') + @ApiOperation({ + summary: ControllerRoute.COMMUNITY.ACTIONS.DELETE_COMMUNITY_SUMMARY, + description: ControllerRoute.COMMUNITY.ACTIONS.DELETE_COMMUNITY_DESCRIPTION, + }) + async deleteCommunity( + @Param() param: GetCommunityParams, + ): Promise { + return this.communityService.deleteCommunity(param.communityId); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('user/:userUuid') async getCommunitiesByUserId(@Param('userUuid') userUuid: string) { - return await this.communityService.getCommunitiesByUserId(userUuid); + try { + return await this.communityService.getCommunitiesByUserId(userUuid); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } } + @ApiBearerAuth() @UseGuards(AdminRoleGuard) @Post('user') async addUserCommunity(@Body() addUserCommunityDto: AddUserCommunityDto) { - await this.communityService.addUserCommunity(addUserCommunityDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'user community added successfully', - }; - } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Put(':communityUuid') - async renameCommunityByUuid( - @Param('communityUuid') communityUuid: string, - @Body() updateCommunityDto: UpdateCommunityNameDto, - ) { - const community = await this.communityService.renameCommunityByUuid( - communityUuid, - updateCommunityDto, - ); - return community; + try { + await this.communityService.addUserCommunity(addUserCommunityDto); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'user community added successfully', + }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } } } diff --git a/src/community/dtos/add.community.dto.ts b/src/community/dtos/add.community.dto.ts index 06aec7c..bd66d56 100644 --- a/src/community/dtos/add.community.dto.ts +++ b/src/community/dtos/add.community.dto.ts @@ -1,19 +1,38 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; export class AddCommunityDto { @ApiProperty({ - description: 'communityName', + description: 'The name of the community', + example: 'Community A', required: true, }) @IsString() @IsNotEmpty() - public communityName: string; + public name: string; + + @ApiProperty({ + description: 'A description of the community', + example: 'This is a community for developers.', + required: false, + }) + @IsString() + @IsOptional() + public description?: string; + + @ApiProperty({ + description: 'The UUID of the region this community belongs to', + example: 'b6fa62c5-4fcf-4872-89a0-571cf431a6c7', + }) + @IsUUID() + @IsNotEmpty() + public regionId: string; constructor(dto: Partial) { Object.assign(this, dto); } } + export class AddUserCommunityDto { @ApiProperty({ description: 'communityUuid', diff --git a/src/community/dtos/get.community.dto.ts b/src/community/dtos/get.community.dto.ts index cae892b..2b84e19 100644 --- a/src/community/dtos/get.community.dto.ts +++ b/src/community/dtos/get.community.dto.ts @@ -7,6 +7,7 @@ import { IsNotEmpty, IsOptional, IsString, + IsUUID, Min, } from 'class-validator'; @@ -20,6 +21,18 @@ export class GetCommunityDto { public communityUuid: string; } +export class GetCommunityParams { + @ApiProperty({ + description: 'Community id of the specific community', + required: true, + name: 'communityId', + }) + @IsUUID() + @IsString() + @IsNotEmpty() + public communityId: string; +} + export class GetCommunityChildDto { @ApiProperty({ example: 1, description: 'Page number', required: true }) @IsInt({ message: 'Page must be a number' }) diff --git a/src/community/dtos/update.community.dto.ts b/src/community/dtos/update.community.dto.ts index 6f15d43..aba1fc2 100644 --- a/src/community/dtos/update.community.dto.ts +++ b/src/community/dtos/update.community.dto.ts @@ -3,12 +3,12 @@ import { IsNotEmpty, IsString } from 'class-validator'; export class UpdateCommunityNameDto { @ApiProperty({ - description: 'communityName', + description: 'community name', required: true, }) @IsString() @IsNotEmpty() - public communityName: string; + public name: string; constructor(dto: Partial) { Object.assign(this, dto); diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index 145dc13..1cfce8b 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -1,183 +1,173 @@ -import { GetCommunityChildDto } from './../dtos/get.community.dto'; -import { SpaceTypeRepository } from './../../../libs/common/src/modules/space/repositories/space.repository'; -import { - Injectable, - HttpException, - HttpStatus, - BadRequestException, -} from '@nestjs/common'; +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddCommunityDto, AddUserCommunityDto } from '../dtos'; -import { - CommunityChildInterface, - GetCommunitiesInterface, - GetCommunityByUserUuidInterface, - GetCommunityByUuidInterface, - RenameCommunityByUuidInterface, -} from '../interface/community.interface'; -import { SpaceEntity } from '@app/common/modules/space/entities'; +import { GetCommunityByUserUuidInterface } from '../interface/community.interface'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; import { UserSpaceRepository } from '@app/common/modules/user/repositories'; -import { SpaceType } from '@app/common/constants/space-type.enum'; -import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; +import { RegionRepository } from '@app/common/modules/region/repositories'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { + TypeORMCustomModel, + TypeORMCustomModelFindAllQuery, +} from '@app/common/models/typeOrmCustom.model'; +import { PageResponse } from '@app/common/dto/pagination.response.dto'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { CommunityDto } from '@app/common/modules/community/dtos'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; @Injectable() export class CommunityService { constructor( private readonly spaceRepository: SpaceRepository, - private readonly spaceTypeRepository: SpaceTypeRepository, private readonly userSpaceRepository: UserSpaceRepository, + private readonly communityRepository: CommunityRepository, + private readonly regionRepository: RegionRepository, ) {} - async addCommunity(addCommunityDto: AddCommunityDto) { - try { - const spaceType = await this.spaceTypeRepository.findOne({ - where: { - type: SpaceType.COMMUNITY, - }, - }); + async createCommunity(dto: AddCommunityDto): Promise { + const { regionId, name, description } = dto; + // Find the region by the provided regionId + const region = await this.regionRepository.findOneBy({ uuid: regionId }); - const community = await this.spaceRepository.save({ - spaceName: addCommunityDto.communityName, - spaceType: { uuid: spaceType.uuid }, - }); - return community; - } catch (err) { - throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR); - } - } - - async getCommunityByUuid( - communityUuid: string, - ): Promise { - try { - const community = await this.spaceRepository.findOne({ - where: { - uuid: communityUuid, - spaceType: { - type: SpaceType.COMMUNITY, - }, - }, - relations: ['spaceType'], - }); - if ( - !community || - !community.spaceType || - community.spaceType.type !== SpaceType.COMMUNITY - ) { - throw new BadRequestException('Invalid community UUID'); - } - return { - uuid: community.uuid, - createdAt: community.createdAt, - updatedAt: community.updatedAt, - name: community.spaceName, - type: community.spaceType.type, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Community not found', HttpStatus.NOT_FOUND); - } - } - } - async getCommunities(): Promise { - try { - const community = await this.spaceRepository.find({ - where: { spaceType: { type: SpaceType.COMMUNITY } }, - relations: ['spaceType'], - }); - return community.map((community) => ({ - uuid: community.uuid, - createdAt: community.createdAt, - updatedAt: community.updatedAt, - name: community.spaceName, - type: community.spaceType.type, - })); - } catch (err) { - throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR); - } - } - async getCommunityChildByUuid( - communityUuid: string, - getCommunityChildDto: GetCommunityChildDto, - ): Promise { - try { - const { includeSubSpaces, page, pageSize } = getCommunityChildDto; - - const space = await this.spaceRepository.findOneOrFail({ - where: { uuid: communityUuid }, - relations: ['children', 'spaceType'], - }); - - if ( - !space || - !space.spaceType || - space.spaceType.type !== SpaceType.COMMUNITY - ) { - throw new BadRequestException('Invalid community UUID'); - } - const totalCount = await this.spaceRepository.count({ - where: { parent: { uuid: space.uuid } }, - }); - const children = await this.buildHierarchy( - space, - includeSubSpaces, - page, - pageSize, + if (!region) { + throw new HttpException( + `Region with ID ${dto.regionId} not found.`, + HttpStatus.NOT_FOUND, ); - return { - uuid: space.uuid, - name: space.spaceName, - type: space.spaceType.type, - totalCount, - children, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Community not found', HttpStatus.NOT_FOUND); - } } - } - private async buildHierarchy( - space: SpaceEntity, - includeSubSpaces: boolean, - page: number, - pageSize: number, - ): Promise { - const children = await this.spaceRepository.find({ - where: { parent: { uuid: space.uuid } }, - relations: ['spaceType'], - skip: (page - 1) * pageSize, - take: pageSize, + // Create the new community entity + const community = this.communityRepository.create({ + name: name, + description: description, + region: region, // Associate with the found region }); - if (!children || children.length === 0 || !includeSubSpaces) { - return children - .filter((child) => child.spaceType.type !== SpaceType.COMMUNITY) // Filter remaining community type - .map((child) => ({ - uuid: child.uuid, - name: child.spaceName, - type: child.spaceType.type, - })); + // Save the community to the database + try { + 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(id: string): Promise { + const community = await this.communityRepository.findOneBy({ uuid: id }); + + // If the community is not found, throw a 404 NotFoundException + if (!community) { + throw new HttpException( + `Community with ID ${id} not found.`, + HttpStatus.NOT_FOUND, + ); } - const childHierarchies = await Promise.all( - children - .filter((child) => child.spaceType.type !== SpaceType.COMMUNITY) // Filter remaining community type - .map(async (child) => ({ - uuid: child.uuid, - name: child.spaceName, - type: child.spaceType.type, - children: await this.buildHierarchy(child, true, 1, pageSize), - })), - ); + // Return a success response + return new SuccessResponseDto({ + data: community, + message: 'Community fetched successfully', + }); + } - return childHierarchies; + async getCommunities( + pageable: Partial, + ): Promise { + pageable.modelName = 'community'; + + const customModel = TypeORMCustomModel(this.communityRepository); + + const { baseResponseDto, paginationResponseDto } = + await customModel.findAll(pageable); + + return new PageResponse( + baseResponseDto, + paginationResponseDto, + ); + } + + async updateCommunity( + id: string, + updateCommunityDto: UpdateCommunityNameDto, + ): Promise { + const community = await this.communityRepository.findOne({ + where: { uuid: id }, + }); + + // If the community doesn't exist, throw a 404 error + if (!community) { + throw new HttpException( + `Community with ID ${id} not found`, + HttpStatus.NOT_FOUND, + ); + } + + try { + const { name } = updateCommunityDto; + + // Find the community by its UUID + + // Update the community name + community.name = name; + + // Save the updated community back to the database + const updatedCommunity = await this.communityRepository.save(community); + + // Return a SuccessResponseDto with the updated community data + return new SuccessResponseDto({ + 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('Community not found', HttpStatus.NOT_FOUND); + } + } + } + + async deleteCommunity(id: string): Promise { + const community = await this.communityRepository.findOne({ + where: { uuid: id }, + }); + + // If the community is not found, throw an error + if (!community) { + throw new HttpException( + `Community with ID ${id} not found`, + HttpStatus.NOT_FOUND, + ); + } + try { + // Find the community by its uuid + + // Remove the community from the database + await this.communityRepository.remove(community); + + // Return success response + return new SuccessResponseDto({ + message: `Community with ID ${id} successfully deleted`, + data: null, + }); + } catch (err) { + // Catch and handle any errors + if (err instanceof HttpException) { + throw err; // If it's an HttpException, rethrow it + } else { + throw new HttpException( + 'An error occurred while deleting the community', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } async getCommunitiesByUserId( @@ -188,7 +178,6 @@ export class CommunityService { relations: ['space', 'space.spaceType'], where: { user: { uuid: userUuid }, - space: { spaceType: { type: SpaceType.COMMUNITY } }, }, }); @@ -200,7 +189,7 @@ export class CommunityService { } const spaces = communities.map((community) => ({ uuid: community.space.uuid, - name: community.space.spaceName, + name: community.space.name, type: community.space.spaceType.type, })); @@ -221,7 +210,7 @@ export class CommunityService { space: { uuid: addUserCommunityDto.communityUuid }, }); } catch (err) { - if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) { + if (err.code === '23505') { throw new HttpException( 'User already belongs to this community', HttpStatus.BAD_REQUEST, @@ -233,46 +222,4 @@ export class CommunityService { ); } } - async renameCommunityByUuid( - communityUuid: string, - updateCommunityDto: UpdateCommunityNameDto, - ): Promise { - try { - const community = await this.spaceRepository.findOneOrFail({ - where: { uuid: communityUuid }, - relations: ['spaceType'], - }); - - if ( - !community || - !community.spaceType || - community.spaceType.type !== SpaceType.COMMUNITY - ) { - throw new BadRequestException('Invalid community UUID'); - } - - await this.spaceRepository.update( - { uuid: communityUuid }, - { spaceName: updateCommunityDto.communityName }, - ); - - // Fetch the updated community - const updatedCommunity = await this.spaceRepository.findOneOrFail({ - where: { uuid: communityUuid }, - relations: ['spaceType'], - }); - - return { - uuid: updatedCommunity.uuid, - name: updatedCommunity.spaceName, - type: updatedCommunity.spaceType.type, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Community not found', HttpStatus.NOT_FOUND); - } - } - } } From d14d3b5ba292902658a8bccc54cffe3363326fd1 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 15 Oct 2024 11:38:49 +0400 Subject: [PATCH 002/109] added community space --- libs/common/src/constants/controller-route.ts | 11 +++ libs/common/src/models/typeOrmCustom.model.ts | 6 +- .../community/entities/community.entity.ts | 4 - .../modules/region/entities/region.entity.ts | 4 + .../modules/space/entities/space.entity.ts | 17 +++- .../src/util/buildTypeORMIncludeQuery.ts | 42 ++++++++++ libs/common/src/util/buildTypeORMSortQuery.ts | 18 +++++ .../src/util/buildTypeORMWhereClause.ts | 10 +++ .../src/util/getPaginationResponseDto.ts | 21 +++++ src/community/community.module.ts | 9 ++- .../community-spaces.controller.ts | 36 +++++++++ .../controllers/community.controller.ts | 1 - src/community/controllers/index.ts | 1 + .../services/community-space.service.ts | 81 +++++++++++++++++++ src/community/services/community.service.ts | 2 +- src/community/services/index.ts | 1 + 16 files changed, 253 insertions(+), 11 deletions(-) create mode 100644 libs/common/src/util/buildTypeORMIncludeQuery.ts create mode 100644 libs/common/src/util/buildTypeORMSortQuery.ts create mode 100644 libs/common/src/util/buildTypeORMWhereClause.ts create mode 100644 libs/common/src/util/getPaginationResponseDto.ts create mode 100644 src/community/controllers/community-spaces.controller.ts create mode 100644 src/community/services/community-space.service.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 5e643c8..7a971fd 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -38,4 +38,15 @@ export class ControllerRoute { 'Create community in the database and return in model'; }; }; + + static COMMUNITYSPACE = class { + public static readonly ROUTE = 'communities/:id/spaces'; + static ACTIONS = class { + public static readonly GET_COMMUNITY_SPACES_HIERARCHY_SUMMARY = + 'Fetch hierarchical structure of spaces within a community.'; + + public static readonly GET_COMMUNITY_SPACES_HIERARCHY_DESCRIPTION = + 'retrieves all the spaces associated with a given community, organized into a hierarchical structure.'; + }; + }; } diff --git a/libs/common/src/models/typeOrmCustom.model.ts b/libs/common/src/models/typeOrmCustom.model.ts index b1e102a..72a818d 100644 --- a/libs/common/src/models/typeOrmCustom.model.ts +++ b/libs/common/src/models/typeOrmCustom.model.ts @@ -3,9 +3,9 @@ import { InternalServerErrorException } from '@nestjs/common'; import { BaseResponseDto } from '../dto/base.response.dto'; import { PageResponseDto } from '../dto/pagination.response.dto'; import { buildTypeORMSortQuery } from '../util/buildTypeORMSortQuery'; -import { getPaginationResponseDto } from '../util/getPaginationResponseDto'; -import { buildTypeORMWhereClause } from '../util/buildTypeORMWhereClause'; import { buildTypeORMIncludeQuery } from '../util/buildTypeORMIncludeQuery'; +import { buildTypeORMWhereClause } from '../util/buildTypeORMWhereClause'; +import { getPaginationResponseDto } from '../util/getPaginationResponseDto'; export interface TypeORMCustomModelFindAllQuery { page: number | undefined; @@ -101,7 +101,7 @@ export function TypeORMCustomModel(repository: Repository) { // Ensure the whereClause is passed directly to findAndCount const [data, count] = await repository.findAndCount({ - where: whereClause.where || whereClause, // Don't wrap this under another 'where' + where: whereClause, take: size, skip: skip, order: order, diff --git a/libs/common/src/modules/community/entities/community.entity.ts b/libs/common/src/modules/community/entities/community.entity.ts index 1474f05..1fafcf9 100644 --- a/libs/common/src/modules/community/entities/community.entity.ts +++ b/libs/common/src/modules/community/entities/community.entity.ts @@ -3,7 +3,6 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { CommunityDto } from '../dtos'; import { RegionEntity } from '../../region/entities'; import { SpaceEntity } from '../../space/entities'; -import { RoleEntity } from '../../role/entities'; @Entity({ name: 'community' }) @Unique(['name']) @@ -32,7 +31,4 @@ export class CommunityEntity extends AbstractEntity { @OneToMany(() => SpaceEntity, (space) => space.community) spaces: SpaceEntity[]; - - @OneToMany(() => RoleEntity, (role) => role.community) - roles: RoleEntity[]; } diff --git a/libs/common/src/modules/region/entities/region.entity.ts b/libs/common/src/modules/region/entities/region.entity.ts index b982f8a..a9bdac1 100644 --- a/libs/common/src/modules/region/entities/region.entity.ts +++ b/libs/common/src/modules/region/entities/region.entity.ts @@ -2,6 +2,7 @@ import { Column, Entity, OneToMany } from 'typeorm'; import { RegionDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { UserEntity } from '../../user/entities'; +import { CommunityEntity } from '../../community/entities'; @Entity({ name: 'region' }) export class RegionEntity extends AbstractEntity { @@ -13,6 +14,9 @@ export class RegionEntity extends AbstractEntity { @OneToMany(() => UserEntity, (user) => user.region) users: UserEntity[]; + @OneToMany(() => CommunityEntity, (community) => community.region) + communities: CommunityEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 10e78a8..fefe30c 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -1,8 +1,16 @@ -import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; +import { + Column, + Entity, + JoinColumn, + ManyToOne, + OneToMany, + Unique, +} from 'typeorm'; import { SpaceDto, SpaceTypeDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { UserSpaceEntity } from '../../user/entities'; import { DeviceEntity } from '../../device/entities'; +import { CommunityEntity } from '../../community/entities'; @Entity({ name: 'space-type' }) export class SpaceTypeEntity extends AbstractEntity { @@ -45,6 +53,13 @@ export class SpaceEntity extends AbstractEntity { }) public spaceName: string; + @ManyToOne(() => CommunityEntity, (community) => community.spaces, { + nullable: false, + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'community_id' }) + community: CommunityEntity; + @Column({ nullable: true, }) diff --git a/libs/common/src/util/buildTypeORMIncludeQuery.ts b/libs/common/src/util/buildTypeORMIncludeQuery.ts new file mode 100644 index 0000000..50583cc --- /dev/null +++ b/libs/common/src/util/buildTypeORMIncludeQuery.ts @@ -0,0 +1,42 @@ +type TypeORMIncludeQuery = string[]; + +const mappingInclude: { [key: string]: any } = { + roles: { + role: true, + }, + users: { + user: true, + }, + community: { + community: true, + }, + space: { + space: true, + }, +}; + +export function buildTypeORMIncludeQuery( + modelName: string, + includeParam?: string, +): TypeORMIncludeQuery | undefined { + if (includeParam) { + const relations: TypeORMIncludeQuery = []; + const fieldsToInclude: string[] = includeParam.split(','); + + fieldsToInclude.forEach((field: string) => { + if (mappingInclude[field]) { + relations.push(field); // Push mapped field + } else { + console.warn( + `Field ${field} not found in mappingInclude for ${modelName}`, + ); + } + }); + + console.log(`Including relations for ${modelName}:`, relations); + + return relations; + } + + return undefined; // If no includes, return undefined +} diff --git a/libs/common/src/util/buildTypeORMSortQuery.ts b/libs/common/src/util/buildTypeORMSortQuery.ts new file mode 100644 index 0000000..6f8c8be --- /dev/null +++ b/libs/common/src/util/buildTypeORMSortQuery.ts @@ -0,0 +1,18 @@ +type TypeORMSortQuery = { [key: string]: 'ASC' | 'DESC' }; + +export function buildTypeORMSortQuery( + sortParam: string | undefined, +): TypeORMSortQuery { + // sortParam format: userId:asc,createdDate:desc + if (!sortParam) { + return {}; + } + + const conditions: string[] = sortParam.split(','); + + return conditions.reduce((acc: TypeORMSortQuery, condition) => { + const [field, direction] = condition.split(':').map((str) => str.trim()); + acc[field] = direction.toUpperCase() === 'DESC' ? 'DESC' : 'ASC'; + return acc; + }, {}); +} diff --git a/libs/common/src/util/buildTypeORMWhereClause.ts b/libs/common/src/util/buildTypeORMWhereClause.ts new file mode 100644 index 0000000..6eda8aa --- /dev/null +++ b/libs/common/src/util/buildTypeORMWhereClause.ts @@ -0,0 +1,10 @@ +import { FindOptionsWhere } from 'typeorm'; + +export function buildTypeORMWhereClause({ + where, +}: { + where?: FindOptionsWhere | FindOptionsWhere[]; // Accepts both object and array formats +}): FindOptionsWhere | FindOptionsWhere[] { + // Return the 'where' clause directly, without wrapping + return where || {}; // If 'where' is undefined, return an empty object +} diff --git a/libs/common/src/util/getPaginationResponseDto.ts b/libs/common/src/util/getPaginationResponseDto.ts new file mode 100644 index 0000000..f721aa7 --- /dev/null +++ b/libs/common/src/util/getPaginationResponseDto.ts @@ -0,0 +1,21 @@ +import { PageResponseDto } from '../dto/pagination.response.dto'; + +export function getPaginationResponseDto( + count: number, + page: number, + size: number, +): PageResponseDto { + const totalItem = count; + const totalPage = Math.ceil(totalItem / size); + const hasNext = page < totalPage ? true : false; + const hasPrevious = page === 1 || page > totalPage ? false : true; + + return { + hasNext, + hasPrevious, + page, + size, + totalItem, + totalPage, + }; +} diff --git a/src/community/community.module.ts b/src/community/community.module.ts index d5f7d93..da17bc5 100644 --- a/src/community/community.module.ts +++ b/src/community/community.module.ts @@ -9,15 +9,22 @@ import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; import { UserRepository } from '@app/common/modules/user/repositories'; import { SpacePermissionService } from '@app/common/helper/services'; +import { CommunitySpaceService } from './services'; +import { CommunitySpaceController } from './controllers'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { RegionRepository } from '@app/common/modules/region/repositories'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], - controllers: [CommunityController], + controllers: [CommunityController, CommunitySpaceController], providers: [ CommunityService, + CommunitySpaceService, SpaceRepository, SpaceTypeRepository, UserSpaceRepository, + RegionRepository, + CommunityRepository, UserRepository, SpacePermissionService, ], diff --git a/src/community/controllers/community-spaces.controller.ts b/src/community/controllers/community-spaces.controller.ts new file mode 100644 index 0000000..5c14f86 --- /dev/null +++ b/src/community/controllers/community-spaces.controller.ts @@ -0,0 +1,36 @@ +import { Controller, Get, Param, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; +import { GetCommunityParams } from '../dtos/get.community.dto'; +// import { CheckUserCommunityGuard } from 'src/guards/user.community.guard'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { CommunitySpaceService } from '../services'; + +@ApiTags('Community Module') +@Controller({ + version: '1', + path: ControllerRoute.COMMUNITYSPACE.ROUTE, +}) +export class CommunitySpaceController { + constructor(private readonly communitySpaceService: CommunitySpaceService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: + ControllerRoute.COMMUNITYSPACE.ACTIONS + .GET_COMMUNITY_SPACES_HIERARCHY_SUMMARY, + description: + ControllerRoute.COMMUNITYSPACE.ACTIONS + .GET_COMMUNITY_SPACES_HIERARCHY_DESCRIPTION, + }) + @Get('/:communityId') + async getCommunityByUuid( + @Param() params: GetCommunityParams, + ): Promise { + return await this.communitySpaceService.getSpacesHierarchyForCommunity( + params.communityId, + ); + } +} diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index d635a80..aee04be 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -25,7 +25,6 @@ import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { ControllerRoute } from '@app/common/constants/controller-route'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; -// import { CommunityPermissionGuard } from 'src/guards/community.permission.guard'; @ApiTags('Community Module') @Controller({ diff --git a/src/community/controllers/index.ts b/src/community/controllers/index.ts index b78bbea..fd6fd56 100644 --- a/src/community/controllers/index.ts +++ b/src/community/controllers/index.ts @@ -1 +1,2 @@ export * from './community.controller'; +export * from './community-spaces.controller'; diff --git a/src/community/services/community-space.service.ts b/src/community/services/community-space.service.ts new file mode 100644 index 0000000..0b80836 --- /dev/null +++ b/src/community/services/community-space.service.ts @@ -0,0 +1,81 @@ +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { SpaceEntity } from '@app/common/modules/space/entities'; + +@Injectable() +export class CommunitySpaceService { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly communityRepository: CommunityRepository, + ) {} + + async getSpacesHierarchyForCommunity( + communityId: string, + ): Promise { + const community = await this.communityRepository.findOne({ + where: { uuid: communityId }, + }); + + // If the community doesn't exist, throw a 404 error + if (!community) { + throw new HttpException( + `Community with ID ${communityId} not found`, + HttpStatus.NOT_FOUND, + ); + } + try { + // Get all spaces related to the community, including the parent-child relations + const spaces = await this.spaceRepository.find({ + where: { community: { uuid: communityId } }, + relations: ['parent', 'children'], // Include parent and children relations + }); + + // Organize spaces into a hierarchical structure + const spaceHierarchy = this.buildSpaceHierarchy(spaces); + + return new SuccessResponseDto({ + message: `Spaces in community ${communityId} successfully fetched in hierarchy`, + data: spaceHierarchy, + statusCode: HttpStatus.OK, + }); + } catch (error) { + throw new HttpException( + 'An error occurred while fetching the spaces', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private buildSpaceHierarchy(spaces: SpaceEntity[]): SpaceEntity[] { + const map = new Map(); + + // Step 1: Create a map of spaces by UUID + spaces.forEach((space) => { + map.set( + space.uuid, + this.spaceRepository.create({ + ...space, + children: [], // Add children if needed + }), + ); + }); + + // Step 2: Organize the hierarchy + const rootSpaces: SpaceEntity[] = []; + spaces.forEach((space) => { + if (space.parent && space.parent.uuid) { + // If the space has a parent, add it to the parent's children array + const parent = map.get(space.parent.uuid); + parent?.children?.push(map.get(space.uuid)); + } else { + // If the space doesn't have a parent, it's a root space + rootSpaces.push(map.get(space.uuid)); + } + }); + + return rootSpaces; // Return the root spaces with children nested within them + } +} diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index 1cfce8b..a1bcff4 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -189,7 +189,7 @@ export class CommunityService { } const spaces = communities.map((community) => ({ uuid: community.space.uuid, - name: community.space.name, + name: community.space.spaceName, type: community.space.spaceType.type, })); diff --git a/src/community/services/index.ts b/src/community/services/index.ts index 2408c7c..257481b 100644 --- a/src/community/services/index.ts +++ b/src/community/services/index.ts @@ -1 +1,2 @@ export * from './community.service'; +export * from './community-space.service'; From b8d4a080ef9a64b8989b3abe09377b48d7bb91a8 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 15 Oct 2024 12:04:54 +0400 Subject: [PATCH 003/109] community user relation --- .../modules/community/entities/community.entity.ts | 13 ++++++++++++- .../common/src/modules/user/entities/user.entity.ts | 12 ++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/libs/common/src/modules/community/entities/community.entity.ts b/libs/common/src/modules/community/entities/community.entity.ts index 1fafcf9..c63da01 100644 --- a/libs/common/src/modules/community/entities/community.entity.ts +++ b/libs/common/src/modules/community/entities/community.entity.ts @@ -1,8 +1,16 @@ -import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; +import { + Column, + Entity, + ManyToMany, + ManyToOne, + OneToMany, + Unique, +} from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { CommunityDto } from '../dtos'; import { RegionEntity } from '../../region/entities'; import { SpaceEntity } from '../../space/entities'; +import { UserEntity } from '../../user/entities'; @Entity({ name: 'community' }) @Unique(['name']) @@ -31,4 +39,7 @@ export class CommunityEntity extends AbstractEntity { @OneToMany(() => SpaceEntity, (space) => space.community) spaces: SpaceEntity[]; + + @ManyToMany(() => UserEntity, (user) => user.communities) + users: UserEntity[]; } diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 141f5de..18f51fb 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -2,6 +2,8 @@ import { Column, DeleteDateColumn, Entity, + JoinTable, + ManyToMany, ManyToOne, OneToMany, Unique, @@ -26,6 +28,7 @@ import { OtpType } from '../../../../src/constants/otp-type.enum'; import { RoleTypeEntity } from '../../role-type/entities'; import { SpaceEntity } from '../../space/entities'; import { VisitorPasswordEntity } from '../../visitor-password/entities'; +import { CommunityEntity } from '../../community/entities'; @Entity({ name: 'user' }) export class UserEntity extends AbstractEntity { @@ -115,6 +118,15 @@ export class UserEntity extends AbstractEntity { (visitorPassword) => visitorPassword.user, ) public visitorPasswords: VisitorPasswordEntity[]; + + @ManyToMany(() => CommunityEntity, (community) => community.users) + @JoinTable({ + name: 'user_communities', // Join table to link users and communities + joinColumn: { name: 'user_id', referencedColumnName: 'uuid' }, + inverseJoinColumn: { name: 'community_id', referencedColumnName: 'uuid' }, + }) + communities: CommunityEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); From d6777c8ec138f9164fe14ccd34edd17837f0e004 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 17 Oct 2024 10:55:26 +0400 Subject: [PATCH 004/109] Added all community endpoints --- libs/common/src/constants/controller-route.ts | 26 +++- libs/common/src/helper/helper.module.ts | 10 +- .../services/community.permission.service.ts | 33 +++++ src/community/community.module.ts | 2 - .../community-spaces.controller.ts | 13 +- .../controllers/community.controller.ts | 58 ++------- src/community/dtos/get.community.dto.ts | 4 +- src/community/services/community.service.ts | 58 +-------- src/guards/community.permission.guard.ts | 18 ++- src/users/controllers/index.ts | 1 + .../user-communities.controller.ts | 80 ++++++++++++ src/users/dtos/index.ts | 2 + src/users/dtos/user-community-param.dto.ts | 12 ++ src/users/dtos/user-param.dto.ts | 11 ++ src/users/services/index.ts | 1 + src/users/services/user-community.service.ts | 114 ++++++++++++++++++ src/users/user.module.ts | 16 ++- 17 files changed, 333 insertions(+), 126 deletions(-) create mode 100644 libs/common/src/helper/services/community.permission.service.ts create mode 100644 src/users/controllers/user-communities.controller.ts create mode 100644 src/users/dtos/user-community-param.dto.ts create mode 100644 src/users/dtos/user-param.dto.ts create mode 100644 src/users/services/user-community.service.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 7a971fd..5c71b4d 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -10,7 +10,7 @@ export class ControllerRoute { }; static COMMUNITY = class { - public static readonly ROUTE = 'communities'; + public static readonly ROUTE = 'community'; static ACTIONS = class { public static readonly GET_COMMUNITY_BY_ID_SUMMARY = 'Get community by community id'; @@ -39,8 +39,8 @@ export class ControllerRoute { }; }; - static COMMUNITYSPACE = class { - public static readonly ROUTE = 'communities/:id/spaces'; + static COMMUNITY_SPACE = class { + public static readonly ROUTE = 'community/:id/spaces'; static ACTIONS = class { public static readonly GET_COMMUNITY_SPACES_HIERARCHY_SUMMARY = 'Fetch hierarchical structure of spaces within a community.'; @@ -49,4 +49,24 @@ export class ControllerRoute { 'retrieves all the spaces associated with a given community, organized into a hierarchical structure.'; }; }; + + static USER_COMMUNITY = class { + public static readonly ROUTE = '/user/:userUuid/communities'; + static ACTIONS = class { + public static readonly GET_USER_COMMUNITIES_SUMMARY = + 'Get communities associated with a user by user UUID'; + public static readonly GET_USER_COMMUNITIES_DESCRIPTION = + 'This endpoint returns the list of communities a specific user is associated with'; + + public static readonly ASSOCIATE_USER_COMMUNITY_SUMMARY = + 'Associate a user with a community'; + public static readonly ASSOCIATE_USER_COMMUNITY_DESCRIPTION = + 'This endpoint associates a user with a community.'; + + public static readonly DISASSOCIATE_USER_COMMUNITY_SUMMARY = + 'Disassociate a user from a community'; + public static readonly DISASSOCIATE_USER_COMMUNITY_DESCRIPTION = + 'This endpoint disassociates a user from a community. It removes the relationship between the specified user and the community. If the user is not associated with the community, an error will be returned.'; + }; + }; } diff --git a/libs/common/src/helper/helper.module.ts b/libs/common/src/helper/helper.module.ts index 34a51c2..b4cf2fd 100644 --- a/libs/common/src/helper/helper.module.ts +++ b/libs/common/src/helper/helper.module.ts @@ -10,19 +10,27 @@ import { DeviceMessagesService } from './services/device.messages.service'; import { DeviceRepositoryModule } from '../modules/device/device.repository.module'; import { DeviceNotificationRepository } from '../modules/device/repositories'; import { DeviceStatusFirebaseModule } from '../firebase/devices-status/devices-status.module'; +import { CommunityPermissionService } from './services/community.permission.service'; +import { CommunityRepository } from '../modules/community/repositories'; @Global() @Module({ providers: [ HelperHashService, SpacePermissionService, + CommunityPermissionService, SpaceRepository, TuyaWebSocketService, OneSignalService, DeviceMessagesService, DeviceNotificationRepository, + CommunityRepository, + ], + exports: [ + HelperHashService, + SpacePermissionService, + CommunityPermissionService, ], - exports: [HelperHashService, SpacePermissionService], controllers: [], imports: [ SpaceRepositoryModule, diff --git a/libs/common/src/helper/services/community.permission.service.ts b/libs/common/src/helper/services/community.permission.service.ts new file mode 100644 index 0000000..8486cb7 --- /dev/null +++ b/libs/common/src/helper/services/community.permission.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; +import { BadRequestException } from '@nestjs/common'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; + +@Injectable() +export class CommunityPermissionService { + constructor(private readonly communityRepository: CommunityRepository) {} + + async checkUserPermission( + communityUuid: string, + userUuid: string, + ): Promise { + try { + const communityData = await this.communityRepository.findOne({ + where: { + uuid: communityUuid, + users: { + uuid: userUuid, + }, + }, + relations: ['users'], + }); + + if (!communityData) { + throw new BadRequestException( + 'You do not have permission to access this community', + ); + } + } catch (err) { + throw new BadRequestException(err.message || 'Invalid UUID'); + } + } +} diff --git a/src/community/community.module.ts b/src/community/community.module.ts index da17bc5..7a866b3 100644 --- a/src/community/community.module.ts +++ b/src/community/community.module.ts @@ -7,7 +7,6 @@ import { SpaceRepository } from '@app/common/modules/space/repositories'; import { SpaceTypeRepository } from '@app/common/modules/space/repositories'; import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; -import { UserRepository } from '@app/common/modules/user/repositories'; import { SpacePermissionService } from '@app/common/helper/services'; import { CommunitySpaceService } from './services'; import { CommunitySpaceController } from './controllers'; @@ -25,7 +24,6 @@ import { RegionRepository } from '@app/common/modules/region/repositories'; UserSpaceRepository, RegionRepository, CommunityRepository, - UserRepository, SpacePermissionService, ], exports: [CommunityService, SpacePermissionService], diff --git a/src/community/controllers/community-spaces.controller.ts b/src/community/controllers/community-spaces.controller.ts index 5c14f86..333644f 100644 --- a/src/community/controllers/community-spaces.controller.ts +++ b/src/community/controllers/community-spaces.controller.ts @@ -6,31 +6,32 @@ import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { ControllerRoute } from '@app/common/constants/controller-route'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { CommunitySpaceService } from '../services'; +import { CommunityPermissionGuard } from 'src/guards/community.permission.guard'; @ApiTags('Community Module') @Controller({ version: '1', - path: ControllerRoute.COMMUNITYSPACE.ROUTE, + path: ControllerRoute.COMMUNITY_SPACE.ROUTE, }) export class CommunitySpaceController { constructor(private readonly communitySpaceService: CommunitySpaceService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, CommunityPermissionGuard) @ApiOperation({ summary: - ControllerRoute.COMMUNITYSPACE.ACTIONS + ControllerRoute.COMMUNITY_SPACE.ACTIONS .GET_COMMUNITY_SPACES_HIERARCHY_SUMMARY, description: - ControllerRoute.COMMUNITYSPACE.ACTIONS + ControllerRoute.COMMUNITY_SPACE.ACTIONS .GET_COMMUNITY_SPACES_HIERARCHY_DESCRIPTION, }) - @Get('/:communityId') + @Get() async getCommunityByUuid( @Param() params: GetCommunityParams, ): Promise { return await this.communitySpaceService.getSpacesHierarchyForCommunity( - params.communityId, + params.communityUuid, ); } } diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index aee04be..925f1c6 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -4,8 +4,6 @@ import { Controller, Delete, Get, - HttpException, - HttpStatus, Param, Post, Put, @@ -13,18 +11,15 @@ import { UseGuards, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; -import { - AddCommunityDto, - AddUserCommunityDto, -} from '../dtos/add.community.dto'; +import { AddCommunityDto } from '../dtos/add.community.dto'; import { GetCommunityParams } from '../dtos/get.community.dto'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; // import { CheckUserCommunityGuard } from 'src/guards/user.community.guard'; -import { AdminRoleGuard } from 'src/guards/admin.role.guard'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { ControllerRoute } from '@app/common/constants/controller-route'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; +import { CommunityPermissionGuard } from 'src/guards/community.permission.guard'; @ApiTags('Community Module') @Controller({ @@ -48,17 +43,17 @@ export class CommunityController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, CommunityPermissionGuard) @ApiOperation({ summary: ControllerRoute.COMMUNITY.ACTIONS.GET_COMMUNITY_BY_ID_SUMMARY, description: ControllerRoute.COMMUNITY.ACTIONS.GET_COMMUNITY_BY_ID_DESCRIPTION, }) - @Get('/:communityId') + @Get('/:communityUuid') async getCommunityByUuid( @Param() params: GetCommunityParams, ): Promise { - return await this.communityService.getCommunityById(params.communityId); + return await this.communityService.getCommunityById(params.communityUuid); } @ApiBearerAuth() @@ -75,25 +70,25 @@ export class CommunityController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(JwtAuthGuard, CommunityPermissionGuard) @ApiOperation({ summary: ControllerRoute.COMMUNITY.ACTIONS.UPDATE_COMMUNITY_SUMMARY, description: ControllerRoute.COMMUNITY.ACTIONS.UPDATE_COMMUNITY_DESCRIPTION, }) - @Put('/:communityId') + @Put('/:communityUuid') async updateCommunity( @Param() param: GetCommunityParams, @Body() updateCommunityDto: UpdateCommunityNameDto, ) { return this.communityService.updateCommunity( - param.communityId, + param.communityUuid, updateCommunityDto, ); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Delete('/:communityId') + @Delete('/:communityUuid') @ApiOperation({ summary: ControllerRoute.COMMUNITY.ACTIONS.DELETE_COMMUNITY_SUMMARY, description: ControllerRoute.COMMUNITY.ACTIONS.DELETE_COMMUNITY_DESCRIPTION, @@ -101,39 +96,6 @@ export class CommunityController { async deleteCommunity( @Param() param: GetCommunityParams, ): Promise { - return this.communityService.deleteCommunity(param.communityId); - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get('user/:userUuid') - async getCommunitiesByUserId(@Param('userUuid') userUuid: string) { - try { - return await this.communityService.getCommunitiesByUserId(userUuid); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - @ApiBearerAuth() - @UseGuards(AdminRoleGuard) - @Post('user') - async addUserCommunity(@Body() addUserCommunityDto: AddUserCommunityDto) { - try { - await this.communityService.addUserCommunity(addUserCommunityDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'user community added successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return this.communityService.deleteCommunity(param.communityUuid); } } diff --git a/src/community/dtos/get.community.dto.ts b/src/community/dtos/get.community.dto.ts index 2b84e19..fe2cf46 100644 --- a/src/community/dtos/get.community.dto.ts +++ b/src/community/dtos/get.community.dto.ts @@ -25,12 +25,12 @@ export class GetCommunityParams { @ApiProperty({ description: 'Community id of the specific community', required: true, - name: 'communityId', + name: 'communityUuid', }) @IsUUID() @IsString() @IsNotEmpty() - public communityId: string; + public communityUuid: string; } export class GetCommunityChildDto { diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index a1bcff4..6eb4055 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -1,7 +1,5 @@ import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { AddCommunityDto, AddUserCommunityDto } from '../dtos'; -import { GetCommunityByUserUuidInterface } from '../interface/community.interface'; +import { AddCommunityDto } from '../dtos'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { RegionRepository } from '@app/common/modules/region/repositories'; @@ -18,7 +16,6 @@ import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; @Injectable() export class CommunityService { constructor( - private readonly spaceRepository: SpaceRepository, private readonly userSpaceRepository: UserSpaceRepository, private readonly communityRepository: CommunityRepository, private readonly regionRepository: RegionRepository, @@ -169,57 +166,4 @@ export class CommunityService { } } } - - async getCommunitiesByUserId( - userUuid: string, - ): Promise { - try { - const communities = await this.userSpaceRepository.find({ - relations: ['space', 'space.spaceType'], - where: { - user: { uuid: userUuid }, - }, - }); - - if (communities.length === 0) { - throw new HttpException( - 'this user has no communities', - HttpStatus.NOT_FOUND, - ); - } - const spaces = communities.map((community) => ({ - uuid: community.space.uuid, - name: community.space.spaceName, - type: community.space.spaceType.type, - })); - - return spaces; - } catch (err) { - if (err instanceof HttpException) { - throw err; - } else { - throw new HttpException('user not found', HttpStatus.NOT_FOUND); - } - } - } - - async addUserCommunity(addUserCommunityDto: AddUserCommunityDto) { - try { - await this.userSpaceRepository.save({ - user: { uuid: addUserCommunityDto.userUuid }, - space: { uuid: addUserCommunityDto.communityUuid }, - }); - } catch (err) { - if (err.code === '23505') { - throw new HttpException( - 'User already belongs to this community', - HttpStatus.BAD_REQUEST, - ); - } - throw new HttpException( - err.message || 'Internal Server Error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } } diff --git a/src/guards/community.permission.guard.ts b/src/guards/community.permission.guard.ts index a078b70..2db06df 100644 --- a/src/guards/community.permission.guard.ts +++ b/src/guards/community.permission.guard.ts @@ -1,5 +1,5 @@ -import { SpaceType } from '@app/common/constants/space-type.enum'; -import { SpacePermissionService } from '@app/common/helper/services/space.permission.service'; +import { RoleType } from '@app/common/constants/role.type.enum'; +import { CommunityPermissionService } from '@app/common/helper/services/community.permission.service'; import { BadRequestException, CanActivate, @@ -9,7 +9,7 @@ import { @Injectable() export class CommunityPermissionGuard implements CanActivate { - constructor(private readonly permissionService: SpacePermissionService) {} + constructor(private readonly permissionService: CommunityPermissionService) {} async canActivate(context: ExecutionContext): Promise { const req = context.switchToHttp().getRequest(); @@ -18,6 +18,17 @@ export class CommunityPermissionGuard implements CanActivate { const { communityUuid } = req.params; const { user } = req; + if ( + user && + user.roles && + user.roles.some( + (role) => + role.type === RoleType.ADMIN || role.type === RoleType.SUPER_ADMIN, + ) + ) { + return true; + } + if (!communityUuid) { throw new BadRequestException('communityUuid is required'); } @@ -25,7 +36,6 @@ export class CommunityPermissionGuard implements CanActivate { await this.permissionService.checkUserPermission( communityUuid, user.uuid, - SpaceType.COMMUNITY, ); return true; diff --git a/src/users/controllers/index.ts b/src/users/controllers/index.ts index edd3705..6c91f32 100644 --- a/src/users/controllers/index.ts +++ b/src/users/controllers/index.ts @@ -1 +1,2 @@ export * from './user.controller'; +export * from './user-communities.controller'; diff --git a/src/users/controllers/user-communities.controller.ts b/src/users/controllers/user-communities.controller.ts new file mode 100644 index 0000000..1ebb6cf --- /dev/null +++ b/src/users/controllers/user-communities.controller.ts @@ -0,0 +1,80 @@ +import { + Controller, + Delete, + Get, + Param, + Post, + UseGuards, +} from '@nestjs/common'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; +import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { UserCommunityService } from '../services'; +import { UserCommunityParamDto, UserParamDto } from '../dtos'; + +@ApiTags('User Module') +@Controller({ + version: EnableDisableStatusEnum.ENABLED, + path: ControllerRoute.USER_COMMUNITY.ROUTE, +}) +export class UserCommunityController { + constructor(private readonly userCommunityService: UserCommunityService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get() + @ApiOperation({ + summary: + ControllerRoute.USER_COMMUNITY.ACTIONS.GET_USER_COMMUNITIES_SUMMARY, + description: + ControllerRoute.USER_COMMUNITY.ACTIONS.GET_USER_COMMUNITIES_DESCRIPTION, + }) + async getCommunitiesByUserId( + @Param() params: UserParamDto, + ): Promise { + return await this.userCommunityService.getCommunitiesByUserId( + params.userUuid, + ); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post(':communityUuid') + @ApiOperation({ + summary: + ControllerRoute.USER_COMMUNITY.ACTIONS.ASSOCIATE_USER_COMMUNITY_SUMMARY, + description: + ControllerRoute.USER_COMMUNITY.ACTIONS + .ASSOCIATE_USER_COMMUNITY_DESCRIPTION, + }) + async associateUserWithCommunity( + @Param() params: UserCommunityParamDto, + ): Promise { + return await this.userCommunityService.associateUserWithCommunity( + params.userUuid, + params.communityUuid, + ); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: + ControllerRoute.USER_COMMUNITY.ACTIONS + .DISASSOCIATE_USER_COMMUNITY_SUMMARY, + description: + ControllerRoute.USER_COMMUNITY.ACTIONS + .DISASSOCIATE_USER_COMMUNITY_DESCRIPTION, + }) + @Delete(':communityUuid') + async disassociateUserFromCommunity( + @Param() params: UserCommunityParamDto, + ): Promise { + return await this.userCommunityService.disassociateUserFromCommunity( + params.userUuid, + params.communityUuid, + ); + } +} diff --git a/src/users/dtos/index.ts b/src/users/dtos/index.ts index 37a7007..5ca1ea7 100644 --- a/src/users/dtos/index.ts +++ b/src/users/dtos/index.ts @@ -1 +1,3 @@ export * from './update.user.dto'; +export * from './user-community-param.dto'; +export * from './user-param.dto'; diff --git a/src/users/dtos/user-community-param.dto.ts b/src/users/dtos/user-community-param.dto.ts new file mode 100644 index 0000000..806d64f --- /dev/null +++ b/src/users/dtos/user-community-param.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; +import { UserParamDto } from './user-param.dto'; + +export class UserCommunityParamDto extends UserParamDto { + @ApiProperty({ + description: 'UUID of the community', + example: 'a9b2c7f5-4d6e-423d-a24d-6f9c758b1b92', + }) + @IsUUID() + communityUuid: string; +} diff --git a/src/users/dtos/user-param.dto.ts b/src/users/dtos/user-param.dto.ts new file mode 100644 index 0000000..323dd93 --- /dev/null +++ b/src/users/dtos/user-param.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; + +export class UserParamDto { + @ApiProperty({ + description: 'UUID of the user', + example: 'e7e8ddf5-3f7d-4e3d-bf3e-8c745b9b7d2c', + }) + @IsUUID() + userUuid: string; +} diff --git a/src/users/services/index.ts b/src/users/services/index.ts index e17ee5c..56c0003 100644 --- a/src/users/services/index.ts +++ b/src/users/services/index.ts @@ -1 +1,2 @@ export * from './user.service'; +export * from './user-community.service'; diff --git a/src/users/services/user-community.service.ts b/src/users/services/user-community.service.ts new file mode 100644 index 0000000..935c675 --- /dev/null +++ b/src/users/services/user-community.service.ts @@ -0,0 +1,114 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { UserRepository } from '@app/common/modules/user/repositories'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; + +@Injectable() +export class UserCommunityService { + constructor( + private readonly userRepository: UserRepository, + private readonly communityRepository: CommunityRepository, + ) {} + async associateUserWithCommunity( + userUuid: string, + communityUuid: string, + ): Promise { + const { user, community } = await this.getUserAndCommunity( + userUuid, + communityUuid, + ); + // Check if the user is already associated + if (user.communities.some((c) => c.uuid === communityUuid)) { + throw new HttpException( + 'User is already associated with the community', + HttpStatus.BAD_REQUEST, + ); + } + + user.communities.push(community); + await this.userRepository.save(user); + + return new SuccessResponseDto({ + message: `User ${userUuid} successfully associated with community ${communityUuid}`, + }); + } + + async disassociateUserFromCommunity( + userUuid: string, + communityUuid: string, + ): Promise { + const { user } = await this.getUserAndCommunity(userUuid, communityUuid); + + if (!user.communities) { + throw new HttpException( + `User ${userUuid} is not associated with any community`, + HttpStatus.BAD_REQUEST, + ); + } + + const isAssociated = user.communities.some((c) => c.uuid === communityUuid); + + // Check if there is no association + if (!isAssociated) { + throw new HttpException( + `User ${userUuid} is not associated with community ${communityUuid}`, + HttpStatus.BAD_REQUEST, + ); + } + + // Remove the community association + user.communities = user.communities.filter((c) => c.uuid !== communityUuid); + await this.userRepository.save(user); + + return new SuccessResponseDto({ + message: `User ${userUuid} successfully disassociated from community ${communityUuid}`, + }); + } + + async getCommunitiesByUserId(userUuid: string): Promise { + const user = await this.userRepository.findOne({ + where: { uuid: userUuid }, + relations: ['communities'], + }); + + if (!user) { + throw new HttpException( + `User with ID ${userUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + if (!user.communities || user.communities.length === 0) { + throw new HttpException( + `User ${userUuid} is not associated with any communities`, + HttpStatus.BAD_REQUEST, + ); + } + + return new SuccessResponseDto({ + data: user.communities, + message: `Communities for user ${userUuid} retrieved successfully`, + }); + } + + private async getUserAndCommunity(userUuid: string, communityUuid: string) { + const user = await this.userRepository.findOne({ + where: { uuid: userUuid }, + relations: ['communities'], + }); + + const community = await this.communityRepository.findOne({ + where: { uuid: communityUuid }, + }); + + if (!user || !community) { + throw new HttpException( + 'User or Community not found', + HttpStatus.NOT_FOUND, + ); + } + + return { user, community }; + } +} diff --git a/src/users/user.module.ts b/src/users/user.module.ts index 6cd6d80..1cba860 100644 --- a/src/users/user.module.ts +++ b/src/users/user.module.ts @@ -2,18 +2,28 @@ import { Module } from '@nestjs/common'; import { UserService } from './services/user.service'; import { UserController } from './controllers/user.controller'; import { ConfigModule } from '@nestjs/config'; -import { UserRepository } from '@app/common/modules/user/repositories'; +import { + UserRepository, + UserSpaceRepository, +} from '@app/common/modules/user/repositories'; import { RegionRepository } from '@app/common/modules/region/repositories'; import { TimeZoneRepository } from '@app/common/modules/timezone/repositories'; +import { UserCommunityController } from './controllers'; +import { CommunityModule } from 'src/community/community.module'; +import { UserCommunityService } from './services'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; @Module({ - imports: [ConfigModule], - controllers: [UserController], + imports: [ConfigModule, CommunityModule], + controllers: [UserController, UserCommunityController], providers: [ UserService, UserRepository, RegionRepository, TimeZoneRepository, + UserSpaceRepository, + CommunityRepository, + UserCommunityService, ], exports: [UserService], }) From 96d52962aaa0be98fa893c02aa8220296a8d3288 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 17 Oct 2024 11:20:05 +0400 Subject: [PATCH 005/109] Space CRUD apis --- libs/common/src/constants/controller-route.ts | 29 ++ src/app.module.ts | 2 + src/space/controllers/index.ts | 1 + src/space/controllers/space.controller.ts | 99 +++++++ src/space/dtos/add.space.dto.ts | 35 +++ src/space/dtos/community-space.param.ts | 11 + src/space/dtos/get.space.param.ts | 12 + src/space/dtos/index.ts | 3 + src/space/services/index.ts | 1 + src/space/services/space.service.ts | 271 ++++++++++++++++++ src/space/space.module.ts | 15 + 11 files changed, 479 insertions(+) create mode 100644 src/space/controllers/index.ts create mode 100644 src/space/controllers/space.controller.ts create mode 100644 src/space/dtos/add.space.dto.ts create mode 100644 src/space/dtos/community-space.param.ts create mode 100644 src/space/dtos/get.space.param.ts create mode 100644 src/space/dtos/index.ts create mode 100644 src/space/services/index.ts create mode 100644 src/space/services/space.service.ts create mode 100644 src/space/space.module.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 5c71b4d..1b20b33 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -69,4 +69,33 @@ export class ControllerRoute { 'This endpoint disassociates a user from a community. It removes the relationship between the specified user and the community. If the user is not associated with the community, an error will be returned.'; }; }; + + static SPACE = class { + public static readonly ROUTE = '/communities/:communityId/spaces'; + static ACTIONS = class { + public static readonly CREATE_SPACE_SUMMARY = 'Create a new space'; + public static readonly CREATE_SPACE_DESCRIPTION = + 'This endpoint allows you to create a space in a specified community. Optionally, you can specify a parent space to nest the new space under it.'; + + public static readonly LIST_SPACE_SUMMARY = 'List spaces in community'; + public static readonly LIST_SPACE_DESCRIPTION = + 'List spaces in specified community by community id'; + + public static readonly GET_SPACE_SUMMARY = 'Get a space in community'; + public static readonly GET_SPACE_DESCRIPTION = + 'Get Space in specified community by community id'; + + public static readonly DELETE_SPACE_SUMMARY = 'Delete a space'; + public static readonly DELETE_SPACE_DESCRIPTION = + 'Deletes a space by its UUID and community ID. If the space has children, they will also be deleted due to cascade delete.'; + + public static readonly UPDATE_SPACE_SUMMARY = 'Update a space'; + public static readonly UPDATE_SPACE_DESCRIPTION = + 'Updates a space by its UUID and community ID. You can update the name, parent space, and other properties. If a parent space is provided and not already a parent, its `isParent` flag will be set to true.'; + + public static readonly GET_HEIRARCHY_SUMMARY = 'Get spaces hierarchy'; + public static readonly GET_HEIRARCHY_DESCRIPTION = + 'Fetches all spaces within a community and returns them in a hierarchical structure with parent-child relationships.'; + }; + }; } diff --git a/src/app.module.ts b/src/app.module.ts index 5c3a52c..568cff8 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -24,6 +24,7 @@ import { RegionModule } from './region/region.module'; import { TimeZoneModule } from './timezone/timezone.module'; import { VisitorPasswordModule } from './vistor-password/visitor-password.module'; import { ScheduleModule } from './schedule/schedule.module'; +import { SpaceModule } from './space/space.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -36,6 +37,7 @@ import { ScheduleModule } from './schedule/schedule.module'; BuildingModule, FloorModule, UnitModule, + SpaceModule, RoomModule, RoomModule, GroupModule, diff --git a/src/space/controllers/index.ts b/src/space/controllers/index.ts new file mode 100644 index 0000000..713b482 --- /dev/null +++ b/src/space/controllers/index.ts @@ -0,0 +1 @@ +export * from './space.controller'; \ No newline at end of file diff --git a/src/space/controllers/space.controller.ts b/src/space/controllers/space.controller.ts new file mode 100644 index 0000000..be75c49 --- /dev/null +++ b/src/space/controllers/space.controller.ts @@ -0,0 +1,99 @@ +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { SpaceService } from '../services'; +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { + Body, + Controller, + Delete, + Get, + Param, + Post, + Put, + Query, + UseGuards, +} from '@nestjs/common'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { AddSpaceDto, CommunitySpaceParam } from '../dtos'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; +import { GetSpaceParam } from '../dtos/get.space.param'; + +@ApiTags('Space Module') +@Controller({ + version: '1', + path: ControllerRoute.SPACE.ROUTE, +}) +export class SpaceController { + constructor(private readonly spaceService: SpaceService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SPACE.ACTIONS.CREATE_SPACE_SUMMARY, + description: ControllerRoute.SPACE.ACTIONS.CREATE_SPACE_DESCRIPTION, + }) + @Post() + async createSpace( + @Body() addSpaceDto: AddSpaceDto, + @Param() communitySpaceParam: CommunitySpaceParam, + ): Promise { + return await this.spaceService.createSpace( + addSpaceDto, + communitySpaceParam.communityUuid, + ); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SPACE.ACTIONS.LIST_SPACE_SUMMARY, + description: ControllerRoute.SPACE.ACTIONS.LIST_SPACE_DESCRIPTION, + }) + @Get() + async list( + @Param() communitySpaceParam: CommunitySpaceParam, + @Query() query: PaginationRequestGetListDto, + ): Promise { + return this.spaceService.list(communitySpaceParam.communityUuid, query); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SPACE.ACTIONS.DELETE_SPACE_SUMMARY, + description: ControllerRoute.SPACE.ACTIONS.DELETE_SPACE_DESCRIPTION, + }) + @Delete('/:spaceUuid') + async deleteSpace(@Param() params: GetSpaceParam): Promise { + return this.spaceService.delete(params.spaceUuid, params.communityUuid); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Put('/:spaceUuid') + @ApiOperation({ + summary: ControllerRoute.SPACE.ACTIONS.UPDATE_SPACE_SUMMARY, + description: ControllerRoute.SPACE.ACTIONS.UPDATE_SPACE_SUMMARY, + }) + async updateSpace( + @Param() params: GetSpaceParam, + @Body() updateSpaceDto: AddSpaceDto, + ): Promise { + return this.spaceService.updateSpace( + params.spaceUuid, + params.communityUuid, + updateSpaceDto, + ); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SPACE.ACTIONS.GET_SPACE_SUMMARY, + description: ControllerRoute.SPACE.ACTIONS.GET_SPACE_DESCRIPTION, + }) + @Get('/:spaceUuid') + async get(@Param() params: GetSpaceParam): Promise { + return this.spaceService.findOne(params.spaceUuid, params.communityUuid); + } +} diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts new file mode 100644 index 0000000..e0a0dac --- /dev/null +++ b/src/space/dtos/add.space.dto.ts @@ -0,0 +1,35 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IsBoolean, + IsNotEmpty, + IsOptional, + IsString, + IsUUID, +} from 'class-validator'; + +export class AddSpaceDto { + @ApiProperty({ + description: 'Name of the space (e.g., Floor 1, Unit 101)', + example: 'Unit 101', + }) + @IsString() + @IsNotEmpty() + name: string; + + @ApiProperty({ + description: 'UUID of the parent space (if any, for hierarchical spaces)', + example: 'f5d7e9c3-44bc-4b12-88f1-1b3cda84752e', + required: false, + }) + @IsUUID() + @IsOptional() + parentUuid?: string; + + @ApiProperty({ + description: 'Indicates whether the space is private or public', + example: false, + default: false, + }) + @IsBoolean() + isPrivate: boolean; +} diff --git a/src/space/dtos/community-space.param.ts b/src/space/dtos/community-space.param.ts new file mode 100644 index 0000000..ab35e4e --- /dev/null +++ b/src/space/dtos/community-space.param.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; + +export class CommunitySpaceParam { + @ApiProperty({ + description: 'UUID of the community this space belongs to', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + communityUuid: string; +} diff --git a/src/space/dtos/get.space.param.ts b/src/space/dtos/get.space.param.ts new file mode 100644 index 0000000..b347acd --- /dev/null +++ b/src/space/dtos/get.space.param.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; +import { CommunitySpaceParam } from './community-space.param'; + +export class GetSpaceParam extends CommunitySpaceParam { + @ApiProperty({ + description: 'UUID of the Space', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + spaceUuid: string; +} diff --git a/src/space/dtos/index.ts b/src/space/dtos/index.ts new file mode 100644 index 0000000..8e67e9f --- /dev/null +++ b/src/space/dtos/index.ts @@ -0,0 +1,3 @@ +export * from './add.space.dto'; +export * from './community-space.param'; +export * from './get.space.param'; diff --git a/src/space/services/index.ts b/src/space/services/index.ts new file mode 100644 index 0000000..e896b3f --- /dev/null +++ b/src/space/services/index.ts @@ -0,0 +1 @@ +export * from './space.service'; diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts new file mode 100644 index 0000000..d28686d --- /dev/null +++ b/src/space/services/space.service.ts @@ -0,0 +1,271 @@ +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { AddSpaceDto } from '../dtos'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { SpaceEntity } from '@app/common/modules/space/entities'; +import { + TypeORMCustomModel, + TypeORMCustomModelFindAllQuery, +} from '@app/common/models/typeOrmCustom.model'; +import { SpaceDto } from '@app/common/modules/space/dtos'; +import { PageResponse } from '@app/common/dto/pagination.response.dto'; + +@Injectable() +export class SpaceService { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly communityRepository: CommunityRepository, + ) {} + + async createSpace( + addSpaceDto: AddSpaceDto, + communityId: string, + ): Promise { + let parent: SpaceEntity | null = null; + + const { parentUuid } = addSpaceDto; + const community = await this.communityRepository.findOne({ + where: { uuid: communityId }, + }); + + // If the community doesn't exist, throw a 404 error + if (!community) { + throw new HttpException( + `Community with ID ${communityId} not found`, + HttpStatus.NOT_FOUND, + ); + } + + if (parentUuid) { + parent = await this.spaceRepository.findOne({ + where: { uuid: parentUuid }, + }); + + // If the community doesn't exist, throw a 404 error + if (!parent) { + throw new HttpException( + `Parent with ID ${parentUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + } + try { + const newSpace = this.spaceRepository.create({ + ...addSpaceDto, + community, + parent: parentUuid ? parent : null, + }); + + await this.spaceRepository.save(newSpace); + + return new SuccessResponseDto({ + statusCode: HttpStatus.CREATED, + data: newSpace, + message: 'Space created successfully', + }); + } catch (error) { + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + async list( + communityId: string, + pageable: Partial, + ): Promise { + const community = await this.communityRepository.findOne({ + where: { uuid: communityId }, + }); + + // If the community doesn't exist, throw a 404 error + if (!community) { + throw new HttpException( + `Community with ID ${communityId} not found`, + HttpStatus.NOT_FOUND, + ); + } + try { + pageable.modelName = 'space'; + pageable.where = { + community: { uuid: communityId }, // Ensure the relation is used in the where clause + }; + console.log('Where clause before building:', pageable.where); + + const customModel = TypeORMCustomModel(this.spaceRepository); + + const { baseResponseDto, paginationResponseDto } = + await customModel.findAll(pageable); + return new PageResponse(baseResponseDto, paginationResponseDto); + } catch (error) { + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + async findOne( + spaceId: string, + communityId: string, + ): Promise { + try { + const community = await this.communityRepository.findOne({ + where: { uuid: communityId }, + }); + + // If the community doesn't exist, throw a 404 error + if (!community) { + throw new HttpException( + `Community with ID ${communityId} not found`, + HttpStatus.NOT_FOUND, + ); + } + + const space = await this.spaceRepository.findOne({ + where: { + uuid: spaceId, + community: { + uuid: communityId, + }, + }, + }); + + // If space is not found, throw a NotFoundException + if (!space) { + throw new HttpException( + `Space with UUID ${spaceId} not found`, + HttpStatus.NOT_FOUND, + ); + } + return new SuccessResponseDto({ + message: `Space with ID ${spaceId} successfully fetched`, + data: space, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; // If it's an HttpException, rethrow it + } else { + throw new HttpException( + 'An error occurred while deleting the community', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + + async delete(spaceId: string, communityId: string): Promise { + try { + // First, check if the community exists + const community = await this.communityRepository.findOne({ + where: { uuid: communityId }, + }); + + if (!community) { + throw new HttpException( + `Community with ID ${communityId} not found`, + HttpStatus.NOT_FOUND, + ); + } + + // Check if the space exists + const space = await this.spaceRepository.findOne({ + where: { uuid: spaceId, community: { uuid: communityId } }, + }); + + if (!space) { + throw new HttpException( + `Space with ID ${spaceId} not found`, + HttpStatus.NOT_FOUND, + ); + } + + // Delete the space + await this.spaceRepository.remove(space); + + return new SuccessResponseDto({ + message: `Space with ID ${spaceId} successfully deleted`, + statusCode: HttpStatus.OK, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( + 'An error occurred while deleting the space', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async updateSpace( + spaceId: string, + communityId: string, + updateSpaceDto: AddSpaceDto, + ): Promise { + try { + // First, check if the community exists + const community = await this.communityRepository.findOne({ + where: { uuid: communityId }, + }); + + if (!community) { + throw new HttpException( + `Community with ID ${communityId} not found`, + HttpStatus.NOT_FOUND, + ); + } + + // Check if the space exists + const space = await this.spaceRepository.findOne({ + where: { uuid: spaceId, community: { uuid: communityId } }, + }); + + if (!space) { + throw new HttpException( + `Space with ID ${spaceId} not found`, + HttpStatus.NOT_FOUND, + ); + } + + // If a parentId is provided, check if the parent exists + const { parentUuid } = updateSpaceDto; + let parent: SpaceEntity | null = null; + if (parentUuid) { + parent = await this.spaceRepository.findOne({ + where: { uuid: parentUuid, community: { uuid: communityId } }, + }); + + // If the parent doesn't exist, throw a 404 error + if (!parent) { + throw new HttpException( + `Parent space with ID ${parentUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + // Set the parent of the current space + space.parent = parent; + } else { + space.parent = null; // If no parent is provided, clear the parent + } + + // Update other space properties from updateSpaceDto + Object.assign(space, updateSpaceDto); + + // Save the updated space + await this.spaceRepository.save(space); + + return new SuccessResponseDto({ + message: `Space with ID ${spaceId} successfully updated`, + data: space, + statusCode: HttpStatus.OK, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( + 'An error occurred while updating the space', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/space/space.module.ts b/src/space/space.module.ts new file mode 100644 index 0000000..69951de --- /dev/null +++ b/src/space/space.module.ts @@ -0,0 +1,15 @@ +import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { SpaceController } from './controllers'; +import { SpaceService } from './services'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; + +@Module({ + imports: [ConfigModule, SpaceRepositoryModule], + controllers: [SpaceController], + providers: [SpaceService, SpaceRepository, CommunityRepository], + exports: [SpaceService], +}) +export class SpaceModule {} From f8c011f9dc5894b91f1960eb29207a27f50dec5f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 21 Oct 2024 09:47:52 +0400 Subject: [PATCH 006/109] space management without roles --- libs/common/src/constants/controller-route.ts | 35 ++++- .../services/community-space.service.ts | 2 - src/space/controllers/index.ts | 3 +- .../controllers/space-user.controller.ts | 49 +++++++ src/space/controllers/space.controller.ts | 29 +++- src/space/dtos/index.ts | 1 + src/space/dtos/user-space.param.ts | 12 ++ src/space/services/index.ts | 1 + src/space/services/space-user.service.ts | 108 +++++++++++++++ src/space/services/space.service.ts | 128 +++++++++++++++--- src/space/space.module.ts | 18 ++- src/users/controllers/index.ts | 1 + .../controllers/user-space.controller.ts | 30 ++++ src/users/services/index.ts | 1 + src/users/services/user-space.service.ts | 30 ++++ 15 files changed, 417 insertions(+), 31 deletions(-) create mode 100644 src/space/controllers/space-user.controller.ts create mode 100644 src/space/dtos/user-space.param.ts create mode 100644 src/space/services/space-user.service.ts create mode 100644 src/users/controllers/user-space.controller.ts create mode 100644 src/users/services/user-space.service.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 1b20b33..f59222b 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -70,6 +70,16 @@ export class ControllerRoute { }; }; + static USER_SPACE = class { + public static readonly ROUTE = '/user/:userUuid/spaces'; + static ACTIONS = class { + public static readonly GET_USER_SPACES_SUMMARY = + 'Retrieve list of spaces a user belongs to'; + public static readonly GET_USER_SPACES_DESCRIPTION = + 'This endpoint retrieves all the spaces that a user is associated with, based on the user ID. It fetches the user spaces by querying the UserSpaceEntity to find the spaces where the user has an association.'; + }; + }; + static SPACE = class { public static readonly ROUTE = '/communities/:communityId/spaces'; static ACTIONS = class { @@ -93,9 +103,30 @@ export class ControllerRoute { public static readonly UPDATE_SPACE_DESCRIPTION = 'Updates a space by its UUID and community ID. You can update the name, parent space, and other properties. If a parent space is provided and not already a parent, its `isParent` flag will be set to true.'; - public static readonly GET_HEIRARCHY_SUMMARY = 'Get spaces hierarchy'; + public static readonly GET_HEIRARCHY_SUMMARY = 'Get space hierarchy'; public static readonly GET_HEIRARCHY_DESCRIPTION = - 'Fetches all spaces within a community and returns them in a hierarchical structure with parent-child relationships.'; + 'This endpoint retrieves the hierarchical structure of spaces under a given space ID. It returns all the child spaces nested within the specified space, organized by their parent-child relationships. '; + + public static readonly CREATE_INVITATION_CODE_SPACE_SUMMARY = + 'Generate a new invitation code for a specific space'; + public static readonly CREATE_INVITATION_CODE_SPACE_DESCRIPTION = + 'This endpoint generates a new 6-character invitation code for a space identified by its UUID and stores it in the space entity'; + }; + }; + + static SPACE_USER = class { + public static readonly ROUTE = + '/communities/:communityId/spaces/:spaceId/user'; + static ACTIONS = class { + public static readonly ASSOCIATE_SPACE_USER_SUMMARY = + 'Associate a user to a space'; + public static readonly ASSOCIATE_SPACE_USER_DESCRIPTION = + 'Associates a user with a given space by their respective UUIDs'; + + public static readonly DISSOCIATE_SPACE_USER_SUMMARY = + 'Disassociate a user from a space'; + public static readonly DISSOCIATE_SPACE_USER_DESCRIPTION = + 'Disassociates a user from a space by removing the existing association.'; }; }; } diff --git a/src/community/services/community-space.service.ts b/src/community/services/community-space.service.ts index 0b80836..54fb4f3 100644 --- a/src/community/services/community-space.service.ts +++ b/src/community/services/community-space.service.ts @@ -67,11 +67,9 @@ export class CommunitySpaceService { const rootSpaces: SpaceEntity[] = []; spaces.forEach((space) => { if (space.parent && space.parent.uuid) { - // If the space has a parent, add it to the parent's children array const parent = map.get(space.parent.uuid); parent?.children?.push(map.get(space.uuid)); } else { - // If the space doesn't have a parent, it's a root space rootSpaces.push(map.get(space.uuid)); } }); diff --git a/src/space/controllers/index.ts b/src/space/controllers/index.ts index 713b482..fed3986 100644 --- a/src/space/controllers/index.ts +++ b/src/space/controllers/index.ts @@ -1 +1,2 @@ -export * from './space.controller'; \ No newline at end of file +export * from './space.controller'; +export * from './space-user.controller'; diff --git a/src/space/controllers/space-user.controller.ts b/src/space/controllers/space-user.controller.ts new file mode 100644 index 0000000..441c3c6 --- /dev/null +++ b/src/space/controllers/space-user.controller.ts @@ -0,0 +1,49 @@ +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { Controller, Delete, Param, Post, UseGuards } from '@nestjs/common'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { SpaceUserService } from '../services'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { UserSpaceDto } from '@app/common/modules/user/dtos'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; + +@ApiTags('Space Module') +@Controller({ + version: '1', + path: ControllerRoute.SPACE_USER.ROUTE, +}) +export class SpaceUserController { + constructor(private readonly spaceUserService: SpaceUserService) {} + + @Post('/:userUuid') + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: + ControllerRoute.SPACE_USER.ACTIONS.ASSOCIATE_SPACE_USER_DESCRIPTION, + description: + ControllerRoute.SPACE_USER.ACTIONS.ASSOCIATE_SPACE_USER_DESCRIPTION, + }) + async associateUserToSpace( + @Param() params: UserSpaceDto, + ): Promise { + return this.spaceUserService.associateUserToSpace( + params.userUuid, + params.spaceUuid, + ); + } + + @Delete('/:userUuid') + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SPACE_USER.ACTIONS.DISSOCIATE_SPACE_USER_SUMMARY, + description: + ControllerRoute.SPACE_USER.ACTIONS.DISSOCIATE_SPACE_USER_DESCRIPTION, + }) + async disassociateUserFromSpace( + @Param() params: UserSpaceDto, + ): Promise { + return this.spaceUserService.disassociateUserFromSpace( + params.userUuid, + params.spaceUuid, + ); + } +} diff --git a/src/space/controllers/space.controller.ts b/src/space/controllers/space.controller.ts index be75c49..2d70156 100644 --- a/src/space/controllers/space.controller.ts +++ b/src/space/controllers/space.controller.ts @@ -94,6 +94,33 @@ export class SpaceController { }) @Get('/:spaceUuid') async get(@Param() params: GetSpaceParam): Promise { - return this.spaceService.findOne(params.spaceUuid, params.communityUuid); + return this.spaceService.findOne(params.spaceUuid); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SPACE.ACTIONS.GET_HEIRARCHY_SUMMARY, + description: ControllerRoute.SPACE.ACTIONS.GET_HEIRARCHY_DESCRIPTION, + }) + @Get('/:spaceUuid/hierarchy') + async getHierarchyUnderSpace( + @Param() params: GetSpaceParam, + ): Promise { + return this.spaceService.getSpacesHierarchyForSpace(params.spaceUuid); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SPACE.ACTIONS.CREATE_INVITATION_CODE_SPACE_SUMMARY, + description: + ControllerRoute.SPACE.ACTIONS.CREATE_INVITATION_CODE_SPACE_DESCRIPTION, + }) + @Post(':spaceUuid/invitation-code') + async generateSpaceInvitationCode( + @Param() params: GetSpaceParam, + ): Promise { + return this.spaceService.getSpaceInvitationCode(params.spaceUuid); } } diff --git a/src/space/dtos/index.ts b/src/space/dtos/index.ts index 8e67e9f..b622787 100644 --- a/src/space/dtos/index.ts +++ b/src/space/dtos/index.ts @@ -1,3 +1,4 @@ export * from './add.space.dto'; export * from './community-space.param'; export * from './get.space.param'; +export * from './user-space.param'; diff --git a/src/space/dtos/user-space.param.ts b/src/space/dtos/user-space.param.ts new file mode 100644 index 0000000..ba62bab --- /dev/null +++ b/src/space/dtos/user-space.param.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; +import { GetSpaceParam } from './get.space.param'; + +export class UserSpaceParam extends GetSpaceParam { + @ApiProperty({ + description: 'Uuid of the user to be associated/ dissociated', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + userUuid: string; +} diff --git a/src/space/services/index.ts b/src/space/services/index.ts index e896b3f..6baa092 100644 --- a/src/space/services/index.ts +++ b/src/space/services/index.ts @@ -1 +1,2 @@ export * from './space.service'; +export * from './space-user.service'; diff --git a/src/space/services/space-user.service.ts b/src/space/services/space-user.service.ts new file mode 100644 index 0000000..5bfb87b --- /dev/null +++ b/src/space/services/space-user.service.ts @@ -0,0 +1,108 @@ +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { + UserRepository, + UserSpaceRepository, +} from '@app/common/modules/user/repositories'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; + +@Injectable() +export class SpaceUserService { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly userRepository: UserRepository, + private readonly userSpaceRepository: UserSpaceRepository, + ) {} + async associateUserToSpace( + userUuid: string, + spaceUuid: string, + ): Promise { + // Find the user by ID + const user = await this.userRepository.findOne({ + where: { uuid: userUuid }, + }); + if (!user) { + throw new HttpException( + `User with ID ${userUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + // Find the space by ID + const space = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid }, + }); + if (!space) { + throw new HttpException( + `Space with ID ${spaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + // Check if the association already exists + const existingAssociation = await this.userSpaceRepository.findOne({ + where: { user: { uuid: userUuid }, space: { uuid: spaceUuid } }, + }); + if (existingAssociation) { + throw new HttpException( + `User is already associated with the space`, + HttpStatus.CONFLICT, + ); + } + + // Create a new UserSpaceEntity entry to associate the user and the space + const userSpace = this.userSpaceRepository.create({ user, space }); + + return new SuccessResponseDto({ + data: userSpace, + message: `Space ${spaceUuid} has been successfully associated t user ${userUuid}`, + }); + } + + async disassociateUserFromSpace( + userUuid: string, + spaceUuid: string, + ): Promise { + // Find the user by ID + const user = await this.userRepository.findOne({ + where: { uuid: userUuid }, + }); + if (!user) { + throw new HttpException( + `User with ID ${userUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + // Find the space by ID + const space = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid }, + }); + if (!space) { + throw new HttpException( + `Space with ID ${spaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + // Find the existing association + const existingAssociation = await this.userSpaceRepository.findOne({ + where: { user: { uuid: userUuid }, space: { uuid: spaceUuid } }, + }); + + if (!existingAssociation) { + throw new HttpException( + `No association found between user ${userUuid} and space ${spaceUuid}`, + HttpStatus.NOT_FOUND, + ); + } + + // Remove the association + await this.userSpaceRepository.remove(existingAssociation); + + return new SuccessResponseDto({ + message: `User ${userUuid} has been successfully disassociated from space ${spaceUuid}`, + }); + } +} diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index d28686d..4a519e9 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -1,5 +1,10 @@ import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { + BadRequestException, + HttpException, + HttpStatus, + Injectable, +} from '@nestjs/common'; import { AddSpaceDto } from '../dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; @@ -11,6 +16,7 @@ import { } from '@app/common/models/typeOrmCustom.model'; import { SpaceDto } from '@app/common/modules/space/dtos'; import { PageResponse } from '@app/common/dto/pagination.response.dto'; +import { generateRandomString } from '@app/common/helper/randomString'; @Injectable() export class SpaceService { @@ -88,9 +94,8 @@ export class SpaceService { try { pageable.modelName = 'space'; pageable.where = { - community: { uuid: communityId }, // Ensure the relation is used in the where clause + community: { uuid: communityId }, }; - console.log('Where clause before building:', pageable.where); const customModel = TypeORMCustomModel(this.spaceRepository); @@ -102,29 +107,11 @@ export class SpaceService { } } - async findOne( - spaceId: string, - communityId: string, - ): Promise { + async findOne(spaceId: string): Promise { try { - const community = await this.communityRepository.findOne({ - where: { uuid: communityId }, - }); - - // If the community doesn't exist, throw a 404 error - if (!community) { - throw new HttpException( - `Community with ID ${communityId} not found`, - HttpStatus.NOT_FOUND, - ); - } - const space = await this.spaceRepository.findOne({ where: { uuid: spaceId, - community: { - uuid: communityId, - }, }, }); @@ -268,4 +255,101 @@ export class SpaceService { ); } } + + async getSpacesHierarchyForSpace(spaceId: string): Promise { + const space = await this.spaceRepository.findOne({ + where: { uuid: spaceId }, + }); + + // If the space doesn't exist, throw a 404 error + if (!space) { + throw new HttpException( + `Space with ID ${spaceId} not found`, + HttpStatus.NOT_FOUND, + ); + } + + try { + // Get all spaces that are children of the provided space, including the parent-child relations + const spaces = await this.spaceRepository.find({ + where: { parent: { uuid: spaceId } }, + relations: ['parent', 'children'], // Include parent and children relations + }); + + // Organize spaces into a hierarchical structure + const spaceHierarchy = this.buildSpaceHierarchy(spaces); + + return new SuccessResponseDto({ + message: `Spaces under space ${spaceId} successfully fetched in hierarchy`, + data: spaceHierarchy, + statusCode: HttpStatus.OK, + }); + } catch (error) { + throw new HttpException( + 'An error occurred while fetching the spaces under the space', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getSpaceInvitationCode(spaceUuid: string): Promise { + try { + const invitationCode = generateRandomString(6); + + const space = await this.spaceRepository.findOne({ + where: { + uuid: spaceUuid, + }, + }); + + if (!space) { + throw new HttpException( + `Space with ID ${spaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + space.invitationCode = invitationCode; + await this.spaceRepository.save(space); + + return new SuccessResponseDto({ + message: `Invitation code has been successfuly added to the space`, + data: space, + }); + } catch (err) { + if (err instanceof BadRequestException) { + throw err; + } else { + throw new HttpException('Space not found', HttpStatus.NOT_FOUND); + } + } + } + + private buildSpaceHierarchy(spaces: SpaceEntity[]): SpaceEntity[] { + const map = new Map(); + + // Step 1: Create a map of spaces by UUID + spaces.forEach((space) => { + map.set( + space.uuid, + this.spaceRepository.create({ + ...space, + children: [], // Add children if needed + }), + ); + }); + + // Step 2: Organize the hierarchy + const rootSpaces: SpaceEntity[] = []; + spaces.forEach((space) => { + if (space.parent && space.parent.uuid) { + const parent = map.get(space.parent.uuid); + parent?.children?.push(map.get(space.uuid)); + } else { + rootSpaces.push(map.get(space.uuid)); + } + }); + + return rootSpaces; // Return the root spaces with children nested within them + } } diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 69951de..45c1f02 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -2,14 +2,26 @@ import { SpaceRepositoryModule } from '@app/common/modules/space/space.repositor import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { SpaceController } from './controllers'; -import { SpaceService } from './services'; +import { SpaceService, SpaceUserService } from './services'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { SpaceUserController } from './controllers/space-user.controller'; +import { + UserRepository, + UserSpaceRepository, +} from '@app/common/modules/user/repositories'; @Module({ imports: [ConfigModule, SpaceRepositoryModule], - controllers: [SpaceController], - providers: [SpaceService, SpaceRepository, CommunityRepository], + controllers: [SpaceController, SpaceUserController], + providers: [ + SpaceService, + SpaceRepository, + CommunityRepository, + UserSpaceRepository, + UserRepository, + SpaceUserService, + ], exports: [SpaceService], }) export class SpaceModule {} diff --git a/src/users/controllers/index.ts b/src/users/controllers/index.ts index 6c91f32..291e2fc 100644 --- a/src/users/controllers/index.ts +++ b/src/users/controllers/index.ts @@ -1,2 +1,3 @@ export * from './user.controller'; export * from './user-communities.controller'; +export * from './user-space.controller'; \ No newline at end of file diff --git a/src/users/controllers/user-space.controller.ts b/src/users/controllers/user-space.controller.ts new file mode 100644 index 0000000..519a033 --- /dev/null +++ b/src/users/controllers/user-space.controller.ts @@ -0,0 +1,30 @@ +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { Controller, Get, Param, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { UserSpaceService } from '../services'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { UserParamDto } from '../dtos'; + +@ApiTags('User Module') +@Controller({ + version: EnableDisableStatusEnum.ENABLED, + path: ControllerRoute.USER_SPACE.ROUTE, +}) +export class UserSpaceController { + constructor(private readonly userSpaceService: UserSpaceService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get() + @ApiOperation({ + summary: ControllerRoute.USER_SPACE.ACTIONS.GET_USER_SPACES_SUMMARY, + description: ControllerRoute.USER_SPACE.ACTIONS.GET_USER_SPACES_DESCRIPTION, + }) + async getSpacesForUser( + @Param() params: UserParamDto, + ): Promise { + return this.userSpaceService.getSpacesForUser(params.userUuid); + } +} diff --git a/src/users/services/index.ts b/src/users/services/index.ts index 56c0003..755afb6 100644 --- a/src/users/services/index.ts +++ b/src/users/services/index.ts @@ -1,2 +1,3 @@ export * from './user.service'; export * from './user-community.service'; +export * from './user-space.service'; diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts new file mode 100644 index 0000000..579ff63 --- /dev/null +++ b/src/users/services/user-space.service.ts @@ -0,0 +1,30 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { UserSpaceRepository } from '@app/common/modules/user/repositories'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; + +@Injectable() +export class UserSpaceService { + constructor(private readonly userSpaceRepository: UserSpaceRepository) {} + + async getSpacesForUser(userUuid: string): Promise { + const userSpaces = await this.userSpaceRepository.find({ + where: { user: { uuid: userUuid } }, + relations: ['space'], + }); + + if (!userSpaces || userSpaces.length === 0) { + throw new HttpException( + `No spaces found for user with ID ${userUuid}`, + HttpStatus.NOT_FOUND, + ); + } + + const spaces = userSpaces.map((userSpace) => userSpace.space); + + return new SuccessResponseDto({ + data: spaces, + message: `Spaces for user ${userUuid} retrieved successfully`, + }); + } +} From 51922a8ae2c7a7897b3753bc59ed9e2de3ae4fe3 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 21 Oct 2024 10:00:01 +0400 Subject: [PATCH 007/109] device start using space --- src/device/controllers/device.controller.ts | 36 +++++----------- src/device/dtos/add.device.dto.ts | 6 +-- src/device/dtos/get.device.dto.ts | 6 +-- src/device/services/device.service.ts | 47 +++++++++++---------- 4 files changed, 41 insertions(+), 54 deletions(-) diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 5f1a7ae..e2aa32c 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -12,11 +12,8 @@ import { Put, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; -import { AddDeviceDto, UpdateDeviceInRoomDto } from '../dtos/add.device.dto'; -import { - GetDeviceByRoomUuidDto, - GetDeviceLogsDto, -} from '../dtos/get.device.dto'; +import { AddDeviceDto, UpdateDeviceInSpaceDto } from '../dtos/add.device.dto'; +import { GetDeviceLogsDto } from '../dtos/get.device.dto'; import { ControlDeviceDto, BatchControlDevicesDto, @@ -30,7 +27,6 @@ import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { CheckDeviceGuard } from 'src/guards/device.guard'; import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; -import { SpaceType } from '@app/common/constants/space-type.enum'; @ApiTags('Device Module') @Controller({ @@ -58,33 +54,21 @@ export class DeviceController { async getDevicesByUser(@Param('userUuid') userUuid: string) { return await this.deviceService.getDevicesByUser(userUuid); } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckRoomGuard) - @Get(SpaceType.ROOM) - async getDevicesByRoomId( - @Query() getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto, - @Req() req: any, - ) { - const userUuid = req.user.uuid; - return await this.deviceService.getDevicesByRoomId( - getDeviceByRoomUuidDto, - userUuid, - ); - } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get('unit/:unitUuid') - async getDevicesByUnitId(@Param('unitUuid') unitUuid: string) { - return await this.deviceService.getDevicesByUnitId(unitUuid); + @Get('space/:spaceUuid') + async getDevicesByUnitId(@Param('spaceUuid') spaceUuid: string) { + return await this.deviceService.getDevicesBySpaceUuid(spaceUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard, CheckRoomGuard) - @Put('room') + @Put('space') async updateDeviceInRoom( - @Body() updateDeviceInRoomDto: UpdateDeviceInRoomDto, + @Body() updateDeviceInSpaceDto: UpdateDeviceInSpaceDto, ) { - const device = await this.deviceService.updateDeviceInRoom( - updateDeviceInRoomDto, + const device = await this.deviceService.updateDeviceInSpace( + updateDeviceInSpaceDto, ); return { diff --git a/src/device/dtos/add.device.dto.ts b/src/device/dtos/add.device.dto.ts index f732317..bff522f 100644 --- a/src/device/dtos/add.device.dto.ts +++ b/src/device/dtos/add.device.dto.ts @@ -18,7 +18,7 @@ export class AddDeviceDto { @IsNotEmpty() public userUuid: string; } -export class UpdateDeviceInRoomDto { +export class UpdateDeviceInSpaceDto { @ApiProperty({ description: 'deviceUuid', required: true, @@ -28,10 +28,10 @@ export class UpdateDeviceInRoomDto { public deviceUuid: string; @ApiProperty({ - description: 'roomUuid', + description: 'spaceUuid', required: true, }) @IsString() @IsNotEmpty() - public roomUuid: string; + public spaceUuid: string; } diff --git a/src/device/dtos/get.device.dto.ts b/src/device/dtos/get.device.dto.ts index 13204de..f3ce5cc 100644 --- a/src/device/dtos/get.device.dto.ts +++ b/src/device/dtos/get.device.dto.ts @@ -1,14 +1,14 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; -export class GetDeviceByRoomUuidDto { +export class GetDeviceBySpaceUuidDto { @ApiProperty({ - description: 'roomUuid', + description: 'spaceUuid', required: true, }) @IsString() @IsNotEmpty() - public roomUuid: string; + public spaceUuid: string; } export class GetDeviceLogsDto { @ApiProperty({ diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 2b37df8..9681632 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -8,7 +8,7 @@ import { } from '@nestjs/common'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { ConfigService } from '@nestjs/config'; -import { AddDeviceDto, UpdateDeviceInRoomDto } from '../dtos/add.device.dto'; +import { AddDeviceDto, UpdateDeviceInSpaceDto } from '../dtos/add.device.dto'; import { DeviceInstructionResponse, GetDeviceDetailsFunctionsInterface, @@ -19,7 +19,7 @@ import { updateDeviceFirmwareInterface, } from '../interfaces/get.device.interface'; import { - GetDeviceByRoomUuidDto, + GetDeviceBySpaceUuidDto, GetDeviceLogsDto, } from '../dtos/get.device.dto'; import { @@ -69,6 +69,7 @@ export class DeviceService { ...(withProductDevice && { relations: ['productDevice'] }), }); } + async getDeviceByDeviceTuyaUuid(deviceTuyaUuid: string) { return await this.deviceRepository.findOne({ where: { @@ -77,6 +78,7 @@ export class DeviceService { relations: ['productDevice'], }); } + async addDeviceUser(addDeviceDto: AddDeviceDto) { try { const device = await this.getDeviceDetailsByDeviceIdTuya( @@ -116,12 +118,13 @@ export class DeviceService { ); } else { throw new HttpException( - error.message || 'Failed to add device in room', + error.message || 'Failed to add device in space', error.status || HttpStatus.INTERNAL_SERVER_ERROR, ); } } } + async getDevicesByUser( userUuid: string, ): Promise { @@ -168,14 +171,15 @@ export class DeviceService { ); } } - async getDevicesByRoomId( - getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto, + + async getDevicesBySpaceId( + getDeviceBySpaceUuidDto: GetDeviceBySpaceUuidDto, userUuid: string, ): Promise { try { const devices = await this.deviceRepository.find({ where: { - spaceDevice: { uuid: getDeviceByRoomUuidDto.roomUuid }, + spaceDevice: { uuid: getDeviceBySpaceUuidDto.spaceUuid }, isActive: true, permission: { userUuid, @@ -210,22 +214,22 @@ export class DeviceService { } catch (error) { // Handle the error here throw new HttpException( - 'Error fetching devices by room', + 'Error fetching devices by space', HttpStatus.INTERNAL_SERVER_ERROR, ); } } - async updateDeviceInRoom(updateDeviceInRoomDto: UpdateDeviceInRoomDto) { + async updateDeviceInSpace(updateDeviceInSpaceDto: UpdateDeviceInSpaceDto) { try { await this.deviceRepository.update( - { uuid: updateDeviceInRoomDto.deviceUuid }, + { uuid: updateDeviceInSpaceDto.deviceUuid }, { - spaceDevice: { uuid: updateDeviceInRoomDto.roomUuid }, + spaceDevice: { uuid: updateDeviceInSpaceDto.spaceUuid }, }, ); const device = await this.deviceRepository.findOne({ where: { - uuid: updateDeviceInRoomDto.deviceUuid, + uuid: updateDeviceInSpaceDto.deviceUuid, }, relations: ['spaceDevice', 'spaceDevice.parent'], }); @@ -238,15 +242,16 @@ export class DeviceService { return { uuid: device.uuid, - roomUuid: device.spaceDevice.uuid, + spaceUuid: device.spaceDevice.uuid, }; } catch (error) { throw new HttpException( - 'Failed to add device in room', + 'Failed to add device in space', HttpStatus.INTERNAL_SERVER_ERROR, ); } } + async transferDeviceInSpacesTuya( deviceId: string, spaceId: string, @@ -294,6 +299,7 @@ export class DeviceService { ); } } + async factoryResetDeviceTuya( deviceUuid: string, ): Promise { @@ -336,6 +342,7 @@ export class DeviceService { ); } } + async batchControlDevices(batchControlDevicesDto: BatchControlDevicesDto) { const { devicesUuid } = batchControlDevicesDto; @@ -777,12 +784,12 @@ export class DeviceService { ); } } - async getDevicesByUnitId(unitUuid: string) { + async getDevicesBySpaceUuid(SpaceUuid: string) { try { const spaces = await this.spaceRepository.find({ where: { parent: { - uuid: unitUuid, + uuid: SpaceUuid, }, devicesSpaceEntity: { isActive: true, @@ -813,7 +820,7 @@ export class DeviceService { return devicesData; } catch (error) { throw new HttpException( - 'This unit does not have any devices', + 'This space does not have any devices', HttpStatus.NOT_FOUND, ); } @@ -874,16 +881,12 @@ export class DeviceService { } } const spaceDevice = device?.spaceDevice; - const parentDevice = spaceDevice?.parent; return { - room: { + space: { uuid: spaceDevice?.uuid, name: spaceDevice?.spaceName, }, - unit: { - uuid: parentDevice?.uuid, - name: parentDevice?.spaceName, - }, + productUuid: device.productDevice.uuid, productType: device.productDevice.prodType, permissionType: device.permission[0].permissionType.type, From e993351a65bdaadb63ee598b76f126311251db70 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 21 Oct 2024 10:21:23 +0400 Subject: [PATCH 008/109] removed permission guard for now --- src/community/controllers/community-spaces.controller.ts | 2 +- src/community/controllers/community.controller.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/community/controllers/community-spaces.controller.ts b/src/community/controllers/community-spaces.controller.ts index 333644f..437b047 100644 --- a/src/community/controllers/community-spaces.controller.ts +++ b/src/community/controllers/community-spaces.controller.ts @@ -17,7 +17,7 @@ export class CommunitySpaceController { constructor(private readonly communitySpaceService: CommunitySpaceService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CommunityPermissionGuard) + @UseGuards(JwtAuthGuard) @ApiOperation({ summary: ControllerRoute.COMMUNITY_SPACE.ACTIONS diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index 925f1c6..374eb59 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -43,7 +43,7 @@ export class CommunityController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CommunityPermissionGuard) + @UseGuards(JwtAuthGuard) @ApiOperation({ summary: ControllerRoute.COMMUNITY.ACTIONS.GET_COMMUNITY_BY_ID_SUMMARY, description: @@ -70,7 +70,7 @@ export class CommunityController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CommunityPermissionGuard) + @UseGuards(JwtAuthGuard) @ApiOperation({ summary: ControllerRoute.COMMUNITY.ACTIONS.UPDATE_COMMUNITY_SUMMARY, description: ControllerRoute.COMMUNITY.ACTIONS.UPDATE_COMMUNITY_DESCRIPTION, From 19bca44aa13b658728dc1601a9aab1cd43b0df74 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 21 Oct 2024 10:21:58 +0400 Subject: [PATCH 009/109] removed unused --- src/community/controllers/community-spaces.controller.ts | 1 - src/community/controllers/community.controller.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/community/controllers/community-spaces.controller.ts b/src/community/controllers/community-spaces.controller.ts index 437b047..1897d21 100644 --- a/src/community/controllers/community-spaces.controller.ts +++ b/src/community/controllers/community-spaces.controller.ts @@ -6,7 +6,6 @@ import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { ControllerRoute } from '@app/common/constants/controller-route'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { CommunitySpaceService } from '../services'; -import { CommunityPermissionGuard } from 'src/guards/community.permission.guard'; @ApiTags('Community Module') @Controller({ diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index 374eb59..86bad74 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -19,7 +19,6 @@ import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { ControllerRoute } from '@app/common/constants/controller-route'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; -import { CommunityPermissionGuard } from 'src/guards/community.permission.guard'; @ApiTags('Community Module') @Controller({ From 50cc6ed38f265b5d0b2b1723feaffef526f549e3 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 21 Oct 2024 10:29:59 +0400 Subject: [PATCH 010/109] fixed communityUuid --- libs/common/src/constants/controller-route.ts | 2 +- src/device/services/device.service.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index f59222b..6265818 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -81,7 +81,7 @@ export class ControllerRoute { }; static SPACE = class { - public static readonly ROUTE = '/communities/:communityId/spaces'; + public static readonly ROUTE = '/communities/:communityUuid/spaces'; static ACTIONS = class { public static readonly CREATE_SPACE_SUMMARY = 'Create a new space'; public static readonly CREATE_SPACE_DESCRIPTION = diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 9681632..25e34c6 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -915,6 +915,7 @@ export class DeviceService { ); } } + async getDeviceLogs(deviceUuid: string, query: GetDeviceLogsDto) { try { const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); From 581618296ddf23e5660d504fa7dfd1303a235c27 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 21 Oct 2024 10:34:55 +0400 Subject: [PATCH 011/109] fixed space name --- libs/common/src/modules/space/entities/space.entity.ts | 6 ------ src/space/dtos/add.space.dto.ts | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index fefe30c..42fea35 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -26,8 +26,6 @@ export class SpaceTypeEntity extends AbstractEntity { }) type: string; - @OneToMany(() => SpaceEntity, (space) => space.spaceType) - spaces: SpaceEntity[]; constructor(partial: Partial) { super(); Object.assign(this, partial); @@ -69,10 +67,6 @@ export class SpaceEntity extends AbstractEntity { @OneToMany(() => SpaceEntity, (space) => space.parent) children: SpaceEntity[]; - @ManyToOne(() => SpaceTypeEntity, (spaceType) => spaceType.spaces, { - nullable: false, - }) - spaceType: SpaceTypeEntity; @OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.space) userSpaces: UserSpaceEntity[]; diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index e0a0dac..9d383bd 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -14,7 +14,7 @@ export class AddSpaceDto { }) @IsString() @IsNotEmpty() - name: string; + spaceName: string; @ApiProperty({ description: 'UUID of the parent space (if any, for hierarchical spaces)', From 3bd18c2b298be92ff0dbfef23e884be427460437 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 22 Oct 2024 10:02:54 +0400 Subject: [PATCH 012/109] removed unit,floor,building,room modules --- src/app.module.ts | 11 +- src/automation/services/automation.service.ts | 11 +- src/building/building.module.ts | 24 - .../controllers/building.controller.ts | 106 ---- src/building/controllers/index.ts | 1 - src/building/dtos/add.building.dto.ts | 42 -- src/building/dtos/get.building.dto.ts | 52 -- src/building/dtos/index.ts | 1 - src/building/dtos/update.building.dto.ts | 16 - src/building/interface/building.interface.ts | 31 -- src/building/services/building.service.ts | 317 ------------ src/building/services/index.ts | 1 - src/device/services/device.service.ts | 105 +++- src/floor/controllers/floor.controller.ts | 104 ---- src/floor/controllers/index.ts | 1 - src/floor/dtos/add.floor.dto.ts | 42 -- src/floor/dtos/get.floor.dto.ts | 52 -- src/floor/dtos/index.ts | 1 - src/floor/dtos/update.floor.dto.ts | 16 - src/floor/floor.module.ts | 24 - src/floor/interface/floor.interface.ts | 32 -- src/floor/services/floor.service.ts | 310 ------------ src/floor/services/index.ts | 1 - src/guards/building.type.guard.ts | 7 +- src/guards/community.type.guard.ts | 7 +- src/guards/floor.type.guard.ts | 8 +- src/guards/room.guard.ts | 4 - src/guards/unit.type.guard.ts | 8 +- src/guards/user.building.guard.ts | 3 +- src/guards/user.community.guard.ts | 4 +- src/guards/user.floor.guard.ts | 3 +- src/guards/user.room.guard.ts | 3 +- src/guards/user.unit.guard.ts | 3 +- src/room/controllers/index.ts | 1 - src/room/controllers/room.controller.ts | 90 ---- src/room/dtos/add.room.dto.ts | 42 -- src/room/dtos/index.ts | 1 - src/room/dtos/update.room.dto.ts | 16 - src/room/interface/room.interface.ts | 24 - src/room/room.module.ts | 24 - src/room/services/index.ts | 1 - src/room/services/room.service.ts | 200 -------- src/scene/services/scene.service.ts | 11 +- src/unit/controllers/index.ts | 1 - src/unit/controllers/unit.controller.ts | 126 ----- src/unit/dtos/add.unit.dto.ts | 61 --- src/unit/dtos/get.unit.dto.ts | 39 -- src/unit/dtos/index.ts | 1 - src/unit/dtos/update.unit.dto.ts | 16 - src/unit/interface/unit.interface.ts | 37 -- src/unit/services/index.ts | 1 - src/unit/services/unit.service.ts | 453 ------------------ src/unit/unit.module.ts | 30 -- 53 files changed, 110 insertions(+), 2416 deletions(-) delete mode 100644 src/building/building.module.ts delete mode 100644 src/building/controllers/building.controller.ts delete mode 100644 src/building/controllers/index.ts delete mode 100644 src/building/dtos/add.building.dto.ts delete mode 100644 src/building/dtos/get.building.dto.ts delete mode 100644 src/building/dtos/index.ts delete mode 100644 src/building/dtos/update.building.dto.ts delete mode 100644 src/building/interface/building.interface.ts delete mode 100644 src/building/services/building.service.ts delete mode 100644 src/building/services/index.ts delete mode 100644 src/floor/controllers/floor.controller.ts delete mode 100644 src/floor/controllers/index.ts delete mode 100644 src/floor/dtos/add.floor.dto.ts delete mode 100644 src/floor/dtos/get.floor.dto.ts delete mode 100644 src/floor/dtos/index.ts delete mode 100644 src/floor/dtos/update.floor.dto.ts delete mode 100644 src/floor/floor.module.ts delete mode 100644 src/floor/interface/floor.interface.ts delete mode 100644 src/floor/services/floor.service.ts delete mode 100644 src/floor/services/index.ts delete mode 100644 src/room/controllers/index.ts delete mode 100644 src/room/controllers/room.controller.ts delete mode 100644 src/room/dtos/add.room.dto.ts delete mode 100644 src/room/dtos/index.ts delete mode 100644 src/room/dtos/update.room.dto.ts delete mode 100644 src/room/interface/room.interface.ts delete mode 100644 src/room/room.module.ts delete mode 100644 src/room/services/index.ts delete mode 100644 src/room/services/room.service.ts delete mode 100644 src/unit/controllers/index.ts delete mode 100644 src/unit/controllers/unit.controller.ts delete mode 100644 src/unit/dtos/add.unit.dto.ts delete mode 100644 src/unit/dtos/get.unit.dto.ts delete mode 100644 src/unit/dtos/index.ts delete mode 100644 src/unit/dtos/update.unit.dto.ts delete mode 100644 src/unit/interface/unit.interface.ts delete mode 100644 src/unit/services/index.ts delete mode 100644 src/unit/services/unit.service.ts delete mode 100644 src/unit/unit.module.ts diff --git a/src/app.module.ts b/src/app.module.ts index 568cff8..820602b 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,14 +3,10 @@ import { ConfigModule } from '@nestjs/config'; import config from './config'; import { AuthenticationModule } from './auth/auth.module'; import { UserModule } from './users/user.module'; -import { RoomModule } from './room/room.module'; import { GroupModule } from './group/group.module'; import { DeviceModule } from './device/device.module'; import { UserDevicePermissionModule } from './user-device-permission/user-device-permission.module'; import { CommunityModule } from './community/community.module'; -import { BuildingModule } from './building/building.module'; -import { FloorModule } from './floor/floor.module'; -import { UnitModule } from './unit/unit.module'; import { RoleModule } from './role/role.module'; import { SeederModule } from '@app/common/seed/seeder.module'; import { UserNotificationModule } from './user-notification/user-notification.module'; @@ -34,12 +30,9 @@ import { SpaceModule } from './space/space.module'; UserModule, RoleModule, CommunityModule, - BuildingModule, - FloorModule, - UnitModule, + SpaceModule, - RoomModule, - RoomModule, + GroupModule, DeviceModule, DeviceMessagesSubscriptionModule, diff --git a/src/automation/services/automation.service.ts b/src/automation/services/automation.service.ts index 3f5a4d5..95d9459 100644 --- a/src/automation/services/automation.service.ts +++ b/src/automation/services/automation.service.ts @@ -10,7 +10,6 @@ import { UpdateAutomationDto, UpdateAutomationStatusDto, } from '../dtos'; -import { GetUnitByUuidInterface } from 'src/unit/interface/unit.interface'; import { ConfigService } from '@nestjs/config'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { convertKeysToSnakeCase } from '@app/common/helper/snakeCaseConverter'; @@ -23,7 +22,6 @@ import { GetAutomationByUnitInterface, } from '../interface/automation.interface'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; -import { SpaceType } from '@app/common/constants/space-type.enum'; import { ActionExecutorEnum, EntityTypeEnum, @@ -126,18 +124,14 @@ export class AutomationService { } } } - async getUnitByUuid(unitUuid: string): Promise { + async getUnitByUuid(unitUuid: string) { try { const unit = await this.spaceRepository.findOne({ where: { uuid: unitUuid, - spaceType: { - type: SpaceType.UNIT, - }, }, - relations: ['spaceType'], }); - if (!unit || !unit.spaceType || unit.spaceType.type !== SpaceType.UNIT) { + if (!unit) { throw new BadRequestException('Invalid unit UUID'); } return { @@ -145,7 +139,6 @@ export class AutomationService { createdAt: unit.createdAt, updatedAt: unit.updatedAt, name: unit.spaceName, - type: unit.spaceType.type, spaceTuyaUuid: unit.spaceTuyaUuid, }; } catch (err) { diff --git a/src/building/building.module.ts b/src/building/building.module.ts deleted file mode 100644 index a78c049..0000000 --- a/src/building/building.module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Module } from '@nestjs/common'; -import { BuildingService } from './services/building.service'; -import { BuildingController } from './controllers/building.controller'; -import { ConfigModule } from '@nestjs/config'; -import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { SpaceTypeRepository } from '@app/common/modules/space/repositories'; -import { UserSpaceRepository } from '@app/common/modules/user/repositories'; -import { UserRepository } from '@app/common/modules/user/repositories'; -import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; - -@Module({ - imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], - controllers: [BuildingController], - providers: [ - BuildingService, - SpaceRepository, - SpaceTypeRepository, - UserSpaceRepository, - UserRepository, - ], - exports: [BuildingService], -}) -export class BuildingModule {} diff --git a/src/building/controllers/building.controller.ts b/src/building/controllers/building.controller.ts deleted file mode 100644 index 8c7d1b1..0000000 --- a/src/building/controllers/building.controller.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { BuildingService } from '../services/building.service'; -import { - Body, - Controller, - Get, - HttpStatus, - Param, - Post, - Put, - Query, - UseGuards, -} from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; -import { AddBuildingDto, AddUserBuildingDto } from '../dtos/add.building.dto'; -import { GetBuildingChildDto } from '../dtos/get.building.dto'; -import { UpdateBuildingNameDto } from '../dtos/update.building.dto'; -import { CheckCommunityTypeGuard } from 'src/guards/community.type.guard'; -import { CheckUserBuildingGuard } from 'src/guards/user.building.guard'; -import { AdminRoleGuard } from 'src/guards/admin.role.guard'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; -import { BuildingPermissionGuard } from 'src/guards/building.permission.guard'; -import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; -import { SpaceType } from '@app/common/constants/space-type.enum'; - -@ApiTags('Building Module') -@Controller({ - version: EnableDisableStatusEnum.ENABLED, - path: SpaceType.BUILDING, -}) -export class BuildingController { - constructor(private readonly buildingService: BuildingService) {} - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckCommunityTypeGuard) - @Post() - async addBuilding(@Body() addBuildingDto: AddBuildingDto) { - const building = await this.buildingService.addBuilding(addBuildingDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Building added successfully', - data: building, - }; - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, BuildingPermissionGuard) - @Get(':buildingUuid') - async getBuildingByUuid(@Param('buildingUuid') buildingUuid: string) { - const building = await this.buildingService.getBuildingByUuid(buildingUuid); - return building; - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, BuildingPermissionGuard) - @Get('child/:buildingUuid') - async getBuildingChildByUuid( - @Param('buildingUuid') buildingUuid: string, - @Query() query: GetBuildingChildDto, - ) { - const building = await this.buildingService.getBuildingChildByUuid( - buildingUuid, - query, - ); - return building; - } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, BuildingPermissionGuard) - @Get('parent/:buildingUuid') - async getBuildingParentByUuid(@Param('buildingUuid') buildingUuid: string) { - const building = - await this.buildingService.getBuildingParentByUuid(buildingUuid); - return building; - } - @ApiBearerAuth() - @UseGuards(AdminRoleGuard, CheckUserBuildingGuard) - @Post('user') - async addUserBuilding(@Body() addUserBuildingDto: AddUserBuildingDto) { - await this.buildingService.addUserBuilding(addUserBuildingDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'user building added successfully', - }; - } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get('user/:userUuid') - async getBuildingsByUserId(@Param('userUuid') userUuid: string) { - return await this.buildingService.getBuildingsByUserId(userUuid); - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, BuildingPermissionGuard) - @Put(':buildingUuid') - async renameBuildingByUuid( - @Param('buildingUuid') buildingUuid: string, - @Body() updateBuildingDto: UpdateBuildingNameDto, - ) { - const building = await this.buildingService.renameBuildingByUuid( - buildingUuid, - updateBuildingDto, - ); - return building; - } -} diff --git a/src/building/controllers/index.ts b/src/building/controllers/index.ts deleted file mode 100644 index b5ec3c2..0000000 --- a/src/building/controllers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './building.controller'; diff --git a/src/building/dtos/add.building.dto.ts b/src/building/dtos/add.building.dto.ts deleted file mode 100644 index 5d79231..0000000 --- a/src/building/dtos/add.building.dto.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class AddBuildingDto { - @ApiProperty({ - description: 'buildingName', - required: true, - }) - @IsString() - @IsNotEmpty() - public buildingName: string; - - @ApiProperty({ - description: 'communityUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public communityUuid: string; - constructor(dto: Partial) { - Object.assign(this, dto); - } -} -export class AddUserBuildingDto { - @ApiProperty({ - description: 'buildingUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public buildingUuid: string; - @ApiProperty({ - description: 'userUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public userUuid: string; - constructor(dto: Partial) { - Object.assign(this, dto); - } -} diff --git a/src/building/dtos/get.building.dto.ts b/src/building/dtos/get.building.dto.ts deleted file mode 100644 index fae0c6b..0000000 --- a/src/building/dtos/get.building.dto.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { BooleanValues } from '@app/common/constants/boolean-values.enum'; -import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { - IsBoolean, - IsInt, - IsNotEmpty, - IsOptional, - IsString, - Min, -} from 'class-validator'; - -export class GetBuildingDto { - @ApiProperty({ - description: 'buildingUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public buildingUuid: string; -} - -export class GetBuildingChildDto { - @ApiProperty({ example: 1, description: 'Page number', required: true }) - @IsInt({ message: 'Page must be a number' }) - @Min(1, { message: 'Page must not be less than 1' }) - @IsNotEmpty() - public page: number; - - @ApiProperty({ - example: 10, - description: 'Number of items per page', - required: true, - }) - @IsInt({ message: 'Page size must be a number' }) - @Min(1, { message: 'Page size must not be less than 1' }) - @IsNotEmpty() - public pageSize: number; - - @ApiProperty({ - example: true, - description: 'Flag to determine whether to fetch full hierarchy', - required: false, - default: false, - }) - @IsOptional() - @IsBoolean() - @Transform((value) => { - return value.obj.includeSubSpaces === BooleanValues.TRUE; - }) - public includeSubSpaces: boolean = false; -} diff --git a/src/building/dtos/index.ts b/src/building/dtos/index.ts deleted file mode 100644 index 93e7c6f..0000000 --- a/src/building/dtos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './add.building.dto'; diff --git a/src/building/dtos/update.building.dto.ts b/src/building/dtos/update.building.dto.ts deleted file mode 100644 index 0f07cbe..0000000 --- a/src/building/dtos/update.building.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class UpdateBuildingNameDto { - @ApiProperty({ - description: 'buildingName', - required: true, - }) - @IsString() - @IsNotEmpty() - public buildingName: string; - - constructor(dto: Partial) { - Object.assign(this, dto); - } -} diff --git a/src/building/interface/building.interface.ts b/src/building/interface/building.interface.ts deleted file mode 100644 index 1127456..0000000 --- a/src/building/interface/building.interface.ts +++ /dev/null @@ -1,31 +0,0 @@ -export interface GetBuildingByUuidInterface { - uuid: string; - createdAt: Date; - updatedAt: Date; - name: string; - type: string; -} - -export interface BuildingChildInterface { - uuid: string; - name: string; - type: string; - totalCount?: number; - children?: BuildingChildInterface[]; -} -export interface BuildingParentInterface { - uuid: string; - name: string; - type: string; - parent?: BuildingParentInterface; -} -export interface RenameBuildingByUuidInterface { - uuid: string; - name: string; - type: string; -} -export interface GetBuildingByUserUuidInterface { - uuid: string; - name: string; - type: string; -} diff --git a/src/building/services/building.service.ts b/src/building/services/building.service.ts deleted file mode 100644 index b3de5d4..0000000 --- a/src/building/services/building.service.ts +++ /dev/null @@ -1,317 +0,0 @@ -import { GetBuildingChildDto } from '../dtos/get.building.dto'; -import { SpaceTypeRepository } from '../../../libs/common/src/modules/space/repositories/space.repository'; -import { - Injectable, - HttpException, - HttpStatus, - BadRequestException, -} from '@nestjs/common'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { AddBuildingDto, AddUserBuildingDto } from '../dtos'; -import { - BuildingChildInterface, - BuildingParentInterface, - GetBuildingByUserUuidInterface, - GetBuildingByUuidInterface, - RenameBuildingByUuidInterface, -} from '../interface/building.interface'; -import { SpaceEntity } from '@app/common/modules/space/entities'; -import { UpdateBuildingNameDto } from '../dtos/update.building.dto'; -import { UserSpaceRepository } from '@app/common/modules/user/repositories'; -import { SpaceType } from '@app/common/constants/space-type.enum'; -import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; - -@Injectable() -export class BuildingService { - constructor( - private readonly spaceRepository: SpaceRepository, - private readonly spaceTypeRepository: SpaceTypeRepository, - private readonly userSpaceRepository: UserSpaceRepository, - ) {} - - async addBuilding(addBuildingDto: AddBuildingDto) { - try { - const spaceType = await this.spaceTypeRepository.findOne({ - where: { - type: SpaceType.BUILDING, - }, - }); - - if (!spaceType) { - throw new BadRequestException('Invalid building UUID'); - } - const building = await this.spaceRepository.save({ - spaceName: addBuildingDto.buildingName, - parent: { uuid: addBuildingDto.communityUuid }, - spaceType: { uuid: spaceType.uuid }, - }); - return building; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Building not found', HttpStatus.NOT_FOUND); - } - } - } - - async getBuildingByUuid( - buildingUuid: string, - ): Promise { - try { - const building = await this.spaceRepository.findOne({ - where: { - uuid: buildingUuid, - spaceType: { - type: SpaceType.BUILDING, - }, - }, - relations: ['spaceType'], - }); - if ( - !building || - !building.spaceType || - building.spaceType.type !== SpaceType.BUILDING - ) { - throw new BadRequestException('Invalid building UUID'); - } - return { - uuid: building.uuid, - createdAt: building.createdAt, - updatedAt: building.updatedAt, - name: building.spaceName, - type: building.spaceType.type, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Building not found', HttpStatus.NOT_FOUND); - } - } - } - async getBuildingChildByUuid( - buildingUuid: string, - getBuildingChildDto: GetBuildingChildDto, - ): Promise { - try { - const { includeSubSpaces, page, pageSize } = getBuildingChildDto; - - const space = await this.spaceRepository.findOneOrFail({ - where: { uuid: buildingUuid }, - relations: ['children', 'spaceType'], - }); - if ( - !space || - !space.spaceType || - space.spaceType.type !== SpaceType.BUILDING - ) { - throw new BadRequestException('Invalid building UUID'); - } - - const totalCount = await this.spaceRepository.count({ - where: { parent: { uuid: space.uuid } }, - }); - - const children = await this.buildHierarchy( - space, - includeSubSpaces, - page, - pageSize, - ); - - return { - uuid: space.uuid, - name: space.spaceName, - type: space.spaceType.type, - totalCount, - children, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Building not found', HttpStatus.NOT_FOUND); - } - } - } - - private async buildHierarchy( - space: SpaceEntity, - includeSubSpaces: boolean, - page: number, - pageSize: number, - ): Promise { - const children = await this.spaceRepository.find({ - where: { parent: { uuid: space.uuid } }, - relations: ['spaceType'], - skip: (page - 1) * pageSize, - take: pageSize, - }); - - if (!children || children.length === 0 || !includeSubSpaces) { - return children - .filter( - (child) => - child.spaceType.type !== SpaceType.BUILDING && - child.spaceType.type !== SpaceType.COMMUNITY, - ) // Filter remaining building and community types - .map((child) => ({ - uuid: child.uuid, - name: child.spaceName, - type: child.spaceType.type, - })); - } - - const childHierarchies = await Promise.all( - children - .filter( - (child) => - child.spaceType.type !== SpaceType.BUILDING && - child.spaceType.type !== SpaceType.COMMUNITY, - ) // Filter remaining building and community types - .map(async (child) => ({ - uuid: child.uuid, - name: child.spaceName, - type: child.spaceType.type, - children: await this.buildHierarchy(child, true, 1, pageSize), - })), - ); - - return childHierarchies; - } - - async getBuildingParentByUuid( - buildingUuid: string, - ): Promise { - try { - const building = await this.spaceRepository.findOne({ - where: { - uuid: buildingUuid, - spaceType: { - type: SpaceType.BUILDING, - }, - }, - relations: ['spaceType', 'parent', 'parent.spaceType'], - }); - if ( - !building || - !building.spaceType || - building.spaceType.type !== SpaceType.BUILDING - ) { - throw new BadRequestException('Invalid building UUID'); - } - return { - uuid: building.uuid, - name: building.spaceName, - type: building.spaceType.type, - parent: { - uuid: building.parent.uuid, - name: building.parent.spaceName, - type: building.parent.spaceType.type, - }, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Building not found', HttpStatus.NOT_FOUND); - } - } - } - - async getBuildingsByUserId( - userUuid: string, - ): Promise { - try { - const buildings = await this.userSpaceRepository.find({ - relations: ['space', 'space.spaceType'], - where: { - user: { uuid: userUuid }, - space: { spaceType: { type: SpaceType.BUILDING } }, - }, - }); - - if (buildings.length === 0) { - throw new HttpException( - 'this user has no buildings', - HttpStatus.NOT_FOUND, - ); - } - const spaces = buildings.map((building) => ({ - uuid: building.space.uuid, - name: building.space.spaceName, - type: building.space.spaceType.type, - })); - - return spaces; - } catch (err) { - if (err instanceof HttpException) { - throw err; - } else { - throw new HttpException('user not found', HttpStatus.NOT_FOUND); - } - } - } - async addUserBuilding(addUserBuildingDto: AddUserBuildingDto) { - try { - await this.userSpaceRepository.save({ - user: { uuid: addUserBuildingDto.userUuid }, - space: { uuid: addUserBuildingDto.buildingUuid }, - }); - } catch (err) { - if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) { - throw new HttpException( - 'User already belongs to this building', - HttpStatus.BAD_REQUEST, - ); - } - throw new HttpException( - err.message || 'Internal Server Error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async renameBuildingByUuid( - buildingUuid: string, - updateBuildingNameDto: UpdateBuildingNameDto, - ): Promise { - try { - const building = await this.spaceRepository.findOneOrFail({ - where: { uuid: buildingUuid }, - relations: ['spaceType'], - }); - - if ( - !building || - !building.spaceType || - building.spaceType.type !== SpaceType.BUILDING - ) { - throw new BadRequestException('Invalid building UUID'); - } - - await this.spaceRepository.update( - { uuid: buildingUuid }, - { spaceName: updateBuildingNameDto.buildingName }, - ); - - // Fetch the updated building - const updatedBuilding = await this.spaceRepository.findOneOrFail({ - where: { uuid: buildingUuid }, - relations: ['spaceType'], - }); - - return { - uuid: updatedBuilding.uuid, - name: updatedBuilding.spaceName, - type: updatedBuilding.spaceType.type, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Building not found', HttpStatus.NOT_FOUND); - } - } - } -} diff --git a/src/building/services/index.ts b/src/building/services/index.ts deleted file mode 100644 index 7b260d2..0000000 --- a/src/building/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './building.service'; diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 25e34c6..9963383 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -38,6 +38,7 @@ import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status import { DeviceStatuses } from '@app/common/constants/device-status.enum'; import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; import { BatteryStatus } from '@app/common/constants/battery-status.enum'; +import { SpaceEntity } from '@app/common/modules/space/entities'; @Injectable() export class DeviceService { @@ -836,6 +837,7 @@ export class DeviceService { 'permission.permissionType', ], }); + const devicesData = await Promise.allSettled( devices.map(async (device) => { let battery = null; @@ -880,22 +882,26 @@ export class DeviceService { battery = batteryStatus.value; } } - const spaceDevice = device?.spaceDevice; - return { - space: { - uuid: spaceDevice?.uuid, - name: spaceDevice?.spaceName, - }, + const spaceHierarchy = await this.getFullSpaceHierarchy( + device?.spaceDevice, + ); + const orderedHierarchy = spaceHierarchy.reverse(); + + return { + spaces: orderedHierarchy.map((space) => ({ + uuid: space.uuid, + spaceName: space.spaceName, + })), productUuid: device.productDevice.uuid, productType: device.productDevice.prodType, - permissionType: device.permission[0].permissionType.type, - ...(await this.getDeviceDetailsByDeviceIdTuya( + // permissionType: device.permission[0].permissionType.type, + /* ...(await this.getDeviceDetailsByDeviceIdTuya( device.deviceTuyaUuid, - )), + )),*/ uuid: device.uuid, ...(battery && { battery }), - } as GetDeviceDetailsInterface; + }; }), ); @@ -969,4 +975,83 @@ export class DeviceService { ); } } + + async getFullSpaceHierarchy( + space: SpaceEntity, + ): Promise<{ uuid: string; spaceName: string }[]> { + try { + console.log('Fetching hierarchy for space:', space.uuid); + + // Fetch only the relevant spaces, starting with the target space + const targetSpace = await this.spaceRepository.findOne({ + where: { uuid: space.uuid }, + relations: ['parent', 'children'], + }); + + // Fetch only the ancestors of the target space + const ancestors = await this.fetchAncestors(targetSpace); + + // Optionally, fetch descendants if required + const descendants = await this.fetchDescendants(targetSpace); + + const fullHierarchy = [...ancestors, targetSpace, ...descendants].map( + (space) => ({ + uuid: space.uuid, + spaceName: space.spaceName, + }), + ); + + return fullHierarchy; + } catch (error) { + console.error('Error fetching space hierarchy:', error.message); + throw new HttpException( + 'Error fetching space hierarchy', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private async fetchAncestors(space: SpaceEntity): Promise { + const ancestors: SpaceEntity[] = []; + + let currentSpace = space; + while (currentSpace && currentSpace.parent) { + // Fetch the parent space + const parent = await this.spaceRepository.findOne({ + where: { uuid: currentSpace.parent.uuid }, + relations: ['parent'], // To continue fetching upwards + }); + + if (parent) { + ancestors.push(parent); + currentSpace = parent; + } else { + currentSpace = null; + } + } + + // Return the ancestors in reverse order to have the root at the start + return ancestors.reverse(); + } + + private async fetchDescendants(space: SpaceEntity): Promise { + const descendants: SpaceEntity[] = []; + + // Fetch the immediate children of the current space + const children = await this.spaceRepository.find({ + where: { parent: { uuid: space.uuid } }, + relations: ['children'], // To continue fetching downwards + }); + + for (const child of children) { + // Add the child to the descendants list + descendants.push(child); + + // Recursively fetch the child's descendants + const childDescendants = await this.fetchDescendants(child); + descendants.push(...childDescendants); + } + + return descendants; + } } diff --git a/src/floor/controllers/floor.controller.ts b/src/floor/controllers/floor.controller.ts deleted file mode 100644 index 9142db6..0000000 --- a/src/floor/controllers/floor.controller.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { FloorService } from '../services/floor.service'; -import { - Body, - Controller, - Get, - HttpStatus, - Param, - Post, - Put, - Query, - UseGuards, -} from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; -import { AddFloorDto, AddUserFloorDto } from '../dtos/add.floor.dto'; -import { GetFloorChildDto } from '../dtos/get.floor.dto'; -import { UpdateFloorNameDto } from '../dtos/update.floor.dto'; -import { CheckBuildingTypeGuard } from 'src/guards/building.type.guard'; -import { CheckUserFloorGuard } from 'src/guards/user.floor.guard'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; -import { AdminRoleGuard } from 'src/guards/admin.role.guard'; -import { FloorPermissionGuard } from 'src/guards/floor.permission.guard'; -import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; -import { SpaceType } from '@app/common/constants/space-type.enum'; - -@ApiTags('Floor Module') -@Controller({ - version: EnableDisableStatusEnum.ENABLED, - path: SpaceType.FLOOR, -}) -export class FloorController { - constructor(private readonly floorService: FloorService) {} - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckBuildingTypeGuard) - @Post() - async addFloor(@Body() addFloorDto: AddFloorDto) { - const floor = await this.floorService.addFloor(addFloorDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Floor added successfully', - data: floor, - }; - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, FloorPermissionGuard) - @Get(':floorUuid') - async getFloorByUuid(@Param('floorUuid') floorUuid: string) { - const floor = await this.floorService.getFloorByUuid(floorUuid); - return floor; - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, FloorPermissionGuard) - @Get('child/:floorUuid') - async getFloorChildByUuid( - @Param('floorUuid') floorUuid: string, - @Query() query: GetFloorChildDto, - ) { - const floor = await this.floorService.getFloorChildByUuid(floorUuid, query); - return floor; - } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, FloorPermissionGuard) - @Get('parent/:floorUuid') - async getFloorParentByUuid(@Param('floorUuid') floorUuid: string) { - const floor = await this.floorService.getFloorParentByUuid(floorUuid); - return floor; - } - - @ApiBearerAuth() - @UseGuards(AdminRoleGuard, CheckUserFloorGuard) - @Post('user') - async addUserFloor(@Body() addUserFloorDto: AddUserFloorDto) { - await this.floorService.addUserFloor(addUserFloorDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'user floor added successfully', - }; - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get('user/:userUuid') - async getFloorsByUserId(@Param('userUuid') userUuid: string) { - return await this.floorService.getFloorsByUserId(userUuid); - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, FloorPermissionGuard) - @Put(':floorUuid') - async renameFloorByUuid( - @Param('floorUuid') floorUuid: string, - @Body() updateFloorNameDto: UpdateFloorNameDto, - ) { - const floor = await this.floorService.renameFloorByUuid( - floorUuid, - updateFloorNameDto, - ); - return floor; - } -} diff --git a/src/floor/controllers/index.ts b/src/floor/controllers/index.ts deleted file mode 100644 index 99eb600..0000000 --- a/src/floor/controllers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './floor.controller'; diff --git a/src/floor/dtos/add.floor.dto.ts b/src/floor/dtos/add.floor.dto.ts deleted file mode 100644 index 3d1655a..0000000 --- a/src/floor/dtos/add.floor.dto.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class AddFloorDto { - @ApiProperty({ - description: 'floorName', - required: true, - }) - @IsString() - @IsNotEmpty() - public floorName: string; - - @ApiProperty({ - description: 'buildingUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public buildingUuid: string; - constructor(dto: Partial) { - Object.assign(this, dto); - } -} -export class AddUserFloorDto { - @ApiProperty({ - description: 'floorUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public floorUuid: string; - @ApiProperty({ - description: 'userUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public userUuid: string; - constructor(dto: Partial) { - Object.assign(this, dto); - } -} diff --git a/src/floor/dtos/get.floor.dto.ts b/src/floor/dtos/get.floor.dto.ts deleted file mode 100644 index 957f81b..0000000 --- a/src/floor/dtos/get.floor.dto.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { BooleanValues } from '@app/common/constants/boolean-values.enum'; -import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { - IsBoolean, - IsInt, - IsNotEmpty, - IsOptional, - IsString, - Min, -} from 'class-validator'; - -export class GetFloorDto { - @ApiProperty({ - description: 'floorUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public floorUuid: string; -} - -export class GetFloorChildDto { - @ApiProperty({ example: 1, description: 'Page number', required: true }) - @IsInt({ message: 'Page must be a number' }) - @Min(1, { message: 'Page must not be less than 1' }) - @IsNotEmpty() - public page: number; - - @ApiProperty({ - example: 10, - description: 'Number of items per page', - required: true, - }) - @IsInt({ message: 'Page size must be a number' }) - @Min(1, { message: 'Page size must not be less than 1' }) - @IsNotEmpty() - public pageSize: number; - - @ApiProperty({ - example: true, - description: 'Flag to determine whether to fetch full hierarchy', - required: false, - default: false, - }) - @IsOptional() - @IsBoolean() - @Transform((value) => { - return value.obj.includeSubSpaces === BooleanValues.TRUE; - }) - public includeSubSpaces: boolean = false; -} diff --git a/src/floor/dtos/index.ts b/src/floor/dtos/index.ts deleted file mode 100644 index 9c08a9f..0000000 --- a/src/floor/dtos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './add.floor.dto'; diff --git a/src/floor/dtos/update.floor.dto.ts b/src/floor/dtos/update.floor.dto.ts deleted file mode 100644 index 11c97b0..0000000 --- a/src/floor/dtos/update.floor.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class UpdateFloorNameDto { - @ApiProperty({ - description: 'floorName', - required: true, - }) - @IsString() - @IsNotEmpty() - public floorName: string; - - constructor(dto: Partial) { - Object.assign(this, dto); - } -} diff --git a/src/floor/floor.module.ts b/src/floor/floor.module.ts deleted file mode 100644 index 9fdc1c7..0000000 --- a/src/floor/floor.module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Module } from '@nestjs/common'; -import { FloorService } from './services/floor.service'; -import { FloorController } from './controllers/floor.controller'; -import { ConfigModule } from '@nestjs/config'; -import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { SpaceTypeRepository } from '@app/common/modules/space/repositories'; -import { UserSpaceRepository } from '@app/common/modules/user/repositories'; -import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; -import { UserRepository } from '@app/common/modules/user/repositories'; - -@Module({ - imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], - controllers: [FloorController], - providers: [ - FloorService, - SpaceRepository, - SpaceTypeRepository, - UserSpaceRepository, - UserRepository, - ], - exports: [FloorService], -}) -export class FloorModule {} diff --git a/src/floor/interface/floor.interface.ts b/src/floor/interface/floor.interface.ts deleted file mode 100644 index 37f35c4..0000000 --- a/src/floor/interface/floor.interface.ts +++ /dev/null @@ -1,32 +0,0 @@ -export interface GetFloorByUuidInterface { - uuid: string; - createdAt: Date; - updatedAt: Date; - name: string; - type: string; -} - -export interface FloorChildInterface { - uuid: string; - name: string; - type: string; - totalCount?: number; - children?: FloorChildInterface[]; -} -export interface FloorParentInterface { - uuid: string; - name: string; - type: string; - parent?: FloorParentInterface; -} -export interface RenameFloorByUuidInterface { - uuid: string; - name: string; - type: string; -} - -export interface GetFloorByUserUuidInterface { - uuid: string; - name: string; - type: string; -} diff --git a/src/floor/services/floor.service.ts b/src/floor/services/floor.service.ts deleted file mode 100644 index 1f7604f..0000000 --- a/src/floor/services/floor.service.ts +++ /dev/null @@ -1,310 +0,0 @@ -import { GetFloorChildDto } from '../dtos/get.floor.dto'; -import { SpaceTypeRepository } from '../../../libs/common/src/modules/space/repositories/space.repository'; -import { - Injectable, - HttpException, - HttpStatus, - BadRequestException, -} from '@nestjs/common'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { AddFloorDto, AddUserFloorDto } from '../dtos'; -import { - FloorChildInterface, - FloorParentInterface, - GetFloorByUserUuidInterface, - GetFloorByUuidInterface, - RenameFloorByUuidInterface, -} from '../interface/floor.interface'; -import { SpaceEntity } from '@app/common/modules/space/entities'; -import { UpdateFloorNameDto } from '../dtos/update.floor.dto'; -import { UserSpaceRepository } from '@app/common/modules/user/repositories'; -import { SpaceType } from '@app/common/constants/space-type.enum'; -import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; - -@Injectable() -export class FloorService { - constructor( - private readonly spaceRepository: SpaceRepository, - private readonly spaceTypeRepository: SpaceTypeRepository, - private readonly userSpaceRepository: UserSpaceRepository, - ) {} - - async addFloor(addFloorDto: AddFloorDto) { - try { - const spaceType = await this.spaceTypeRepository.findOne({ - where: { - type: SpaceType.FLOOR, - }, - }); - - const floor = await this.spaceRepository.save({ - spaceName: addFloorDto.floorName, - parent: { uuid: addFloorDto.buildingUuid }, - spaceType: { uuid: spaceType.uuid }, - }); - return floor; - } catch (err) { - throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR); - } - } - - async getFloorByUuid(floorUuid: string): Promise { - try { - const floor = await this.spaceRepository.findOne({ - where: { - uuid: floorUuid, - spaceType: { - type: SpaceType.FLOOR, - }, - }, - relations: ['spaceType'], - }); - if ( - !floor || - !floor.spaceType || - floor.spaceType.type !== SpaceType.FLOOR - ) { - throw new BadRequestException('Invalid floor UUID'); - } - - return { - uuid: floor.uuid, - createdAt: floor.createdAt, - updatedAt: floor.updatedAt, - name: floor.spaceName, - type: floor.spaceType.type, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); - } - } - } - async getFloorChildByUuid( - floorUuid: string, - getFloorChildDto: GetFloorChildDto, - ): Promise { - try { - const { includeSubSpaces, page, pageSize } = getFloorChildDto; - - const space = await this.spaceRepository.findOneOrFail({ - where: { uuid: floorUuid }, - relations: ['children', 'spaceType'], - }); - - if ( - !space || - !space.spaceType || - space.spaceType.type !== SpaceType.FLOOR - ) { - throw new BadRequestException('Invalid floor UUID'); - } - const totalCount = await this.spaceRepository.count({ - where: { parent: { uuid: space.uuid } }, - }); - - const children = await this.buildHierarchy( - space, - includeSubSpaces, - page, - pageSize, - ); - - return { - uuid: space.uuid, - name: space.spaceName, - type: space.spaceType.type, - totalCount, - children, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); - } - } - } - - private async buildHierarchy( - space: SpaceEntity, - includeSubSpaces: boolean, - page: number, - pageSize: number, - ): Promise { - const children = await this.spaceRepository.find({ - where: { parent: { uuid: space.uuid } }, - relations: ['spaceType'], - skip: (page - 1) * pageSize, - take: pageSize, - }); - - if (!children || children.length === 0 || !includeSubSpaces) { - return children - .filter( - (child) => - child.spaceType.type !== SpaceType.FLOOR && - child.spaceType.type !== SpaceType.BUILDING && - child.spaceType.type !== SpaceType.COMMUNITY, - ) // Filter remaining floor and building and community types - .map((child) => ({ - uuid: child.uuid, - name: child.spaceName, - type: child.spaceType.type, - })); - } - - const childHierarchies = await Promise.all( - children - .filter( - (child) => - child.spaceType.type !== SpaceType.FLOOR && - child.spaceType.type !== SpaceType.BUILDING && - child.spaceType.type !== SpaceType.COMMUNITY, - ) // Filter remaining floor and building and community types - .map(async (child) => ({ - uuid: child.uuid, - name: child.spaceName, - type: child.spaceType.type, - children: await this.buildHierarchy(child, true, 1, pageSize), - })), - ); - - return childHierarchies; - } - - async getFloorParentByUuid(floorUuid: string): Promise { - try { - const floor = await this.spaceRepository.findOne({ - where: { - uuid: floorUuid, - spaceType: { - type: SpaceType.FLOOR, - }, - }, - relations: ['spaceType', 'parent', 'parent.spaceType'], - }); - if ( - !floor || - !floor.spaceType || - floor.spaceType.type !== SpaceType.FLOOR - ) { - throw new BadRequestException('Invalid floor UUID'); - } - - return { - uuid: floor.uuid, - name: floor.spaceName, - type: floor.spaceType.type, - parent: { - uuid: floor.parent.uuid, - name: floor.parent.spaceName, - type: floor.parent.spaceType.type, - }, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); - } - } - } - - async getFloorsByUserId( - userUuid: string, - ): Promise { - try { - const floors = await this.userSpaceRepository.find({ - relations: ['space', 'space.spaceType'], - where: { - user: { uuid: userUuid }, - space: { spaceType: { type: SpaceType.FLOOR } }, - }, - }); - - if (floors.length === 0) { - throw new HttpException( - 'this user has no floors', - HttpStatus.NOT_FOUND, - ); - } - const spaces = floors.map((floor) => ({ - uuid: floor.space.uuid, - name: floor.space.spaceName, - type: floor.space.spaceType.type, - })); - - return spaces; - } catch (err) { - if (err instanceof HttpException) { - throw err; - } else { - throw new HttpException('user not found', HttpStatus.NOT_FOUND); - } - } - } - async addUserFloor(addUserFloorDto: AddUserFloorDto) { - try { - await this.userSpaceRepository.save({ - user: { uuid: addUserFloorDto.userUuid }, - space: { uuid: addUserFloorDto.floorUuid }, - }); - } catch (err) { - if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) { - throw new HttpException( - 'User already belongs to this floor', - HttpStatus.BAD_REQUEST, - ); - } - throw new HttpException( - err.message || 'Internal Server Error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async renameFloorByUuid( - floorUuid: string, - updateFloorDto: UpdateFloorNameDto, - ): Promise { - try { - const floor = await this.spaceRepository.findOneOrFail({ - where: { uuid: floorUuid }, - relations: ['spaceType'], - }); - - if ( - !floor || - !floor.spaceType || - floor.spaceType.type !== SpaceType.FLOOR - ) { - throw new BadRequestException('Invalid floor UUID'); - } - - await this.spaceRepository.update( - { uuid: floorUuid }, - { spaceName: updateFloorDto.floorName }, - ); - - // Fetch the updated floor - const updatedFloor = await this.spaceRepository.findOneOrFail({ - where: { uuid: floorUuid }, - relations: ['spaceType'], - }); - - return { - uuid: updatedFloor.uuid, - name: updatedFloor.spaceName, - type: updatedFloor.spaceType.type, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Floor not found', HttpStatus.NOT_FOUND); - } - } - } -} diff --git a/src/floor/services/index.ts b/src/floor/services/index.ts deleted file mode 100644 index e6f7946..0000000 --- a/src/floor/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './floor.service'; diff --git a/src/guards/building.type.guard.ts b/src/guards/building.type.guard.ts index dd2870e..29fba8a 100644 --- a/src/guards/building.type.guard.ts +++ b/src/guards/building.type.guard.ts @@ -1,4 +1,3 @@ -import { SpaceType } from '@app/common/constants/space-type.enum'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { Injectable, @@ -40,11 +39,7 @@ export class CheckBuildingTypeGuard implements CanActivate { where: { uuid: buildingUuid }, relations: ['spaceType'], }); - if ( - !buildingData || - !buildingData.spaceType || - buildingData.spaceType.type !== SpaceType.BUILDING - ) { + if (!buildingData) { throw new BadRequestException('Invalid building UUID'); } } diff --git a/src/guards/community.type.guard.ts b/src/guards/community.type.guard.ts index 9dc5d01..f001978 100644 --- a/src/guards/community.type.guard.ts +++ b/src/guards/community.type.guard.ts @@ -6,7 +6,6 @@ import { } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException } from '@nestjs/common'; -import { SpaceType } from '@app/common/constants/space-type.enum'; @Injectable() export class CheckCommunityTypeGuard implements CanActivate { @@ -41,11 +40,7 @@ export class CheckCommunityTypeGuard implements CanActivate { relations: ['spaceType'], }); - if ( - !communityData || - !communityData.spaceType || - communityData.spaceType.type !== SpaceType.COMMUNITY - ) { + if (!communityData) { throw new BadRequestException('Invalid community UUID'); } } diff --git a/src/guards/floor.type.guard.ts b/src/guards/floor.type.guard.ts index 3e6b875..b7b7215 100644 --- a/src/guards/floor.type.guard.ts +++ b/src/guards/floor.type.guard.ts @@ -1,4 +1,3 @@ -import { SpaceType } from '@app/common/constants/space-type.enum'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { Injectable, @@ -37,14 +36,9 @@ export class CheckFloorTypeGuard implements CanActivate { async checkFloorIsFloorType(floorUuid: string) { const floorData = await this.spaceRepository.findOne({ where: { uuid: floorUuid }, - relations: ['spaceType'], }); - if ( - !floorData || - !floorData.spaceType || - floorData.spaceType.type !== SpaceType.FLOOR - ) { + if (!floorData) { throw new BadRequestException('Invalid floor UUID'); } } diff --git a/src/guards/room.guard.ts b/src/guards/room.guard.ts index bd63520..e8d0550 100644 --- a/src/guards/room.guard.ts +++ b/src/guards/room.guard.ts @@ -8,7 +8,6 @@ import { import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException, NotFoundException } from '@nestjs/common'; import { DeviceRepository } from '@app/common/modules/device/repositories'; -import { SpaceType } from '@app/common/constants/space-type.enum'; @Injectable() export class CheckRoomGuard implements CanActivate { @@ -43,9 +42,6 @@ export class CheckRoomGuard implements CanActivate { const room = await this.spaceRepository.findOne({ where: { uuid: roomUuid, - spaceType: { - type: SpaceType.ROOM, - }, }, }); if (!room) { diff --git a/src/guards/unit.type.guard.ts b/src/guards/unit.type.guard.ts index a753756..d1fa50b 100644 --- a/src/guards/unit.type.guard.ts +++ b/src/guards/unit.type.guard.ts @@ -1,4 +1,3 @@ -import { SpaceType } from '@app/common/constants/space-type.enum'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { Injectable, @@ -37,14 +36,9 @@ export class CheckUnitTypeGuard implements CanActivate { async checkFloorIsFloorType(unitUuid: string) { const unitData = await this.spaceRepository.findOne({ where: { uuid: unitUuid }, - relations: ['spaceType'], }); - if ( - !unitData || - !unitData.spaceType || - unitData.spaceType.type !== SpaceType.UNIT - ) { + if (!unitData) { throw new BadRequestException('Invalid unit UUID'); } } diff --git a/src/guards/user.building.guard.ts b/src/guards/user.building.guard.ts index 3f74500..aa23d89 100644 --- a/src/guards/user.building.guard.ts +++ b/src/guards/user.building.guard.ts @@ -7,7 +7,6 @@ import { import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException, NotFoundException } from '@nestjs/common'; import { UserRepository } from '@app/common/modules/user/repositories'; -import { SpaceType } from '@app/common/constants/space-type.enum'; @Injectable() export class CheckUserBuildingGuard implements CanActivate { @@ -44,7 +43,7 @@ export class CheckUserBuildingGuard implements CanActivate { private async checkBuildingIsFound(spaceUuid: string) { const spaceData = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid, spaceType: { type: SpaceType.BUILDING } }, + where: { uuid: spaceUuid }, relations: ['spaceType'], }); if (!spaceData) { diff --git a/src/guards/user.community.guard.ts b/src/guards/user.community.guard.ts index e8dea71..d3c1e5a 100644 --- a/src/guards/user.community.guard.ts +++ b/src/guards/user.community.guard.ts @@ -7,7 +7,6 @@ import { import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException, NotFoundException } from '@nestjs/common'; import { UserRepository } from '@app/common/modules/user/repositories'; -import { SpaceType } from '@app/common/constants/space-type.enum'; @Injectable() export class CheckUserCommunityGuard implements CanActivate { @@ -44,8 +43,7 @@ export class CheckUserCommunityGuard implements CanActivate { private async checkCommunityIsFound(spaceUuid: string) { const spaceData = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid, spaceType: { type: SpaceType.COMMUNITY } }, - relations: ['spaceType'], + where: { uuid: spaceUuid }, }); if (!spaceData) { throw new NotFoundException('Community not found'); diff --git a/src/guards/user.floor.guard.ts b/src/guards/user.floor.guard.ts index 6faa520..3c6e45e 100644 --- a/src/guards/user.floor.guard.ts +++ b/src/guards/user.floor.guard.ts @@ -44,8 +44,7 @@ export class CheckUserFloorGuard implements CanActivate { private async checkFloorIsFound(spaceUuid: string) { const spaceData = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid, spaceType: { type: SpaceType.FLOOR } }, - relations: ['spaceType'], + where: { uuid: spaceUuid }, }); if (!spaceData) { throw new NotFoundException('Floor not found'); diff --git a/src/guards/user.room.guard.ts b/src/guards/user.room.guard.ts index 49c77b8..9f38712 100644 --- a/src/guards/user.room.guard.ts +++ b/src/guards/user.room.guard.ts @@ -7,7 +7,6 @@ import { import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException, NotFoundException } from '@nestjs/common'; import { UserRepository } from '@app/common/modules/user/repositories'; -import { SpaceType } from '@app/common/constants/space-type.enum'; @Injectable() export class CheckUserRoomGuard implements CanActivate { @@ -44,7 +43,7 @@ export class CheckUserRoomGuard implements CanActivate { private async checkRoomIsFound(spaceUuid: string) { const spaceData = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid, spaceType: { type: SpaceType.ROOM } }, + where: { uuid: spaceUuid }, relations: ['spaceType'], }); if (!spaceData) { diff --git a/src/guards/user.unit.guard.ts b/src/guards/user.unit.guard.ts index eb60a27..f5f1bfb 100644 --- a/src/guards/user.unit.guard.ts +++ b/src/guards/user.unit.guard.ts @@ -7,7 +7,6 @@ import { import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException, NotFoundException } from '@nestjs/common'; import { UserRepository } from '@app/common/modules/user/repositories'; -import { SpaceType } from '@app/common/constants/space-type.enum'; @Injectable() export class CheckUserUnitGuard implements CanActivate { @@ -44,7 +43,7 @@ export class CheckUserUnitGuard implements CanActivate { private async checkUnitIsFound(spaceUuid: string) { const spaceData = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid, spaceType: { type: SpaceType.UNIT } }, + where: { uuid: spaceUuid }, relations: ['spaceType'], }); if (!spaceData) { diff --git a/src/room/controllers/index.ts b/src/room/controllers/index.ts deleted file mode 100644 index 4225d61..0000000 --- a/src/room/controllers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './room.controller'; diff --git a/src/room/controllers/room.controller.ts b/src/room/controllers/room.controller.ts deleted file mode 100644 index e3eb687..0000000 --- a/src/room/controllers/room.controller.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { RoomService } from '../services/room.service'; -import { - Body, - Controller, - Get, - HttpStatus, - Param, - Post, - Put, - UseGuards, -} from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; -import { AddRoomDto, AddUserRoomDto } from '../dtos/add.room.dto'; -import { UpdateRoomNameDto } from '../dtos/update.room.dto'; -import { CheckUnitTypeGuard } from 'src/guards/unit.type.guard'; -import { CheckUserRoomGuard } from 'src/guards/user.room.guard'; -import { AdminRoleGuard } from 'src/guards/admin.role.guard'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; -import { RoomPermissionGuard } from 'src/guards/room.permission.guard'; -import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; -import { SpaceType } from '@app/common/constants/space-type.enum'; - -@ApiTags('Room Module') -@Controller({ - version: EnableDisableStatusEnum.ENABLED, - path: SpaceType.ROOM, -}) -export class RoomController { - constructor(private readonly roomService: RoomService) {} - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckUnitTypeGuard) - @Post() - async addRoom(@Body() addRoomDto: AddRoomDto) { - const room = await this.roomService.addRoom(addRoomDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Room added successfully', - data: room, - }; - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, RoomPermissionGuard) - @Get(':roomUuid') - async getRoomByUuid(@Param('roomUuid') roomUuid: string) { - const room = await this.roomService.getRoomByUuid(roomUuid); - return room; - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, RoomPermissionGuard) - @Get('parent/:roomUuid') - async getRoomParentByUuid(@Param('roomUuid') roomUuid: string) { - const room = await this.roomService.getRoomParentByUuid(roomUuid); - return room; - } - @ApiBearerAuth() - @UseGuards(AdminRoleGuard, CheckUserRoomGuard) - @Post('user') - async addUserRoom(@Body() addUserRoomDto: AddUserRoomDto) { - await this.roomService.addUserRoom(addUserRoomDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'user room added successfully', - }; - } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get('user/:userUuid') - async getRoomsByUserId(@Param('userUuid') userUuid: string) { - return await this.roomService.getRoomsByUserId(userUuid); - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, RoomPermissionGuard) - @Put(':roomUuid') - async renameRoomByUuid( - @Param('roomUuid') roomUuid: string, - @Body() updateRoomNameDto: UpdateRoomNameDto, - ) { - const room = await this.roomService.renameRoomByUuid( - roomUuid, - updateRoomNameDto, - ); - return room; - } -} diff --git a/src/room/dtos/add.room.dto.ts b/src/room/dtos/add.room.dto.ts deleted file mode 100644 index 2718a29..0000000 --- a/src/room/dtos/add.room.dto.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class AddRoomDto { - @ApiProperty({ - description: 'roomName', - required: true, - }) - @IsString() - @IsNotEmpty() - public roomName: string; - - @ApiProperty({ - description: 'unitUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public unitUuid: string; - constructor(dto: Partial) { - Object.assign(this, dto); - } -} -export class AddUserRoomDto { - @ApiProperty({ - description: 'roomUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public roomUuid: string; - @ApiProperty({ - description: 'userUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public userUuid: string; - constructor(dto: Partial) { - Object.assign(this, dto); - } -} diff --git a/src/room/dtos/index.ts b/src/room/dtos/index.ts deleted file mode 100644 index a510b75..0000000 --- a/src/room/dtos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './add.room.dto'; diff --git a/src/room/dtos/update.room.dto.ts b/src/room/dtos/update.room.dto.ts deleted file mode 100644 index 8f54092..0000000 --- a/src/room/dtos/update.room.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class UpdateRoomNameDto { - @ApiProperty({ - description: 'roomName', - required: true, - }) - @IsString() - @IsNotEmpty() - public roomName: string; - - constructor(dto: Partial) { - Object.assign(this, dto); - } -} diff --git a/src/room/interface/room.interface.ts b/src/room/interface/room.interface.ts deleted file mode 100644 index 49473a3..0000000 --- a/src/room/interface/room.interface.ts +++ /dev/null @@ -1,24 +0,0 @@ -export interface GetRoomByUuidInterface { - uuid: string; - createdAt: Date; - updatedAt: Date; - name: string; - type: string; -} - -export interface RoomParentInterface { - uuid: string; - name: string; - type: string; - parent?: RoomParentInterface; -} -export interface RenameRoomByUuidInterface { - uuid: string; - name: string; - type: string; -} -export interface GetRoomByUserUuidInterface { - uuid: string; - name: string; - type: string; -} diff --git a/src/room/room.module.ts b/src/room/room.module.ts deleted file mode 100644 index 4a07d1a..0000000 --- a/src/room/room.module.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Module } from '@nestjs/common'; -import { RoomService } from './services/room.service'; -import { RoomController } from './controllers/room.controller'; -import { ConfigModule } from '@nestjs/config'; -import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { SpaceTypeRepository } from '@app/common/modules/space/repositories'; -import { UserSpaceRepository } from '@app/common/modules/user/repositories'; -import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; -import { UserRepository } from '@app/common/modules/user/repositories'; - -@Module({ - imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], - controllers: [RoomController], - providers: [ - RoomService, - SpaceRepository, - SpaceTypeRepository, - UserSpaceRepository, - UserRepository, - ], - exports: [RoomService], -}) -export class RoomModule {} diff --git a/src/room/services/index.ts b/src/room/services/index.ts deleted file mode 100644 index 4f45e9a..0000000 --- a/src/room/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './room.service'; diff --git a/src/room/services/room.service.ts b/src/room/services/room.service.ts deleted file mode 100644 index 340df0f..0000000 --- a/src/room/services/room.service.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { SpaceTypeRepository } from '../../../libs/common/src/modules/space/repositories/space.repository'; -import { - Injectable, - HttpException, - HttpStatus, - BadRequestException, -} from '@nestjs/common'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { AddRoomDto, AddUserRoomDto } from '../dtos'; -import { - RoomParentInterface, - GetRoomByUuidInterface, - RenameRoomByUuidInterface, - GetRoomByUserUuidInterface, -} from '../interface/room.interface'; -import { UpdateRoomNameDto } from '../dtos/update.room.dto'; -import { UserSpaceRepository } from '@app/common/modules/user/repositories'; -import { SpaceType } from '@app/common/constants/space-type.enum'; -import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; - -@Injectable() -export class RoomService { - constructor( - private readonly spaceRepository: SpaceRepository, - private readonly spaceTypeRepository: SpaceTypeRepository, - private readonly userSpaceRepository: UserSpaceRepository, - ) {} - - async addRoom(addRoomDto: AddRoomDto) { - try { - const spaceType = await this.spaceTypeRepository.findOne({ - where: { - type: SpaceType.ROOM, - }, - }); - - const room = await this.spaceRepository.save({ - spaceName: addRoomDto.roomName, - parent: { uuid: addRoomDto.unitUuid }, - spaceType: { uuid: spaceType.uuid }, - }); - return room; - } catch (err) { - throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR); - } - } - - async getRoomByUuid(roomUuid: string): Promise { - try { - const room = await this.spaceRepository.findOne({ - where: { - uuid: roomUuid, - spaceType: { - type: SpaceType.ROOM, - }, - }, - relations: ['spaceType'], - }); - if (!room || !room.spaceType || room.spaceType.type !== SpaceType.ROOM) { - throw new BadRequestException('Invalid room UUID'); - } - - return { - uuid: room.uuid, - createdAt: room.createdAt, - updatedAt: room.updatedAt, - name: room.spaceName, - type: room.spaceType.type, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Room not found', HttpStatus.NOT_FOUND); - } - } - } - - async getRoomParentByUuid(roomUuid: string): Promise { - try { - const room = await this.spaceRepository.findOne({ - where: { - uuid: roomUuid, - spaceType: { - type: SpaceType.ROOM, - }, - }, - relations: ['spaceType', 'parent', 'parent.spaceType'], - }); - if (!room || !room.spaceType || room.spaceType.type !== SpaceType.ROOM) { - throw new BadRequestException('Invalid room UUID'); - } - - return { - uuid: room.uuid, - name: room.spaceName, - type: room.spaceType.type, - parent: { - uuid: room.parent.uuid, - name: room.parent.spaceName, - type: room.parent.spaceType.type, - }, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Room not found', HttpStatus.NOT_FOUND); - } - } - } - - async getRoomsByUserId( - userUuid: string, - ): Promise { - try { - const rooms = await this.userSpaceRepository.find({ - relations: ['space', 'space.spaceType'], - where: { - user: { uuid: userUuid }, - space: { spaceType: { type: SpaceType.ROOM } }, - }, - }); - - if (rooms.length === 0) { - throw new HttpException('this user has no rooms', HttpStatus.NOT_FOUND); - } - const spaces = rooms.map((room) => ({ - uuid: room.space.uuid, - name: room.space.spaceName, - type: room.space.spaceType.type, - })); - - return spaces; - } catch (err) { - if (err instanceof HttpException) { - throw err; - } else { - throw new HttpException('user not found', HttpStatus.NOT_FOUND); - } - } - } - async addUserRoom(addUserRoomDto: AddUserRoomDto) { - try { - await this.userSpaceRepository.save({ - user: { uuid: addUserRoomDto.userUuid }, - space: { uuid: addUserRoomDto.roomUuid }, - }); - } catch (err) { - if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) { - throw new HttpException( - 'User already belongs to this room', - HttpStatus.BAD_REQUEST, - ); - } - throw new HttpException( - err.message || 'Internal Server Error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async renameRoomByUuid( - roomUuid: string, - updateRoomNameDto: UpdateRoomNameDto, - ): Promise { - try { - const room = await this.spaceRepository.findOneOrFail({ - where: { uuid: roomUuid }, - relations: ['spaceType'], - }); - - if (!room || !room.spaceType || room.spaceType.type !== SpaceType.ROOM) { - throw new BadRequestException('Invalid room UUID'); - } - - await this.spaceRepository.update( - { uuid: roomUuid }, - { spaceName: updateRoomNameDto.roomName }, - ); - - // Fetch the updated room - const updateRoom = await this.spaceRepository.findOneOrFail({ - where: { uuid: roomUuid }, - relations: ['spaceType'], - }); - - return { - uuid: updateRoom.uuid, - name: updateRoom.spaceName, - type: updateRoom.spaceType.type, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Room not found', HttpStatus.NOT_FOUND); - } - } - } -} diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 30e53aa..5615011 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -6,7 +6,6 @@ import { } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddSceneTapToRunDto, UpdateSceneTapToRunDto } from '../dtos'; -import { GetUnitByUuidInterface } from 'src/unit/interface/unit.interface'; import { ConfigService } from '@nestjs/config'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { convertKeysToSnakeCase } from '@app/common/helper/snakeCaseConverter'; @@ -18,7 +17,6 @@ import { SceneDetailsResult, } from '../interface/scene.interface'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; -import { SpaceType } from '@app/common/constants/space-type.enum'; import { ActionExecutorEnum } from '@app/common/constants/automation.enum'; @Injectable() @@ -104,18 +102,14 @@ export class SceneService { } } } - async getUnitByUuid(unitUuid: string): Promise { + async getUnitByUuid(unitUuid: string) { try { const unit = await this.spaceRepository.findOne({ where: { uuid: unitUuid, - spaceType: { - type: SpaceType.UNIT, - }, }, - relations: ['spaceType'], }); - if (!unit || !unit.spaceType || unit.spaceType.type !== SpaceType.UNIT) { + if (!unit) { throw new BadRequestException('Invalid unit UUID'); } return { @@ -123,7 +117,6 @@ export class SceneService { createdAt: unit.createdAt, updatedAt: unit.updatedAt, name: unit.spaceName, - type: unit.spaceType.type, spaceTuyaUuid: unit.spaceTuyaUuid, }; } catch (err) { diff --git a/src/unit/controllers/index.ts b/src/unit/controllers/index.ts deleted file mode 100644 index c8d7271..0000000 --- a/src/unit/controllers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './unit.controller'; diff --git a/src/unit/controllers/unit.controller.ts b/src/unit/controllers/unit.controller.ts deleted file mode 100644 index 8dbede7..0000000 --- a/src/unit/controllers/unit.controller.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { UnitService } from '../services/unit.service'; -import { - Body, - Controller, - Get, - HttpStatus, - Param, - Post, - Put, - Query, - UseGuards, -} from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; -import { - AddUnitDto, - AddUserUnitDto, - AddUserUnitUsingCodeDto, -} from '../dtos/add.unit.dto'; -import { GetUnitChildDto } from '../dtos/get.unit.dto'; -import { UpdateUnitNameDto } from '../dtos/update.unit.dto'; -import { CheckFloorTypeGuard } from 'src/guards/floor.type.guard'; -import { CheckUserUnitGuard } from 'src/guards/user.unit.guard'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; -import { UnitPermissionGuard } from 'src/guards/unit.permission.guard'; -import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; -import { SpaceType } from '@app/common/constants/space-type.enum'; - -@ApiTags('Unit Module') -@Controller({ - version: EnableDisableStatusEnum.ENABLED, - path: SpaceType.UNIT, -}) -export class UnitController { - constructor(private readonly unitService: UnitService) {} - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckFloorTypeGuard) - @Post() - async addUnit(@Body() addUnitDto: AddUnitDto) { - const unit = await this.unitService.addUnit(addUnitDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Unit added successfully', - data: unit, - }; - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, UnitPermissionGuard) - @Get(':unitUuid') - async getUnitByUuid(@Param('unitUuid') unitUuid: string) { - const unit = await this.unitService.getUnitByUuid(unitUuid); - return unit; - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, UnitPermissionGuard) - @Get('child/:unitUuid') - async getUnitChildByUuid( - @Param('unitUuid') unitUuid: string, - @Query() query: GetUnitChildDto, - ) { - const unit = await this.unitService.getUnitChildByUuid(unitUuid, query); - return unit; - } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, UnitPermissionGuard) - @Get('parent/:unitUuid') - async getUnitParentByUuid(@Param('unitUuid') unitUuid: string) { - const unit = await this.unitService.getUnitParentByUuid(unitUuid); - return unit; - } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckUserUnitGuard) - @Post('user') - async addUserUnit(@Body() addUserUnitDto: AddUserUnitDto) { - await this.unitService.addUserUnit(addUserUnitDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'user unit added successfully', - }; - } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get('user/:userUuid') - async getUnitsByUserId(@Param('userUuid') userUuid: string) { - return await this.unitService.getUnitsByUserId(userUuid); - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, UnitPermissionGuard) - @Put(':unitUuid') - async renameUnitByUuid( - @Param('unitUuid') unitUuid: string, - @Body() updateUnitNameDto: UpdateUnitNameDto, - ) { - const unit = await this.unitService.renameUnitByUuid( - unitUuid, - updateUnitNameDto, - ); - return unit; - } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, UnitPermissionGuard) - @Get(':unitUuid/invitation-code') - async getUnitInvitationCode(@Param('unitUuid') unitUuid: string) { - const unit = await this.unitService.getUnitInvitationCode(unitUuid); - return unit; - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Post('user/verify-code') - async verifyCodeAndAddUserUnit( - @Body() addUserUnitUsingCodeDto: AddUserUnitUsingCodeDto, - ) { - await this.unitService.verifyCodeAndAddUserUnit(addUserUnitUsingCodeDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'user unit added successfully', - }; - } -} diff --git a/src/unit/dtos/add.unit.dto.ts b/src/unit/dtos/add.unit.dto.ts deleted file mode 100644 index 9896c37..0000000 --- a/src/unit/dtos/add.unit.dto.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class AddUnitDto { - @ApiProperty({ - description: 'unitName', - required: true, - }) - @IsString() - @IsNotEmpty() - public unitName: string; - - @ApiProperty({ - description: 'floorUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public floorUuid: string; - constructor(dto: Partial) { - Object.assign(this, dto); - } -} -export class AddUserUnitDto { - @ApiProperty({ - description: 'unitUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public unitUuid: string; - @ApiProperty({ - description: 'userUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public userUuid: string; - constructor(dto: Partial) { - Object.assign(this, dto); - } -} -export class AddUserUnitUsingCodeDto { - @ApiProperty({ - description: 'userUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public userUuid: string; - @ApiProperty({ - description: 'inviteCode', - required: true, - }) - @IsString() - @IsNotEmpty() - public inviteCode: string; - constructor(dto: Partial) { - Object.assign(this, dto); - } -} diff --git a/src/unit/dtos/get.unit.dto.ts b/src/unit/dtos/get.unit.dto.ts deleted file mode 100644 index 2fae52a..0000000 --- a/src/unit/dtos/get.unit.dto.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsInt, IsNotEmpty, IsString, Min } from 'class-validator'; - -export class GetUnitDto { - @ApiProperty({ - description: 'unitUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public unitUuid: string; -} - -export class GetUnitChildDto { - @ApiProperty({ example: 1, description: 'Page number', required: true }) - @IsInt({ message: 'Page must be a number' }) - @Min(1, { message: 'Page must not be less than 1' }) - @IsNotEmpty() - public page: number; - - @ApiProperty({ - example: 10, - description: 'Number of items per page', - required: true, - }) - @IsInt({ message: 'Page size must be a number' }) - @Min(1, { message: 'Page size must not be less than 1' }) - @IsNotEmpty() - public pageSize: number; -} -export class GetUnitByUserIdDto { - @ApiProperty({ - description: 'userUuid', - required: true, - }) - @IsString() - @IsNotEmpty() - public userUuid: string; -} diff --git a/src/unit/dtos/index.ts b/src/unit/dtos/index.ts deleted file mode 100644 index 970d13d..0000000 --- a/src/unit/dtos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './add.unit.dto'; diff --git a/src/unit/dtos/update.unit.dto.ts b/src/unit/dtos/update.unit.dto.ts deleted file mode 100644 index 2d69902..0000000 --- a/src/unit/dtos/update.unit.dto.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class UpdateUnitNameDto { - @ApiProperty({ - description: 'unitName', - required: true, - }) - @IsString() - @IsNotEmpty() - public unitName: string; - - constructor(dto: Partial) { - Object.assign(this, dto); - } -} diff --git a/src/unit/interface/unit.interface.ts b/src/unit/interface/unit.interface.ts deleted file mode 100644 index 635f38e..0000000 --- a/src/unit/interface/unit.interface.ts +++ /dev/null @@ -1,37 +0,0 @@ -export interface GetUnitByUuidInterface { - uuid: string; - createdAt: Date; - updatedAt: Date; - name: string; - type: string; - spaceTuyaUuid: string; -} - -export interface UnitChildInterface { - uuid: string; - name: string; - type: string; - totalCount?: number; - children?: UnitChildInterface[]; -} -export interface UnitParentInterface { - uuid: string; - name: string; - type: string; - parent?: UnitParentInterface; -} -export interface RenameUnitByUuidInterface { - uuid: string; - name: string; - type: string; -} -export interface GetUnitByUserUuidInterface { - uuid: string; - name: string; - type: string; -} -export interface addTuyaSpaceInterface { - success: boolean; - result: string; - msg: string; -} diff --git a/src/unit/services/index.ts b/src/unit/services/index.ts deleted file mode 100644 index 0540c40..0000000 --- a/src/unit/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './unit.service'; diff --git a/src/unit/services/unit.service.ts b/src/unit/services/unit.service.ts deleted file mode 100644 index 826c044..0000000 --- a/src/unit/services/unit.service.ts +++ /dev/null @@ -1,453 +0,0 @@ -import { GetUnitChildDto } from '../dtos/get.unit.dto'; -import { SpaceTypeRepository } from '../../../libs/common/src/modules/space/repositories/space.repository'; -import { - Injectable, - HttpException, - HttpStatus, - BadRequestException, -} from '@nestjs/common'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { AddUnitDto, AddUserUnitDto, AddUserUnitUsingCodeDto } from '../dtos'; -import { - UnitChildInterface, - UnitParentInterface, - GetUnitByUuidInterface, - RenameUnitByUuidInterface, - GetUnitByUserUuidInterface, - addTuyaSpaceInterface, -} from '../interface/unit.interface'; -import { SpaceEntity } from '@app/common/modules/space/entities'; -import { UpdateUnitNameDto } from '../dtos/update.unit.dto'; -import { UserSpaceRepository } from '@app/common/modules/user/repositories'; -import { generateRandomString } from '@app/common/helper/randomString'; -import { UserDevicePermissionService } from 'src/user-device-permission/services'; -import { PermissionType } from '@app/common/constants/permission-type.enum'; -import { TuyaContext } from '@tuya/tuya-connector-nodejs'; -import { ConfigService } from '@nestjs/config'; -import { SpaceType } from '@app/common/constants/space-type.enum'; -import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; - -@Injectable() -export class UnitService { - private tuya: TuyaContext; - constructor( - private readonly configService: ConfigService, - private readonly spaceRepository: SpaceRepository, - private readonly spaceTypeRepository: SpaceTypeRepository, - private readonly userSpaceRepository: UserSpaceRepository, - private readonly userDevicePermissionService: UserDevicePermissionService, - ) { - const accessKey = this.configService.get('auth-config.ACCESS_KEY'); - const secretKey = this.configService.get('auth-config.SECRET_KEY'); - const tuyaEuUrl = this.configService.get('tuya-config.TUYA_EU_URL'); - - this.tuya = new TuyaContext({ - baseUrl: tuyaEuUrl, - accessKey, - secretKey, - }); - } - - async addUnit(addUnitDto: AddUnitDto) { - try { - const spaceType = await this.spaceTypeRepository.findOne({ - where: { - type: SpaceType.UNIT, - }, - }); - const tuyaUnit = await this.addUnitTuya(addUnitDto.unitName); - if (!tuyaUnit.result) { - throw new HttpException('Error creating unit', HttpStatus.BAD_REQUEST); - } - - const unit = await this.spaceRepository.save({ - spaceName: addUnitDto.unitName, - parent: { uuid: addUnitDto.floorUuid }, - spaceType: { uuid: spaceType.uuid }, - spaceTuyaUuid: tuyaUnit.result, - }); - return unit; - } catch (err) { - throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR); - } - } - async addUnitTuya(unitName: string): Promise { - try { - const path = `/v2.0/cloud/space/creation`; - const response = await this.tuya.request({ - method: 'POST', - path, - body: { - name: unitName, - }, - }); - - return response as addTuyaSpaceInterface; - } catch (error) { - throw new HttpException( - 'Error creating unit from Tuya', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - async getUnitByUuid(unitUuid: string): Promise { - try { - const unit = await this.spaceRepository.findOne({ - where: { - uuid: unitUuid, - spaceType: { - type: SpaceType.UNIT, - }, - }, - relations: ['spaceType'], - }); - if (!unit || !unit.spaceType || unit.spaceType.type !== SpaceType.UNIT) { - throw new BadRequestException('Invalid unit UUID'); - } - return { - uuid: unit.uuid, - createdAt: unit.createdAt, - updatedAt: unit.updatedAt, - name: unit.spaceName, - type: unit.spaceType.type, - spaceTuyaUuid: unit.spaceTuyaUuid, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); - } - } - } - async getUnitChildByUuid( - unitUuid: string, - getUnitChildDto: GetUnitChildDto, - ): Promise { - try { - const { page, pageSize } = getUnitChildDto; - - const space = await this.spaceRepository.findOneOrFail({ - where: { uuid: unitUuid }, - relations: ['children', 'spaceType'], - }); - - if ( - !space || - !space.spaceType || - space.spaceType.type !== SpaceType.UNIT - ) { - throw new BadRequestException('Invalid unit UUID'); - } - - const totalCount = await this.spaceRepository.count({ - where: { parent: { uuid: space.uuid } }, - }); - - const children = await this.buildHierarchy(space, false, page, pageSize); - - return { - uuid: space.uuid, - name: space.spaceName, - type: space.spaceType.type, - totalCount, - children, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); - } - } - } - - private async buildHierarchy( - space: SpaceEntity, - includeSubSpaces: boolean, - page: number, - pageSize: number, - ): Promise { - const children = await this.spaceRepository.find({ - where: { parent: { uuid: space.uuid } }, - relations: ['spaceType'], - skip: (page - 1) * pageSize, - take: pageSize, - }); - - if (!children || children.length === 0 || !includeSubSpaces) { - return children - .filter( - (child) => - child.spaceType.type !== SpaceType.UNIT && - child.spaceType.type !== SpaceType.FLOOR && - child.spaceType.type !== SpaceType.COMMUNITY && - child.spaceType.type !== SpaceType.UNIT, - ) // Filter remaining unit and floor and community and unit types - .map((child) => ({ - uuid: child.uuid, - name: child.spaceName, - type: child.spaceType.type, - })); - } - - const childHierarchies = await Promise.all( - children - .filter( - (child) => - child.spaceType.type !== SpaceType.UNIT && - child.spaceType.type !== SpaceType.FLOOR && - child.spaceType.type !== SpaceType.COMMUNITY && - child.spaceType.type !== SpaceType.UNIT, - ) // Filter remaining unit and floor and community and unit types - .map(async (child) => ({ - uuid: child.uuid, - name: child.spaceName, - type: child.spaceType.type, - children: await this.buildHierarchy(child, true, 1, pageSize), - })), - ); - - return childHierarchies; - } - - async getUnitParentByUuid(unitUuid: string): Promise { - try { - const unit = await this.spaceRepository.findOne({ - where: { - uuid: unitUuid, - spaceType: { - type: SpaceType.UNIT, - }, - }, - relations: ['spaceType', 'parent', 'parent.spaceType'], - }); - if (!unit || !unit.spaceType || unit.spaceType.type !== SpaceType.UNIT) { - throw new BadRequestException('Invalid unit UUID'); - } - return { - uuid: unit.uuid, - name: unit.spaceName, - type: unit.spaceType.type, - parent: { - uuid: unit.parent.uuid, - name: unit.parent.spaceName, - type: unit.parent.spaceType.type, - }, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); - } - } - } - async getUnitsByUserId( - userUuid: string, - ): Promise { - try { - const units = await this.userSpaceRepository.find({ - relations: ['space', 'space.spaceType'], - where: { - user: { uuid: userUuid }, - space: { spaceType: { type: SpaceType.UNIT } }, - }, - }); - - if (units.length === 0) { - throw new HttpException('this user has no units', HttpStatus.NOT_FOUND); - } - const spaces = units.map((unit) => ({ - uuid: unit.space.uuid, - name: unit.space.spaceName, - type: unit.space.spaceType.type, - })); - - return spaces; - } catch (err) { - if (err instanceof HttpException) { - throw err; - } else { - throw new HttpException('user not found', HttpStatus.NOT_FOUND); - } - } - } - - async addUserUnit(addUserUnitDto: AddUserUnitDto) { - try { - return await this.userSpaceRepository.save({ - user: { uuid: addUserUnitDto.userUuid }, - space: { uuid: addUserUnitDto.unitUuid }, - }); - } catch (err) { - if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) { - throw new HttpException( - 'User already belongs to this unit', - HttpStatus.BAD_REQUEST, - ); - } - throw new HttpException( - err.message || 'Internal Server Error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - async renameUnitByUuid( - unitUuid: string, - updateUnitNameDto: UpdateUnitNameDto, - ): Promise { - try { - const unit = await this.spaceRepository.findOneOrFail({ - where: { uuid: unitUuid }, - relations: ['spaceType'], - }); - - if (!unit || !unit.spaceType || unit.spaceType.type !== SpaceType.UNIT) { - throw new BadRequestException('Invalid unit UUID'); - } - - await this.spaceRepository.update( - { uuid: unitUuid }, - { spaceName: updateUnitNameDto.unitName }, - ); - - // Fetch the updated unit - const updatedUnit = await this.spaceRepository.findOneOrFail({ - where: { uuid: unitUuid }, - relations: ['spaceType'], - }); - - return { - uuid: updatedUnit.uuid, - name: updatedUnit.spaceName, - type: updatedUnit.spaceType.type, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); - } - } - } - async getUnitInvitationCode(unitUuid: string): Promise { - try { - // Generate a 6-character random invitation code - const invitationCode = generateRandomString(6); - - // Update the unit with the new invitation code - await this.spaceRepository.update({ uuid: unitUuid }, { invitationCode }); - - // Fetch the updated unit - const updatedUnit = await this.spaceRepository.findOneOrFail({ - where: { uuid: unitUuid }, - relations: ['spaceType'], - }); - - return { - uuid: updatedUnit.uuid, - invitationCode: updatedUnit.invitationCode, - type: updatedUnit.spaceType.type, - }; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; - } else { - throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); - } - } - } - async verifyCodeAndAddUserUnit( - addUserUnitUsingCodeDto: AddUserUnitUsingCodeDto, - ) { - try { - const unit = await this.findUnitByInviteCode( - addUserUnitUsingCodeDto.inviteCode, - ); - - await this.addUserToUnit(addUserUnitUsingCodeDto.userUuid, unit.uuid); - - await this.clearUnitInvitationCode(unit.uuid); - - const deviceUUIDs = await this.getDeviceUUIDsForUnit(unit.uuid); - - await this.addUserPermissionsToDevices( - addUserUnitUsingCodeDto.userUuid, - deviceUUIDs, - ); - } catch (err) { - throw new HttpException( - 'Invalid invitation code', - HttpStatus.BAD_REQUEST, - ); - } - } - - private async findUnitByInviteCode(inviteCode: string): Promise { - const unit = await this.spaceRepository.findOneOrFail({ - where: { - invitationCode: inviteCode, - spaceType: { type: SpaceType.UNIT }, - }, - relations: ['spaceType'], - }); - - return unit; - } - - private async addUserToUnit(userUuid: string, unitUuid: string) { - const user = await this.addUserUnit({ userUuid, unitUuid }); - - if (user.uuid) { - return user; - } else { - throw new HttpException( - 'Failed to add user to unit', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - private async clearUnitInvitationCode(unitUuid: string) { - await this.spaceRepository.update( - { uuid: unitUuid }, - { invitationCode: null }, - ); - } - - private async getDeviceUUIDsForUnit( - unitUuid: string, - ): Promise<{ uuid: string }[]> { - const devices = await this.spaceRepository.find({ - where: { parent: { uuid: unitUuid } }, - relations: ['devicesSpaceEntity', 'devicesSpaceEntity.productDevice'], - }); - - const allDevices = devices.flatMap((space) => space.devicesSpaceEntity); - - return allDevices.map((device) => ({ uuid: device.uuid })); - } - - private async addUserPermissionsToDevices( - userUuid: string, - deviceUUIDs: { uuid: string }[], - ): Promise { - const permissionPromises = deviceUUIDs.map(async (device) => { - try { - await this.userDevicePermissionService.addUserPermission({ - userUuid, - deviceUuid: device.uuid, - permissionType: PermissionType.CONTROLLABLE, - }); - } catch (error) { - console.error( - `Failed to add permission for device ${device.uuid}: ${error.message}`, - ); - } - }); - - await Promise.all(permissionPromises); - } -} diff --git a/src/unit/unit.module.ts b/src/unit/unit.module.ts deleted file mode 100644 index 7ecd965..0000000 --- a/src/unit/unit.module.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Module } from '@nestjs/common'; -import { UnitService } from './services/unit.service'; -import { UnitController } from './controllers/unit.controller'; -import { ConfigModule } from '@nestjs/config'; -import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { SpaceTypeRepository } from '@app/common/modules/space/repositories'; -import { UserSpaceRepository } from '@app/common/modules/user/repositories'; -import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; -import { UserRepository } from '@app/common/modules/user/repositories'; -import { UserDevicePermissionService } from 'src/user-device-permission/services'; -import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; -import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; - -@Module({ - imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], - controllers: [UnitController], - providers: [ - UnitService, - SpaceRepository, - SpaceTypeRepository, - UserSpaceRepository, - UserRepository, - UserDevicePermissionService, - DeviceUserPermissionRepository, - PermissionTypeRepository, - ], - exports: [UnitService], -}) -export class UnitModule {} From a6f87fffc5f930bb4550fd142229a95a2fbfc06b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 22 Oct 2024 11:36:31 +0400 Subject: [PATCH 013/109] added community to all devices --- src/device/services/device.service.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 9963383..bf8edab 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -832,6 +832,7 @@ export class DeviceService { where: { isActive: true }, relations: [ 'spaceDevice.parent', + 'spaceDevice.community', 'productDevice', 'permission', 'permission.permissionType', @@ -886,6 +887,7 @@ export class DeviceService { const spaceHierarchy = await this.getFullSpaceHierarchy( device?.spaceDevice, ); + console.log(device.spaceDevice.community); const orderedHierarchy = spaceHierarchy.reverse(); return { @@ -895,6 +897,10 @@ export class DeviceService { })), productUuid: device.productDevice.uuid, productType: device.productDevice.prodType, + community: { + uuid: device.spaceDevice.community.uuid, + name: device.spaceDevice.community.name, + }, // permissionType: device.permission[0].permissionType.type, /* ...(await this.getDeviceDetailsByDeviceIdTuya( device.deviceTuyaUuid, From 63a98fae55fb321bb183d5657cd0697ae6911b2c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 24 Oct 2024 11:48:42 +0400 Subject: [PATCH 014/109] fixed issue in associating --- libs/common/src/constants/controller-route.ts | 2 +- src/space/controllers/space-user.controller.ts | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 6265818..78aa262 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -116,7 +116,7 @@ export class ControllerRoute { static SPACE_USER = class { public static readonly ROUTE = - '/communities/:communityId/spaces/:spaceId/user'; + '/communities/:communityUuid/spaces/:spaceUuid/user'; static ACTIONS = class { public static readonly ASSOCIATE_SPACE_USER_SUMMARY = 'Associate a user to a space'; diff --git a/src/space/controllers/space-user.controller.ts b/src/space/controllers/space-user.controller.ts index 441c3c6..89fa3a0 100644 --- a/src/space/controllers/space-user.controller.ts +++ b/src/space/controllers/space-user.controller.ts @@ -1,10 +1,10 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; import { Controller, Delete, Param, Post, UseGuards } from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { SpaceUserService } from '../services'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { UserSpaceDto } from '@app/common/modules/user/dtos'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { UserSpaceParam } from '../dtos'; @ApiTags('Space Module') @Controller({ @@ -14,6 +14,7 @@ import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; export class SpaceUserController { constructor(private readonly spaceUserService: SpaceUserService) {} + @ApiBearerAuth() @Post('/:userUuid') @UseGuards(JwtAuthGuard) @ApiOperation({ @@ -23,7 +24,7 @@ export class SpaceUserController { ControllerRoute.SPACE_USER.ACTIONS.ASSOCIATE_SPACE_USER_DESCRIPTION, }) async associateUserToSpace( - @Param() params: UserSpaceDto, + @Param() params: UserSpaceParam, ): Promise { return this.spaceUserService.associateUserToSpace( params.userUuid, @@ -31,6 +32,7 @@ export class SpaceUserController { ); } + @ApiBearerAuth() @Delete('/:userUuid') @UseGuards(JwtAuthGuard) @ApiOperation({ @@ -39,7 +41,7 @@ export class SpaceUserController { ControllerRoute.SPACE_USER.ACTIONS.DISSOCIATE_SPACE_USER_DESCRIPTION, }) async disassociateUserFromSpace( - @Param() params: UserSpaceDto, + @Param() params: UserSpaceParam, ): Promise { return this.spaceUserService.disassociateUserFromSpace( params.userUuid, From 58a060e076771abcd3841008d2bbba9565fa47f7 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 24 Oct 2024 12:22:29 +0400 Subject: [PATCH 015/109] updated list space for a user --- src/space/services/space-user.service.ts | 2 +- src/users/services/user-space.service.ts | 3 +-- src/users/user.module.ts | 7 ++++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/space/services/space-user.service.ts b/src/space/services/space-user.service.ts index 5bfb87b..d4f2e9d 100644 --- a/src/space/services/space-user.service.ts +++ b/src/space/services/space-user.service.ts @@ -51,9 +51,9 @@ export class SpaceUserService { ); } - // Create a new UserSpaceEntity entry to associate the user and the space const userSpace = this.userSpaceRepository.create({ user, space }); + await this.userSpaceRepository.save(userSpace); return new SuccessResponseDto({ data: userSpace, message: `Space ${spaceUuid} has been successfully associated t user ${userUuid}`, diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index 579ff63..3dc565a 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -20,10 +20,9 @@ export class UserSpaceService { ); } - const spaces = userSpaces.map((userSpace) => userSpace.space); return new SuccessResponseDto({ - data: spaces, + data: userSpaces, message: `Spaces for user ${userUuid} retrieved successfully`, }); } diff --git a/src/users/user.module.ts b/src/users/user.module.ts index 1cba860..61fc323 100644 --- a/src/users/user.module.ts +++ b/src/users/user.module.ts @@ -8,14 +8,14 @@ import { } from '@app/common/modules/user/repositories'; import { RegionRepository } from '@app/common/modules/region/repositories'; import { TimeZoneRepository } from '@app/common/modules/timezone/repositories'; -import { UserCommunityController } from './controllers'; +import { UserCommunityController, UserSpaceController } from './controllers'; import { CommunityModule } from 'src/community/community.module'; -import { UserCommunityService } from './services'; +import { UserCommunityService, UserSpaceService } from './services'; import { CommunityRepository } from '@app/common/modules/community/repositories'; @Module({ imports: [ConfigModule, CommunityModule], - controllers: [UserController, UserCommunityController], + controllers: [UserController, UserSpaceController, UserCommunityController], providers: [ UserService, UserRepository, @@ -24,6 +24,7 @@ import { CommunityRepository } from '@app/common/modules/community/repositories' UserSpaceRepository, CommunityRepository, UserCommunityService, + UserSpaceService, ], exports: [UserService], }) From 6d6560b3e05274b4333048c18237064f6e196f85 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 25 Oct 2024 13:59:25 +0400 Subject: [PATCH 016/109] Added subspace entity --- libs/common/src/database/database.module.ts | 3 +- .../modules/device/entities/device.entity.ts | 10 ++++- libs/common/src/modules/space/dtos/index.ts | 1 + .../src/modules/space/dtos/subspace.dto.ts | 29 +++++++++++++++ .../src/modules/space/entities/index.ts | 1 + .../modules/space/entities/space.entity.ts | 6 +++ .../modules/space/entities/subspace.entity.ts | 37 +++++++++++++++++++ .../modules/space/space.repository.module.ts | 6 ++- src/users/services/user-space.service.ts | 3 +- 9 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 libs/common/src/modules/space/dtos/subspace.dto.ts create mode 100644 libs/common/src/modules/space/entities/subspace.entity.ts diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index deaa021..e87b14c 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -8,7 +8,7 @@ import { UserOtpEntity } from '../modules/user/entities'; import { ProductEntity } from '../modules/product/entities'; import { DeviceEntity } from '../modules/device/entities'; import { PermissionTypeEntity } from '../modules/permission/entities'; -import { SpaceEntity } from '../modules/space/entities'; +import { SpaceEntity, SubspaceEntity } from '../modules/space/entities'; import { SpaceTypeEntity } from '../modules/space/entities'; import { UserSpaceEntity } from '../modules/user/entities'; import { DeviceUserPermissionEntity } from '../modules/device/entities'; @@ -44,6 +44,7 @@ import { CommunityEntity } from '../modules/community/entities'; PermissionTypeEntity, CommunityEntity, SpaceEntity, + SubspaceEntity, SpaceTypeEntity, UserSpaceEntity, DeviceUserPermissionEntity, diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index 5a96255..fd32ea3 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -1,7 +1,7 @@ -import { Column, Entity, ManyToOne, OneToMany, Unique, Index } from 'typeorm'; +import { Column, Entity, ManyToOne, OneToMany, Unique, Index, JoinColumn } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceDto, DeviceUserPermissionDto } from '../dtos/device.dto'; -import { SpaceEntity } from '../../space/entities'; +import { SpaceEntity, SubspaceEntity } from '../../space/entities'; import { ProductEntity } from '../../product/entities'; import { UserEntity } from '../../user/entities'; import { DeviceNotificationDto } from '../dtos'; @@ -52,6 +52,12 @@ export class DeviceEntity extends AbstractEntity { }) productDevice: ProductEntity; + @ManyToOne(() => SubspaceEntity, (subspace) => subspace.devices, { + nullable: true, + }) + @JoinColumn({ name: 'subspace_id' }) + subspace: SubspaceEntity; + @Index() @Column({ nullable: false }) uuid: string; diff --git a/libs/common/src/modules/space/dtos/index.ts b/libs/common/src/modules/space/dtos/index.ts index 9144208..fcc0fdd 100644 --- a/libs/common/src/modules/space/dtos/index.ts +++ b/libs/common/src/modules/space/dtos/index.ts @@ -1 +1,2 @@ export * from './space.dto'; +export * from './subspace.dto'; diff --git a/libs/common/src/modules/space/dtos/subspace.dto.ts b/libs/common/src/modules/space/dtos/subspace.dto.ts new file mode 100644 index 0000000..d852345 --- /dev/null +++ b/libs/common/src/modules/space/dtos/subspace.dto.ts @@ -0,0 +1,29 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { SpaceDto } from './space.dto'; +import { DeviceDto } from '../../device/dtos'; + +export class SubspaceDto { + @ApiProperty({ + example: 'd7a44e8a-32d5-4f39-ae2e-013f1245aead', + description: 'UUID of the subspace', + }) + uuid: string; + + @ApiProperty({ + example: 'Meeting Room 1', + description: 'Name of the subspace', + }) + subspaceName: string; + + @ApiProperty({ + type: () => SpaceDto, + description: 'The space to which this subspace belongs', + }) + space: SpaceDto; + + @ApiProperty({ + example: [], + description: 'List of devices assigned to this subspace, if any', + }) + devices: DeviceDto[]; +} diff --git a/libs/common/src/modules/space/entities/index.ts b/libs/common/src/modules/space/entities/index.ts index bce8032..e551c0a 100644 --- a/libs/common/src/modules/space/entities/index.ts +++ b/libs/common/src/modules/space/entities/index.ts @@ -1 +1,2 @@ export * from './space.entity'; +export * from './subspace.entity'; diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 42fea35..f9e8290 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -11,6 +11,7 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { UserSpaceEntity } from '../../user/entities'; import { DeviceEntity } from '../../device/entities'; import { CommunityEntity } from '../../community/entities'; +import { SubspaceEntity } from './subspace.entity'; @Entity({ name: 'space-type' }) export class SpaceTypeEntity extends AbstractEntity { @@ -71,6 +72,11 @@ export class SpaceEntity extends AbstractEntity { @OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.space) userSpaces: UserSpaceEntity[]; + @OneToMany(() => SubspaceEntity, (subspace) => subspace.space, { + nullable: true, + }) + subspaces?: SubspaceEntity[]; + @OneToMany( () => DeviceEntity, (devicesSpaceEntity) => devicesSpaceEntity.spaceDevice, diff --git a/libs/common/src/modules/space/entities/subspace.entity.ts b/libs/common/src/modules/space/entities/subspace.entity.ts new file mode 100644 index 0000000..ab38b00 --- /dev/null +++ b/libs/common/src/modules/space/entities/subspace.entity.ts @@ -0,0 +1,37 @@ +import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { DeviceEntity } from '../../device/entities'; +import { SpaceEntity } from './space.entity'; +import { SubspaceDto } from '../dtos'; + +@Entity({ name: 'subspace' }) +export class SubspaceEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', + nullable: false, + }) + public uuid: string; + + @Column({ + nullable: false, + }) + public subspaceName: string; + + @ManyToOne(() => SpaceEntity, (space) => space.subspaces, { + nullable: false, + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'space_id' }) + space: SpaceEntity; + + @OneToMany(() => DeviceEntity, (device) => device.subspace, { + nullable: true, + }) + devices: DeviceEntity[]; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/space/space.repository.module.ts b/libs/common/src/modules/space/space.repository.module.ts index 9906708..ea3c606 100644 --- a/libs/common/src/modules/space/space.repository.module.ts +++ b/libs/common/src/modules/space/space.repository.module.ts @@ -1,11 +1,13 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { SpaceEntity, SpaceTypeEntity } from './entities'; +import { SpaceEntity, SpaceTypeEntity, SubspaceEntity } from './entities'; @Module({ providers: [], exports: [], controllers: [], - imports: [TypeOrmModule.forFeature([SpaceEntity, SpaceTypeEntity])], + imports: [ + TypeOrmModule.forFeature([SpaceEntity, SpaceTypeEntity, SubspaceEntity]), + ], }) export class SpaceRepositoryModule {} diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index 3dc565a..68e6f00 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -10,7 +10,7 @@ export class UserSpaceService { async getSpacesForUser(userUuid: string): Promise { const userSpaces = await this.userSpaceRepository.find({ where: { user: { uuid: userUuid } }, - relations: ['space'], + relations: ['space', 'space.community'], }); if (!userSpaces || userSpaces.length === 0) { @@ -20,7 +20,6 @@ export class UserSpaceService { ); } - return new SuccessResponseDto({ data: userSpaces, message: `Spaces for user ${userUuid} retrieved successfully`, From 74428b408e5d4ab05c482d9fa35689d1b5df232d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 28 Oct 2024 09:53:47 +0400 Subject: [PATCH 017/109] add subspace controller --- libs/common/src/constants/controller-route.ts | 26 +++ .../services/space.permission.service.ts | 3 - libs/common/src/models/typeOrmCustom.model.ts | 4 +- .../space/repositories/space.repository.ts | 9 +- .../src/util/buildTypeORMIncludeQuery.ts | 3 + .../src/util/buildTypeORMWhereClause.ts | 33 ++- src/space/controllers/index.ts | 1 + src/space/controllers/subspace.controller.ts | 91 ++++++++ src/space/dtos/index.ts | 1 + src/space/dtos/subspace/add.subspace.dto.ts | 12 ++ src/space/dtos/subspace/get.subspace.param.ts | 12 ++ src/space/dtos/subspace/index.ts | 2 + src/space/services/index.ts | 1 + src/space/services/subspace.service.ts | 203 ++++++++++++++++++ src/space/space.module.ts | 18 +- 15 files changed, 401 insertions(+), 18 deletions(-) create mode 100644 src/space/controllers/subspace.controller.ts create mode 100644 src/space/dtos/subspace/add.subspace.dto.ts create mode 100644 src/space/dtos/subspace/get.subspace.param.ts create mode 100644 src/space/dtos/subspace/index.ts create mode 100644 src/space/services/subspace.service.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 78aa262..3095819 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -129,4 +129,30 @@ export class ControllerRoute { 'Disassociates a user from a space by removing the existing association.'; }; }; + + static SUBSPACE = class { + public static readonly ROUTE = + '/communities/:communityUuid/spaces/:spaceUuid/subspaces'; + static ACTIONS = class { + public static readonly CREATE_SUBSPACE_SUMMARY = 'Create Subspace'; + public static readonly CREATE_SUBSPACE_DESCRIPTION = + 'Creates a new subspace within a specific space and community.'; + + public static readonly LIST_SUBSPACES_SUMMARY = 'List Subspaces'; + public static readonly LIST_SUBSPACES_DESCRIPTION = + 'Retrieves a list of subspaces within a specified space and community.'; + + public static readonly GET_SUBSPACE_SUMMARY = 'Get Subspace'; + public static readonly GET_SUBSPACE_DESCRIPTION = + 'Fetches a specific subspace by UUID within a given space and community'; + + public static readonly UPDATE_SUBSPACE_SUMMARY = 'Update Subspace'; + public static readonly UPDATE_SUBSPACE_DESCRIPTION = + 'Updates a specific subspace within a given space and community.'; + + public static readonly DELETE_SUBSPACE_SUMMARY = 'Delete Subspace'; + public static readonly DELETE_SUBSPACE_DESCRIPTION = + 'Deletes a specific subspace within a given space and community.'; + }; + }; } diff --git a/libs/common/src/helper/services/space.permission.service.ts b/libs/common/src/helper/services/space.permission.service.ts index 0f11cbc..6733aa2 100644 --- a/libs/common/src/helper/services/space.permission.service.ts +++ b/libs/common/src/helper/services/space.permission.service.ts @@ -15,9 +15,6 @@ export class SpacePermissionService { const spaceData = await this.spaceRepository.findOne({ where: { uuid: spaceUuid, - spaceType: { - type: type, - }, userSpaces: { user: { uuid: userUuid, diff --git a/libs/common/src/models/typeOrmCustom.model.ts b/libs/common/src/models/typeOrmCustom.model.ts index 72a818d..0f3abbc 100644 --- a/libs/common/src/models/typeOrmCustom.model.ts +++ b/libs/common/src/models/typeOrmCustom.model.ts @@ -97,7 +97,7 @@ export function TypeORMCustomModel(repository: Repository) { // Use the where clause directly, without wrapping it under 'where' const whereClause = buildTypeORMWhereClause({ where }); - console.log('Where clause after building:', whereClause); + console.log('Final where clause:', whereClause); // Ensure the whereClause is passed directly to findAndCount const [data, count] = await repository.findAndCount({ @@ -112,7 +112,7 @@ export function TypeORMCustomModel(repository: Repository) { const paginationResponseDto = getPaginationResponseDto(count, page, size); const baseResponseDto: BaseResponseDto = { data, - message: getResponseMessage(modelName, { where: whereClause }), + message: getResponseMessage(modelName, { where }), }; return { baseResponseDto, paginationResponseDto }; diff --git a/libs/common/src/modules/space/repositories/space.repository.ts b/libs/common/src/modules/space/repositories/space.repository.ts index ea11a6e..5e0962c 100644 --- a/libs/common/src/modules/space/repositories/space.repository.ts +++ b/libs/common/src/modules/space/repositories/space.repository.ts @@ -1,6 +1,6 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; -import { SpaceEntity, SpaceTypeEntity } from '../entities'; +import { SpaceEntity, SpaceTypeEntity, SubspaceEntity } from '../entities'; @Injectable() export class SpaceRepository extends Repository { @@ -15,3 +15,10 @@ export class SpaceTypeRepository extends Repository { super(SpaceTypeEntity, dataSource.createEntityManager()); } } + +@Injectable() +export class SubspaceRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SubspaceEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/util/buildTypeORMIncludeQuery.ts b/libs/common/src/util/buildTypeORMIncludeQuery.ts index 50583cc..dda60df 100644 --- a/libs/common/src/util/buildTypeORMIncludeQuery.ts +++ b/libs/common/src/util/buildTypeORMIncludeQuery.ts @@ -13,6 +13,9 @@ const mappingInclude: { [key: string]: any } = { space: { space: true, }, + subspace: { + subspace: true, + }, }; export function buildTypeORMIncludeQuery( diff --git a/libs/common/src/util/buildTypeORMWhereClause.ts b/libs/common/src/util/buildTypeORMWhereClause.ts index 6eda8aa..99119eb 100644 --- a/libs/common/src/util/buildTypeORMWhereClause.ts +++ b/libs/common/src/util/buildTypeORMWhereClause.ts @@ -1,10 +1,29 @@ import { FindOptionsWhere } from 'typeorm'; -export function buildTypeORMWhereClause({ - where, -}: { - where?: FindOptionsWhere | FindOptionsWhere[]; // Accepts both object and array formats -}): FindOptionsWhere | FindOptionsWhere[] { - // Return the 'where' clause directly, without wrapping - return where || {}; // If 'where' is undefined, return an empty object +export function buildTypeORMWhereClause({ where }) { + if (!where) return {}; + + // Remove extra nesting if `where` is wrapped within an additional `where` property + const condition = where.where ? where.where : where; + + console.log(condition); + const convertToNestedObject = (condition: any): any => { + const result = {}; + for (const [key, value] of Object.entries(condition)) { + if (key.includes('.')) { + const [parentKey, childKey] = key.split('.'); + result[parentKey] = { + ...(result[parentKey] || {}), + [childKey]: value, + }; + } else { + result[key] = value; + } + } + return result; + }; + + return Array.isArray(condition) + ? condition.map((item) => convertToNestedObject(item)) + : convertToNestedObject(condition); } diff --git a/src/space/controllers/index.ts b/src/space/controllers/index.ts index fed3986..0a5bc08 100644 --- a/src/space/controllers/index.ts +++ b/src/space/controllers/index.ts @@ -1,2 +1,3 @@ export * from './space.controller'; export * from './space-user.controller'; +export * from './subspace.controller'; diff --git a/src/space/controllers/subspace.controller.ts b/src/space/controllers/subspace.controller.ts new file mode 100644 index 0000000..025140f --- /dev/null +++ b/src/space/controllers/subspace.controller.ts @@ -0,0 +1,91 @@ +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { + Body, + Controller, + Delete, + Get, + Param, + Post, + Put, + Query, + UseGuards, +} from '@nestjs/common'; +import { SubSpaceService } from '../services'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../dtos'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; + +@ApiTags('Space Module') +@Controller({ + version: '1', + path: ControllerRoute.SUBSPACE.ROUTE, +}) +export class SubSpaceController { + constructor(private readonly subSpaceService: SubSpaceService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post() + @ApiOperation({ + summary: ControllerRoute.SUBSPACE.ACTIONS.CREATE_SUBSPACE_SUMMARY, + description: ControllerRoute.SUBSPACE.ACTIONS.CREATE_SUBSPACE_DESCRIPTION, + }) + async createSubspace( + @Param() params: GetSpaceParam, + @Body() addSubspaceDto: AddSubspaceDto, + ): Promise { + return this.subSpaceService.createSubspace(addSubspaceDto, params); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SUBSPACE.ACTIONS.LIST_SUBSPACES_SUMMARY, + description: ControllerRoute.SUBSPACE.ACTIONS.LIST_SUBSPACES_DESCRIPTION, + }) + @Get() + async list( + @Param() params: GetSpaceParam, + @Query() query: PaginationRequestGetListDto, + ): Promise { + return this.subSpaceService.list(params, query); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SUBSPACE.ACTIONS.GET_SUBSPACE_SUMMARY, + description: ControllerRoute.SUBSPACE.ACTIONS.GET_SUBSPACE_DESCRIPTION, + }) + @Get(':subSpaceUuid') + async findOne(@Param() params: GetSubSpaceParam): Promise { + return this.subSpaceService.findOne(params); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SUBSPACE.ACTIONS.UPDATE_SUBSPACE_SUMMARY, + description: ControllerRoute.SUBSPACE.ACTIONS.UPDATE_SUBSPACE_DESCRIPTION, + }) + @Put(':subSpaceUuid') + async updateSubspace( + @Param() params: GetSubSpaceParam, + @Body() updateSubSpaceDto: AddSubspaceDto, + ): Promise { + return this.subSpaceService.updateSubSpace(params, updateSubSpaceDto); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SUBSPACE.ACTIONS.DELETE_SUBSPACE_SUMMARY, + description: ControllerRoute.SUBSPACE.ACTIONS.DELETE_SUBSPACE_DESCRIPTION, + }) + @Delete(':subSpaceUuid') + async delete(@Param() params: GetSubSpaceParam): Promise { + return this.subSpaceService.delete(params); + } +} diff --git a/src/space/dtos/index.ts b/src/space/dtos/index.ts index b622787..2cebe7b 100644 --- a/src/space/dtos/index.ts +++ b/src/space/dtos/index.ts @@ -2,3 +2,4 @@ export * from './add.space.dto'; export * from './community-space.param'; export * from './get.space.param'; export * from './user-space.param'; +export * from './subspace'; diff --git a/src/space/dtos/subspace/add.subspace.dto.ts b/src/space/dtos/subspace/add.subspace.dto.ts new file mode 100644 index 0000000..a2b12e2 --- /dev/null +++ b/src/space/dtos/subspace/add.subspace.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class AddSubspaceDto { + @ApiProperty({ + example: 'Meeting Room 1', + description: 'Name of the subspace', + }) + @IsNotEmpty() + @IsString() + subspaceName: string; +} diff --git a/src/space/dtos/subspace/get.subspace.param.ts b/src/space/dtos/subspace/get.subspace.param.ts new file mode 100644 index 0000000..e1c63be --- /dev/null +++ b/src/space/dtos/subspace/get.subspace.param.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; +import { GetSpaceParam } from '../get.space.param'; + +export class GetSubSpaceParam extends GetSpaceParam { + @ApiProperty({ + description: 'UUID of the sub space', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + subSpaceUuid: string; +} diff --git a/src/space/dtos/subspace/index.ts b/src/space/dtos/subspace/index.ts new file mode 100644 index 0000000..5d284f4 --- /dev/null +++ b/src/space/dtos/subspace/index.ts @@ -0,0 +1,2 @@ +export * from './add.subspace.dto'; +export * from './get.subspace.param'; diff --git a/src/space/services/index.ts b/src/space/services/index.ts index 6baa092..8f12a9b 100644 --- a/src/space/services/index.ts +++ b/src/space/services/index.ts @@ -1,2 +1,3 @@ export * from './space.service'; export * from './space-user.service'; +export * from './subspace.service'; diff --git a/src/space/services/subspace.service.ts b/src/space/services/subspace.service.ts new file mode 100644 index 0000000..a8b951f --- /dev/null +++ b/src/space/services/subspace.service.ts @@ -0,0 +1,203 @@ +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { + SpaceRepository, + SubspaceRepository, +} from '@app/common/modules/space/repositories'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../dtos'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { + TypeORMCustomModel, + TypeORMCustomModelFindAllQuery, +} from '@app/common/models/typeOrmCustom.model'; +import { PageResponse } from '@app/common/dto/pagination.response.dto'; +import { SubspaceDto } from '@app/common/modules/space/dtos'; + +@Injectable() +export class SubSpaceService { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly communityRepository: CommunityRepository, + private readonly subspaceRepository: SubspaceRepository, + ) {} + + async createSubspace( + addSubspaceDto: AddSubspaceDto, + params: GetSpaceParam, + ): Promise { + const { communityUuid, spaceUuid } = params; + const space = await this.validateCommunityAndSpace( + communityUuid, + spaceUuid, + ); + + try { + const newSubspace = this.subspaceRepository.create({ + ...addSubspaceDto, + space, + }); + + await this.subspaceRepository.save(newSubspace); + + return new SuccessResponseDto({ + statusCode: HttpStatus.CREATED, + data: newSubspace, + message: 'Subspace created successfully', + }); + } catch (error) { + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + async list( + params: GetSpaceParam, + pageable: Partial, + ): Promise { + const { communityUuid, spaceUuid } = params; + await this.validateCommunityAndSpace(communityUuid, spaceUuid); + + try { + pageable.modelName = 'subspace'; + pageable.where = { space: { uuid: spaceUuid } }; + const customModel = TypeORMCustomModel(this.subspaceRepository); + + const { baseResponseDto, paginationResponseDto } = + await customModel.findAll(pageable); + return new PageResponse( + baseResponseDto, + paginationResponseDto, + ); + } catch (error) { + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + async findOne(params: GetSubSpaceParam): Promise { + const { communityUuid, subSpaceUuid, spaceUuid } = params; + await this.validateCommunityAndSpace(communityUuid, spaceUuid); + try { + const subSpace = await this.subspaceRepository.findOne({ + where: { + uuid: subSpaceUuid, + }, + }); + + // If space is not found, throw a NotFoundException + if (!subSpace) { + throw new HttpException( + `Sub Space with UUID ${subSpaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + return new SuccessResponseDto({ + message: `Subspace with ID ${subSpaceUuid} successfully fetched`, + data: subSpace, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; // If it's an HttpException, rethrow it + } else { + throw new HttpException( + 'An error occurred while deleting the subspace', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + + async updateSubSpace( + params: GetSubSpaceParam, + updateSubSpaceDto: AddSubspaceDto, + ): Promise { + const { spaceUuid, communityUuid, subSpaceUuid } = params; + await this.validateCommunityAndSpace(communityUuid, spaceUuid); + + const subSpace = await this.subspaceRepository.findOne({ + where: { uuid: subSpaceUuid }, + }); + + if (!subSpace) { + throw new HttpException( + `SubSpace with ID ${subSpaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + try { + Object.assign(subSpace, updateSubSpaceDto); + + await this.subspaceRepository.save(subSpace); + + return new SuccessResponseDto({ + message: `Subspace with ID ${subSpaceUuid} successfully updated`, + data: subSpace, + statusCode: HttpStatus.OK, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( + 'An error occurred while updating the subspace', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async delete(params: GetSubSpaceParam): Promise { + const { spaceUuid, communityUuid, subSpaceUuid } = params; + await this.validateCommunityAndSpace(communityUuid, spaceUuid); + + const subspace = await this.subspaceRepository.findOne({ + where: { uuid: subSpaceUuid }, + }); + + if (!subspace) { + throw new HttpException( + `Subspace with ID ${subSpaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + try { + await this.subspaceRepository.remove(subspace); + + return new SuccessResponseDto({ + message: `Subspace with ID ${subSpaceUuid} successfully deleted`, + statusCode: HttpStatus.OK, + }); + } catch (error) { + throw new HttpException( + 'An error occurred while deleting the subspace', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private async validateCommunityAndSpace( + communityUuid: string, + spaceUuid: string, + ) { + const community = await this.communityRepository.findOne({ + where: { uuid: communityUuid }, + }); + if (!community) { + throw new HttpException( + `Community with ID ${communityUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + const space = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid, community: { uuid: communityUuid } }, + }); + if (!space) { + throw new HttpException( + `Space with ID ${spaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + return space; + } +} diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 45c1f02..0de73a9 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -1,11 +1,17 @@ import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { SpaceController } from './controllers'; -import { SpaceService, SpaceUserService } from './services'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { + SpaceController, + SpaceUserController, + SubSpaceController, +} from './controllers'; +import { SpaceService, SpaceUserService, SubSpaceService } from './services'; +import { + SpaceRepository, + SubspaceRepository, +} from '@app/common/modules/space/repositories'; import { CommunityRepository } from '@app/common/modules/community/repositories'; -import { SpaceUserController } from './controllers/space-user.controller'; import { UserRepository, UserSpaceRepository, @@ -13,11 +19,13 @@ import { @Module({ imports: [ConfigModule, SpaceRepositoryModule], - controllers: [SpaceController, SpaceUserController], + controllers: [SpaceController, SpaceUserController, SubSpaceController], providers: [ SpaceService, + SubSpaceService, SpaceRepository, CommunityRepository, + SubspaceRepository, UserSpaceRepository, UserRepository, SpaceUserService, From eb77f8ab993a60bd715b5cdf71c240bbc897372d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 29 Oct 2024 13:13:44 +0400 Subject: [PATCH 018/109] subspace endpoints --- libs/common/src/constants/controller-route.ts | 22 +++ src/space/controllers/index.ts | 2 +- src/space/controllers/subspace/index.ts | 2 + .../subspace/subspace-device.controller.ts | 64 +++++++++ .../{ => subspace}/subspace.controller.ts | 4 +- .../subspace/add.subspace-device.param.ts | 12 ++ src/space/dtos/subspace/index.ts | 1 + src/space/services/index.ts | 2 +- src/space/services/space.service.ts | 43 +++--- src/space/services/subspace/index.ts | 2 + .../subspace/subspace-device.service.ts | 128 ++++++++++++++++++ .../{ => subspace}/subspace.service.ts | 2 +- src/space/space.module.ts | 17 ++- 13 files changed, 275 insertions(+), 26 deletions(-) create mode 100644 src/space/controllers/subspace/index.ts create mode 100644 src/space/controllers/subspace/subspace-device.controller.ts rename src/space/controllers/{ => subspace}/subspace.controller.ts (97%) create mode 100644 src/space/dtos/subspace/add.subspace-device.param.ts create mode 100644 src/space/services/subspace/index.ts create mode 100644 src/space/services/subspace/subspace-device.service.ts rename src/space/services/{ => subspace}/subspace.service.ts (99%) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 3095819..55cb9a7 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -155,4 +155,26 @@ export class ControllerRoute { 'Deletes a specific subspace within a given space and community.'; }; }; + + static SUBSPACE_DEVICE = class { + public static readonly ROUTE = + '/communities/:communityUuid/spaces/:spaceUuid/subspaces/:subSpaceUuid/devices'; + + static ACTIONS = class { + public static readonly LIST_SUBSPACE_DEVICE_SUMMARY = + 'List devices in a subspace'; + public static readonly LIST_SUBSPACE_DEVICE_DESCRIPTION = + 'Retrieves a list of all devices associated with a specified subspace.'; + + public static readonly ASSOCIATE_SUBSPACE_DEVICE_SUMMARY = + 'Associate a device to a subspace'; + public static readonly ASSOCIATE_SUBSPACE_DEVICE_DESCRIPTION = + 'Associates a device with a specific subspace, enabling it to be managed within that subspace context.'; + + public static readonly DISASSOCIATE_SUBSPACE_DEVICE_SUMMARY = + 'Disassociate a device from a subspace'; + public static readonly DISASSOCIATE_SUBSPACE_DEVICE_DESCRIPTION = + 'Removes the association of a device from a specific subspace, making it no longer managed within that subspace.'; + }; + }; } diff --git a/src/space/controllers/index.ts b/src/space/controllers/index.ts index 0a5bc08..dff9330 100644 --- a/src/space/controllers/index.ts +++ b/src/space/controllers/index.ts @@ -1,3 +1,3 @@ export * from './space.controller'; export * from './space-user.controller'; -export * from './subspace.controller'; +export * from './subspace'; diff --git a/src/space/controllers/subspace/index.ts b/src/space/controllers/subspace/index.ts new file mode 100644 index 0000000..de80b58 --- /dev/null +++ b/src/space/controllers/subspace/index.ts @@ -0,0 +1,2 @@ +export * from './subspace.controller'; +export * from './subspace-device.controller'; diff --git a/src/space/controllers/subspace/subspace-device.controller.ts b/src/space/controllers/subspace/subspace-device.controller.ts new file mode 100644 index 0000000..46c4863 --- /dev/null +++ b/src/space/controllers/subspace/subspace-device.controller.ts @@ -0,0 +1,64 @@ +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos'; +import { SubspaceDeviceService } from 'src/space/services'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; + +@ApiTags('Space Module') +@Controller({ + version: '1', + path: ControllerRoute.SUBSPACE_DEVICE.ROUTE, +}) +export class SubSpaceDeviceController { + constructor(private readonly subspaceDeviceService: SubspaceDeviceService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: + ControllerRoute.SUBSPACE_DEVICE.ACTIONS.LIST_SUBSPACE_DEVICE_SUMMARY, + description: + ControllerRoute.SUBSPACE_DEVICE.ACTIONS.LIST_SUBSPACE_DEVICE_DESCRIPTION, + }) + @Get() + async listDevicesInSubspace( + @Param() params: GetSubSpaceParam, + ): Promise { + return await this.subspaceDeviceService.listDevicesInSubspace(params); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: + ControllerRoute.SUBSPACE_DEVICE.ACTIONS.ASSOCIATE_SUBSPACE_DEVICE_SUMMARY, + description: + ControllerRoute.SUBSPACE_DEVICE.ACTIONS + .ASSOCIATE_SUBSPACE_DEVICE_DESCRIPTION, + }) + @Post('/:deviceUuid') + async associateDeviceToSubspace( + @Param() params: DeviceSubSpaceParam, + ): Promise { + return await this.subspaceDeviceService.associateDeviceToSubspace(params); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: + ControllerRoute.SUBSPACE_DEVICE.ACTIONS + .DISASSOCIATE_SUBSPACE_DEVICE_SUMMARY, + description: + ControllerRoute.SUBSPACE_DEVICE.ACTIONS + .DISASSOCIATE_SUBSPACE_DEVICE_DESCRIPTION, + }) + @Post('/:deviceUuid') + async disassociateDeviceFromSubspace( + @Param() params: DeviceSubSpaceParam, + ): Promise { + return await this.subspaceDeviceService.associateDeviceToSubspace(params); + } +} diff --git a/src/space/controllers/subspace.controller.ts b/src/space/controllers/subspace/subspace.controller.ts similarity index 97% rename from src/space/controllers/subspace.controller.ts rename to src/space/controllers/subspace/subspace.controller.ts index 025140f..9f766c4 100644 --- a/src/space/controllers/subspace.controller.ts +++ b/src/space/controllers/subspace/subspace.controller.ts @@ -10,9 +10,9 @@ import { Query, UseGuards, } from '@nestjs/common'; -import { SubSpaceService } from '../services'; +import { SubSpaceService } from '../../services'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; -import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../dtos'; +import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; diff --git a/src/space/dtos/subspace/add.subspace-device.param.ts b/src/space/dtos/subspace/add.subspace-device.param.ts new file mode 100644 index 0000000..885e09d --- /dev/null +++ b/src/space/dtos/subspace/add.subspace-device.param.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; +import { GetSubSpaceParam } from './get.subspace.param'; + +export class DeviceSubSpaceParam extends GetSubSpaceParam { + @ApiProperty({ + description: 'UUID of the device', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + deviceUuid: string; +} diff --git a/src/space/dtos/subspace/index.ts b/src/space/dtos/subspace/index.ts index 5d284f4..0463a85 100644 --- a/src/space/dtos/subspace/index.ts +++ b/src/space/dtos/subspace/index.ts @@ -1,2 +1,3 @@ export * from './add.subspace.dto'; export * from './get.subspace.param'; +export * from './add.subspace-device.param'; diff --git a/src/space/services/index.ts b/src/space/services/index.ts index 8f12a9b..42e2621 100644 --- a/src/space/services/index.ts +++ b/src/space/services/index.ts @@ -1,3 +1,3 @@ export * from './space.service'; export * from './space-user.service'; -export * from './subspace.service'; +export * from './subspace'; diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 4a519e9..6bb6b12 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -107,23 +107,23 @@ export class SpaceService { } } - async findOne(spaceId: string): Promise { + async findOne(spaceUuid: string): Promise { try { const space = await this.spaceRepository.findOne({ where: { - uuid: spaceId, + uuid: spaceUuid, }, }); // If space is not found, throw a NotFoundException if (!space) { throw new HttpException( - `Space with UUID ${spaceId} not found`, + `Space with UUID ${spaceUuid} not found`, HttpStatus.NOT_FOUND, ); } return new SuccessResponseDto({ - message: `Space with ID ${spaceId} successfully fetched`, + message: `Space with ID ${spaceUuid} successfully fetched`, data: space, }); } catch (error) { @@ -138,28 +138,31 @@ export class SpaceService { } } - async delete(spaceId: string, communityId: string): Promise { + async delete( + spaceUuid: string, + communityUuid: string, + ): Promise { try { // First, check if the community exists const community = await this.communityRepository.findOne({ - where: { uuid: communityId }, + where: { uuid: communityUuid }, }); if (!community) { throw new HttpException( - `Community with ID ${communityId} not found`, + `Community with ID ${communityUuid} not found`, HttpStatus.NOT_FOUND, ); } // Check if the space exists const space = await this.spaceRepository.findOne({ - where: { uuid: spaceId, community: { uuid: communityId } }, + where: { uuid: spaceUuid, community: { uuid: communityUuid } }, }); if (!space) { throw new HttpException( - `Space with ID ${spaceId} not found`, + `Space with ID ${spaceUuid} not found`, HttpStatus.NOT_FOUND, ); } @@ -168,7 +171,7 @@ export class SpaceService { await this.spaceRepository.remove(space); return new SuccessResponseDto({ - message: `Space with ID ${spaceId} successfully deleted`, + message: `Space with ID ${spaceUuid} successfully deleted`, statusCode: HttpStatus.OK, }); } catch (error) { @@ -183,7 +186,7 @@ export class SpaceService { } async updateSpace( - spaceId: string, + spaceUuid: string, communityId: string, updateSpaceDto: AddSpaceDto, ): Promise { @@ -202,12 +205,12 @@ export class SpaceService { // Check if the space exists const space = await this.spaceRepository.findOne({ - where: { uuid: spaceId, community: { uuid: communityId } }, + where: { uuid: spaceUuid, community: { uuid: communityId } }, }); if (!space) { throw new HttpException( - `Space with ID ${spaceId} not found`, + `Space with ID ${spaceUuid} not found`, HttpStatus.NOT_FOUND, ); } @@ -241,7 +244,7 @@ export class SpaceService { await this.spaceRepository.save(space); return new SuccessResponseDto({ - message: `Space with ID ${spaceId} successfully updated`, + message: `Space with ID ${spaceUuid} successfully updated`, data: space, statusCode: HttpStatus.OK, }); @@ -256,15 +259,17 @@ export class SpaceService { } } - async getSpacesHierarchyForSpace(spaceId: string): Promise { + async getSpacesHierarchyForSpace( + spaceUuid: string, + ): Promise { const space = await this.spaceRepository.findOne({ - where: { uuid: spaceId }, + where: { uuid: spaceUuid }, }); // If the space doesn't exist, throw a 404 error if (!space) { throw new HttpException( - `Space with ID ${spaceId} not found`, + `Space with ID ${spaceUuid} not found`, HttpStatus.NOT_FOUND, ); } @@ -272,7 +277,7 @@ export class SpaceService { try { // Get all spaces that are children of the provided space, including the parent-child relations const spaces = await this.spaceRepository.find({ - where: { parent: { uuid: spaceId } }, + where: { parent: { uuid: spaceUuid } }, relations: ['parent', 'children'], // Include parent and children relations }); @@ -280,7 +285,7 @@ export class SpaceService { const spaceHierarchy = this.buildSpaceHierarchy(spaces); return new SuccessResponseDto({ - message: `Spaces under space ${spaceId} successfully fetched in hierarchy`, + message: `Spaces under space ${spaceUuid} successfully fetched in hierarchy`, data: spaceHierarchy, statusCode: HttpStatus.OK, }); diff --git a/src/space/services/subspace/index.ts b/src/space/services/subspace/index.ts new file mode 100644 index 0000000..973d199 --- /dev/null +++ b/src/space/services/subspace/index.ts @@ -0,0 +1,2 @@ +export * from './subspace.service'; +export * from './subspace-device.service'; diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts new file mode 100644 index 0000000..6b02bd9 --- /dev/null +++ b/src/space/services/subspace/subspace-device.service.ts @@ -0,0 +1,128 @@ +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { + SpaceRepository, + SubspaceRepository, +} from '@app/common/modules/space/repositories'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; + +@Injectable() +export class SubspaceDeviceService { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly communityRepository: CommunityRepository, + private readonly subspaceRepository: SubspaceRepository, + private readonly deviceRepository: DeviceRepository, + ) {} + + async listDevicesInSubspace( + params: GetSubSpaceParam, + ): Promise { + const { subSpaceUuid, spaceUuid, communityUuid } = params; + + await this.validateCommunityAndSpace(communityUuid, spaceUuid); + + const subspace = await this.findSubspaceWithDevices(subSpaceUuid); + return new SuccessResponseDto({ + data: subspace.devices, + message: 'Successfully retrieved list of devices', + }); + } + + async associateDeviceToSubspace(params: DeviceSubSpaceParam) { + const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid } = params; + + await this.validateCommunityAndSpace(communityUuid, spaceUuid); + + const subspace = await this.findSubspace(subSpaceUuid); + const device = await this.findDevice(deviceUuid); + + device.subspace = subspace; + await this.deviceRepository.save(device); + + return new SuccessResponseDto({ + data: device, + message: 'Successfully associated device to subspace', + }); + } + + async disassociateDeviceFromSubspace(params: DeviceSubSpaceParam) { + const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid } = params; + + await this.validateCommunityAndSpace(communityUuid, spaceUuid); + + await this.findSubspace(subSpaceUuid); + const device = await this.findDevice(deviceUuid); + + device.subspace = null; + await this.deviceRepository.save(device); + + return new SuccessResponseDto({ + data: device, + message: 'Successfully dissociated device from subspace', + }); + } + + // Helper method to validate community and space + private async validateCommunityAndSpace( + communityUuid: string, + spaceUuid: string, + ) { + const community = await this.communityRepository.findOne({ + where: { uuid: communityUuid }, + }); + if (!community) { + this.throwNotFound('Community', communityUuid); + } + + const space = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid, community: { uuid: communityUuid } }, + }); + if (!space) { + this.throwNotFound('Space', spaceUuid); + } + return space; + } + + // Helper method to find subspace with devices relation + private async findSubspaceWithDevices(subSpaceUuid: string) { + const subspace = await this.subspaceRepository.findOne({ + where: { uuid: subSpaceUuid }, + relations: ['devices'], + }); + if (!subspace) { + this.throwNotFound('Subspace', subSpaceUuid); + } + return subspace; + } + + private async findSubspace(subSpaceUuid: string) { + const subspace = await this.subspaceRepository.findOne({ + where: { uuid: subSpaceUuid }, + }); + if (!subspace) { + this.throwNotFound('Subspace', subSpaceUuid); + } + return subspace; + } + + private async findDevice(deviceUuid: string) { + const device = await this.deviceRepository.findOne({ + where: { uuid: deviceUuid }, + }); + if (!device) { + this.throwNotFound('Device', deviceUuid); + } + return device; + } + + private throwNotFound(entity: string, uuid: string) { + throw new HttpException( + `${entity} with ID ${uuid} not found`, + HttpStatus.NOT_FOUND, + ); + } +} diff --git a/src/space/services/subspace.service.ts b/src/space/services/subspace/subspace.service.ts similarity index 99% rename from src/space/services/subspace.service.ts rename to src/space/services/subspace/subspace.service.ts index a8b951f..22c60ee 100644 --- a/src/space/services/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -5,7 +5,7 @@ import { SubspaceRepository, } from '@app/common/modules/space/repositories'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../dtos'; +import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../../dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { TypeORMCustomModel, diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 0de73a9..8ca4ae3 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -6,7 +6,12 @@ import { SpaceUserController, SubSpaceController, } from './controllers'; -import { SpaceService, SpaceUserService, SubSpaceService } from './services'; +import { + SpaceService, + SpaceUserService, + SubspaceDeviceService, + SubSpaceService, +} from './services'; import { SpaceRepository, SubspaceRepository, @@ -16,14 +21,22 @@ import { UserRepository, UserSpaceRepository, } from '@app/common/modules/user/repositories'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; @Module({ imports: [ConfigModule, SpaceRepositoryModule], - controllers: [SpaceController, SpaceUserController, SubSpaceController], + controllers: [ + SpaceController, + SpaceUserController, + SubSpaceController, + SubSpaceController, + ], providers: [ SpaceService, SubSpaceService, + SubspaceDeviceService, SpaceRepository, + DeviceRepository, CommunityRepository, SubspaceRepository, UserSpaceRepository, From 9aecb3c3bc6dd45c174757f0226c162ce88c72ee Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 29 Oct 2024 13:15:28 +0400 Subject: [PATCH 019/109] removed user community --- src/users/controllers/index.ts | 3 +- .../user-communities.controller.ts | 80 ------------ src/users/services/index.ts | 1 - src/users/services/user-community.service.ts | 114 ------------------ src/users/user.module.ts | 7 +- 5 files changed, 4 insertions(+), 201 deletions(-) delete mode 100644 src/users/controllers/user-communities.controller.ts delete mode 100644 src/users/services/user-community.service.ts diff --git a/src/users/controllers/index.ts b/src/users/controllers/index.ts index 291e2fc..f79349e 100644 --- a/src/users/controllers/index.ts +++ b/src/users/controllers/index.ts @@ -1,3 +1,2 @@ export * from './user.controller'; -export * from './user-communities.controller'; -export * from './user-space.controller'; \ No newline at end of file +export * from './user-space.controller'; diff --git a/src/users/controllers/user-communities.controller.ts b/src/users/controllers/user-communities.controller.ts deleted file mode 100644 index 1ebb6cf..0000000 --- a/src/users/controllers/user-communities.controller.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { - Controller, - Delete, - Get, - Param, - Post, - UseGuards, -} from '@nestjs/common'; -import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; -import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; -import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; -import { ControllerRoute } from '@app/common/constants/controller-route'; -import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { UserCommunityService } from '../services'; -import { UserCommunityParamDto, UserParamDto } from '../dtos'; - -@ApiTags('User Module') -@Controller({ - version: EnableDisableStatusEnum.ENABLED, - path: ControllerRoute.USER_COMMUNITY.ROUTE, -}) -export class UserCommunityController { - constructor(private readonly userCommunityService: UserCommunityService) {} - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get() - @ApiOperation({ - summary: - ControllerRoute.USER_COMMUNITY.ACTIONS.GET_USER_COMMUNITIES_SUMMARY, - description: - ControllerRoute.USER_COMMUNITY.ACTIONS.GET_USER_COMMUNITIES_DESCRIPTION, - }) - async getCommunitiesByUserId( - @Param() params: UserParamDto, - ): Promise { - return await this.userCommunityService.getCommunitiesByUserId( - params.userUuid, - ); - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Post(':communityUuid') - @ApiOperation({ - summary: - ControllerRoute.USER_COMMUNITY.ACTIONS.ASSOCIATE_USER_COMMUNITY_SUMMARY, - description: - ControllerRoute.USER_COMMUNITY.ACTIONS - .ASSOCIATE_USER_COMMUNITY_DESCRIPTION, - }) - async associateUserWithCommunity( - @Param() params: UserCommunityParamDto, - ): Promise { - return await this.userCommunityService.associateUserWithCommunity( - params.userUuid, - params.communityUuid, - ); - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @ApiOperation({ - summary: - ControllerRoute.USER_COMMUNITY.ACTIONS - .DISASSOCIATE_USER_COMMUNITY_SUMMARY, - description: - ControllerRoute.USER_COMMUNITY.ACTIONS - .DISASSOCIATE_USER_COMMUNITY_DESCRIPTION, - }) - @Delete(':communityUuid') - async disassociateUserFromCommunity( - @Param() params: UserCommunityParamDto, - ): Promise { - return await this.userCommunityService.disassociateUserFromCommunity( - params.userUuid, - params.communityUuid, - ); - } -} diff --git a/src/users/services/index.ts b/src/users/services/index.ts index 755afb6..c22eedd 100644 --- a/src/users/services/index.ts +++ b/src/users/services/index.ts @@ -1,3 +1,2 @@ export * from './user.service'; -export * from './user-community.service'; export * from './user-space.service'; diff --git a/src/users/services/user-community.service.ts b/src/users/services/user-community.service.ts deleted file mode 100644 index 935c675..0000000 --- a/src/users/services/user-community.service.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { UserRepository } from '@app/common/modules/user/repositories'; -import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; -import { CommunityRepository } from '@app/common/modules/community/repositories'; - -@Injectable() -export class UserCommunityService { - constructor( - private readonly userRepository: UserRepository, - private readonly communityRepository: CommunityRepository, - ) {} - async associateUserWithCommunity( - userUuid: string, - communityUuid: string, - ): Promise { - const { user, community } = await this.getUserAndCommunity( - userUuid, - communityUuid, - ); - // Check if the user is already associated - if (user.communities.some((c) => c.uuid === communityUuid)) { - throw new HttpException( - 'User is already associated with the community', - HttpStatus.BAD_REQUEST, - ); - } - - user.communities.push(community); - await this.userRepository.save(user); - - return new SuccessResponseDto({ - message: `User ${userUuid} successfully associated with community ${communityUuid}`, - }); - } - - async disassociateUserFromCommunity( - userUuid: string, - communityUuid: string, - ): Promise { - const { user } = await this.getUserAndCommunity(userUuid, communityUuid); - - if (!user.communities) { - throw new HttpException( - `User ${userUuid} is not associated with any community`, - HttpStatus.BAD_REQUEST, - ); - } - - const isAssociated = user.communities.some((c) => c.uuid === communityUuid); - - // Check if there is no association - if (!isAssociated) { - throw new HttpException( - `User ${userUuid} is not associated with community ${communityUuid}`, - HttpStatus.BAD_REQUEST, - ); - } - - // Remove the community association - user.communities = user.communities.filter((c) => c.uuid !== communityUuid); - await this.userRepository.save(user); - - return new SuccessResponseDto({ - message: `User ${userUuid} successfully disassociated from community ${communityUuid}`, - }); - } - - async getCommunitiesByUserId(userUuid: string): Promise { - const user = await this.userRepository.findOne({ - where: { uuid: userUuid }, - relations: ['communities'], - }); - - if (!user) { - throw new HttpException( - `User with ID ${userUuid} not found`, - HttpStatus.NOT_FOUND, - ); - } - - if (!user.communities || user.communities.length === 0) { - throw new HttpException( - `User ${userUuid} is not associated with any communities`, - HttpStatus.BAD_REQUEST, - ); - } - - return new SuccessResponseDto({ - data: user.communities, - message: `Communities for user ${userUuid} retrieved successfully`, - }); - } - - private async getUserAndCommunity(userUuid: string, communityUuid: string) { - const user = await this.userRepository.findOne({ - where: { uuid: userUuid }, - relations: ['communities'], - }); - - const community = await this.communityRepository.findOne({ - where: { uuid: communityUuid }, - }); - - if (!user || !community) { - throw new HttpException( - 'User or Community not found', - HttpStatus.NOT_FOUND, - ); - } - - return { user, community }; - } -} diff --git a/src/users/user.module.ts b/src/users/user.module.ts index 61fc323..d65ee68 100644 --- a/src/users/user.module.ts +++ b/src/users/user.module.ts @@ -8,14 +8,14 @@ import { } from '@app/common/modules/user/repositories'; import { RegionRepository } from '@app/common/modules/region/repositories'; import { TimeZoneRepository } from '@app/common/modules/timezone/repositories'; -import { UserCommunityController, UserSpaceController } from './controllers'; +import { UserSpaceController } from './controllers'; import { CommunityModule } from 'src/community/community.module'; -import { UserCommunityService, UserSpaceService } from './services'; +import { UserSpaceService } from './services'; import { CommunityRepository } from '@app/common/modules/community/repositories'; @Module({ imports: [ConfigModule, CommunityModule], - controllers: [UserController, UserSpaceController, UserCommunityController], + controllers: [UserController, UserSpaceController], providers: [ UserService, UserRepository, @@ -23,7 +23,6 @@ import { CommunityRepository } from '@app/common/modules/community/repositories' TimeZoneRepository, UserSpaceRepository, CommunityRepository, - UserCommunityService, UserSpaceService, ], exports: [UserService], From 84bcf098d3958af3bb1f9cbd352e6a566343b78c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 29 Oct 2024 13:19:47 +0400 Subject: [PATCH 020/109] Removed community user --- .../services/community.permission.service.ts | 8 +------- .../community/entities/community.entity.ts | 18 +++++++----------- .../src/modules/user/entities/user.entity.ts | 11 ----------- src/guards/community.permission.guard.ts | 5 +---- 4 files changed, 9 insertions(+), 33 deletions(-) diff --git a/libs/common/src/helper/services/community.permission.service.ts b/libs/common/src/helper/services/community.permission.service.ts index 8486cb7..2422e38 100644 --- a/libs/common/src/helper/services/community.permission.service.ts +++ b/libs/common/src/helper/services/community.permission.service.ts @@ -6,17 +6,11 @@ import { CommunityRepository } from '@app/common/modules/community/repositories' export class CommunityPermissionService { constructor(private readonly communityRepository: CommunityRepository) {} - async checkUserPermission( - communityUuid: string, - userUuid: string, - ): Promise { + async checkUserPermission(communityUuid: string): Promise { try { const communityData = await this.communityRepository.findOne({ where: { uuid: communityUuid, - users: { - uuid: userUuid, - }, }, relations: ['users'], }); diff --git a/libs/common/src/modules/community/entities/community.entity.ts b/libs/common/src/modules/community/entities/community.entity.ts index c63da01..64a1a7e 100644 --- a/libs/common/src/modules/community/entities/community.entity.ts +++ b/libs/common/src/modules/community/entities/community.entity.ts @@ -1,16 +1,8 @@ -import { - Column, - Entity, - ManyToMany, - ManyToOne, - OneToMany, - Unique, -} from 'typeorm'; +import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { CommunityDto } from '../dtos'; import { RegionEntity } from '../../region/entities'; import { SpaceEntity } from '../../space/entities'; -import { UserEntity } from '../../user/entities'; @Entity({ name: 'community' }) @Unique(['name']) @@ -40,6 +32,10 @@ export class CommunityEntity extends AbstractEntity { @OneToMany(() => SpaceEntity, (space) => space.community) spaces: SpaceEntity[]; - @ManyToMany(() => UserEntity, (user) => user.communities) - users: UserEntity[]; + @Column({ + type: 'varchar', + length: 255, + nullable: true, + }) + externalId: string; } diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 18f51fb..c9a11e6 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -2,8 +2,6 @@ import { Column, DeleteDateColumn, Entity, - JoinTable, - ManyToMany, ManyToOne, OneToMany, Unique, @@ -28,7 +26,6 @@ import { OtpType } from '../../../../src/constants/otp-type.enum'; import { RoleTypeEntity } from '../../role-type/entities'; import { SpaceEntity } from '../../space/entities'; import { VisitorPasswordEntity } from '../../visitor-password/entities'; -import { CommunityEntity } from '../../community/entities'; @Entity({ name: 'user' }) export class UserEntity extends AbstractEntity { @@ -119,14 +116,6 @@ export class UserEntity extends AbstractEntity { ) public visitorPasswords: VisitorPasswordEntity[]; - @ManyToMany(() => CommunityEntity, (community) => community.users) - @JoinTable({ - name: 'user_communities', // Join table to link users and communities - joinColumn: { name: 'user_id', referencedColumnName: 'uuid' }, - inverseJoinColumn: { name: 'community_id', referencedColumnName: 'uuid' }, - }) - communities: CommunityEntity[]; - constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/src/guards/community.permission.guard.ts b/src/guards/community.permission.guard.ts index 2db06df..10d9c5e 100644 --- a/src/guards/community.permission.guard.ts +++ b/src/guards/community.permission.guard.ts @@ -33,10 +33,7 @@ export class CommunityPermissionGuard implements CanActivate { throw new BadRequestException('communityUuid is required'); } - await this.permissionService.checkUserPermission( - communityUuid, - user.uuid, - ); + await this.permissionService.checkUserPermission(communityUuid); return true; } catch (error) { From 00ed2c5bbf8ad41a2151f451ffdb2b4ccfd4cd7f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 29 Oct 2024 13:23:30 +0400 Subject: [PATCH 021/109] remove unused --- src/community/services/community.service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index 6eb4055..17fac6a 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -1,7 +1,6 @@ import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { AddCommunityDto } from '../dtos'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; -import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { RegionRepository } from '@app/common/modules/region/repositories'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { @@ -16,7 +15,6 @@ import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; @Injectable() export class CommunityService { constructor( - private readonly userSpaceRepository: UserSpaceRepository, private readonly communityRepository: CommunityRepository, private readonly regionRepository: RegionRepository, ) {} From d6e6eb9b1fa9594b1b586297c74658b93fdbdc78 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 29 Oct 2024 14:24:01 +0400 Subject: [PATCH 022/109] Added tuya space to community --- libs/common/src/common.module.ts | 4 +- libs/common/src/constants/controller-route.ts | 6 +- .../src/integrations/tuya/tuya.service.ts | 38 +++++++++++++ .../community/entities/community.entity.ts | 9 +-- .../modules/region/entities/region.entity.ts | 4 -- src/community/community.module.ts | 4 +- src/community/dtos/add.community.dto.ts | 8 --- src/community/services/community.service.ts | 55 ++++++++++++------- 8 files changed, 82 insertions(+), 46 deletions(-) create mode 100644 libs/common/src/integrations/tuya/tuya.service.ts diff --git a/libs/common/src/common.module.ts b/libs/common/src/common.module.ts index d156de3..08a8fe2 100644 --- a/libs/common/src/common.module.ts +++ b/libs/common/src/common.module.ts @@ -7,10 +7,12 @@ import { ConfigModule } from '@nestjs/config'; import config from './config'; import { EmailService } from './util/email.service'; import { ErrorMessageService } from 'src/error-message/error-message.service'; +import { TuyaService } from './integrations/tuya/tuya.service'; @Module({ - providers: [CommonService, EmailService, ErrorMessageService], + providers: [CommonService, EmailService, ErrorMessageService, TuyaService], exports: [ CommonService, + TuyaService, HelperModule, AuthModule, EmailService, diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 55cb9a7..cee2273 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -13,10 +13,10 @@ export class ControllerRoute { public static readonly ROUTE = 'community'; static ACTIONS = class { public static readonly GET_COMMUNITY_BY_ID_SUMMARY = - 'Get community by community id'; + 'Get community by community community uuid'; public static readonly GET_COMMUNITY_BY_ID_DESCRIPTION = - 'Get community by community id - ( [a-zA-Z0-9]{10} )'; + 'Get community by community community uuid - ( [a-zA-Z0-9]{10} )'; public static readonly LIST_COMMUNITY_SUMMARY = 'Get list of community'; @@ -40,7 +40,7 @@ export class ControllerRoute { }; static COMMUNITY_SPACE = class { - public static readonly ROUTE = 'community/:id/spaces'; + public static readonly ROUTE = 'community/:communityUuid/space'; static ACTIONS = class { public static readonly GET_COMMUNITY_SPACES_HIERARCHY_SUMMARY = 'Fetch hierarchical structure of spaces within a community.'; diff --git a/libs/common/src/integrations/tuya/tuya.service.ts b/libs/common/src/integrations/tuya/tuya.service.ts new file mode 100644 index 0000000..48d8ebe --- /dev/null +++ b/libs/common/src/integrations/tuya/tuya.service.ts @@ -0,0 +1,38 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { TuyaContext } from '@tuya/tuya-connector-nodejs'; + +@Injectable() +export class TuyaService { + private tuya: TuyaContext; + + constructor(private readonly configService: ConfigService) { + const accessKey = this.configService.get('auth-config.ACCESS_KEY'); + const secretKey = this.configService.get('auth-config.SECRET_KEY'); + const tuyaEuUrl = this.configService.get('tuya-config.TUYA_EU_URL'); + this.tuya = new TuyaContext({ + baseUrl: tuyaEuUrl, + accessKey, + secretKey, + }); + } + async createSpace({ name }: { name: string }) { + const path = '/v2.0/cloud/space/creation'; + const body = { name }; + + const response = await this.tuya.request({ + method: 'POST', + path, + body, + }); + + if (response.success) { + return response.result as string; + } else { + throw new HttpException( + 'Error creating space in Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/libs/common/src/modules/community/entities/community.entity.ts b/libs/common/src/modules/community/entities/community.entity.ts index 64a1a7e..3b6f122 100644 --- a/libs/common/src/modules/community/entities/community.entity.ts +++ b/libs/common/src/modules/community/entities/community.entity.ts @@ -1,7 +1,6 @@ -import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; +import { Column, Entity, OneToMany, Unique } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { CommunityDto } from '../dtos'; -import { RegionEntity } from '../../region/entities'; import { SpaceEntity } from '../../space/entities'; @Entity({ name: 'community' }) @@ -23,12 +22,6 @@ export class CommunityEntity extends AbstractEntity { @Column({ length: 255, nullable: true }) description: string; - @ManyToOne(() => RegionEntity, (region) => region.communities, { - nullable: false, - onDelete: 'CASCADE', - }) - region: RegionEntity; - @OneToMany(() => SpaceEntity, (space) => space.community) spaces: SpaceEntity[]; diff --git a/libs/common/src/modules/region/entities/region.entity.ts b/libs/common/src/modules/region/entities/region.entity.ts index a9bdac1..b982f8a 100644 --- a/libs/common/src/modules/region/entities/region.entity.ts +++ b/libs/common/src/modules/region/entities/region.entity.ts @@ -2,7 +2,6 @@ import { Column, Entity, OneToMany } from 'typeorm'; import { RegionDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { UserEntity } from '../../user/entities'; -import { CommunityEntity } from '../../community/entities'; @Entity({ name: 'region' }) export class RegionEntity extends AbstractEntity { @@ -14,9 +13,6 @@ export class RegionEntity extends AbstractEntity { @OneToMany(() => UserEntity, (user) => user.region) users: UserEntity[]; - @OneToMany(() => CommunityEntity, (community) => community.region) - communities: CommunityEntity[]; - constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/src/community/community.module.ts b/src/community/community.module.ts index 7a866b3..c01a9a2 100644 --- a/src/community/community.module.ts +++ b/src/community/community.module.ts @@ -11,7 +11,7 @@ import { SpacePermissionService } from '@app/common/helper/services'; import { CommunitySpaceService } from './services'; import { CommunitySpaceController } from './controllers'; import { CommunityRepository } from '@app/common/modules/community/repositories'; -import { RegionRepository } from '@app/common/modules/region/repositories'; +import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], @@ -22,7 +22,7 @@ import { RegionRepository } from '@app/common/modules/region/repositories'; SpaceRepository, SpaceTypeRepository, UserSpaceRepository, - RegionRepository, + TuyaService, CommunityRepository, SpacePermissionService, ], diff --git a/src/community/dtos/add.community.dto.ts b/src/community/dtos/add.community.dto.ts index bd66d56..1ae91da 100644 --- a/src/community/dtos/add.community.dto.ts +++ b/src/community/dtos/add.community.dto.ts @@ -20,14 +20,6 @@ export class AddCommunityDto { @IsOptional() public description?: string; - @ApiProperty({ - description: 'The UUID of the region this community belongs to', - example: 'b6fa62c5-4fcf-4872-89a0-571cf431a6c7', - }) - @IsUUID() - @IsNotEmpty() - public regionId: string; - constructor(dto: Partial) { Object.assign(this, dto); } diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index 17fac6a..ba0e6f9 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -1,7 +1,6 @@ import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { AddCommunityDto } from '../dtos'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; -import { RegionRepository } from '@app/common/modules/region/repositories'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { TypeORMCustomModel, @@ -11,23 +10,25 @@ import { PageResponse } from '@app/common/dto/pagination.response.dto'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { CommunityDto } from '@app/common/modules/community/dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; @Injectable() export class CommunityService { constructor( private readonly communityRepository: CommunityRepository, - private readonly regionRepository: RegionRepository, + private readonly tuyaService: TuyaService, ) {} async createCommunity(dto: AddCommunityDto): Promise { - const { regionId, name, description } = dto; - // Find the region by the provided regionId - const region = await this.regionRepository.findOneBy({ uuid: regionId }); + const { name, description } = dto; - if (!region) { + const existingCommunity = await this.communityRepository.findOneBy({ + name, + }); + if (existingCommunity) { throw new HttpException( - `Region with ID ${dto.regionId} not found.`, - HttpStatus.NOT_FOUND, + `A community with the name '${name}' already exists.`, + HttpStatus.BAD_REQUEST, ); } @@ -35,11 +36,12 @@ export class CommunityService { const community = this.communityRepository.create({ name: name, description: description, - region: region, // Associate with the found region }); // 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, @@ -52,13 +54,15 @@ export class CommunityService { } } - async getCommunityById(id: string): Promise { - const community = await this.communityRepository.findOneBy({ uuid: id }); + async getCommunityById(communityUuid: string): Promise { + const community = await this.communityRepository.findOneBy({ + uuid: communityUuid, + }); // If the community is not found, throw a 404 NotFoundException if (!community) { throw new HttpException( - `Community with ID ${id} not found.`, + `Community with ID ${communityUuid} not found.`, HttpStatus.NOT_FOUND, ); } @@ -87,17 +91,17 @@ export class CommunityService { } async updateCommunity( - id: string, + communityUuid: string, updateCommunityDto: UpdateCommunityNameDto, ): Promise { const community = await this.communityRepository.findOne({ - where: { uuid: id }, + where: { uuid: communityUuid }, }); // If the community doesn't exist, throw a 404 error if (!community) { throw new HttpException( - `Community with ID ${id} not found`, + `Community with ID ${communityUuid} not found`, HttpStatus.NOT_FOUND, ); } @@ -129,15 +133,15 @@ export class CommunityService { } } - async deleteCommunity(id: string): Promise { + async deleteCommunity(communityUuid: string): Promise { const community = await this.communityRepository.findOne({ - where: { uuid: id }, + where: { uuid: communityUuid }, }); // If the community is not found, throw an error if (!community) { throw new HttpException( - `Community with ID ${id} not found`, + `Community with ID ${communityUuid} not found`, HttpStatus.NOT_FOUND, ); } @@ -145,11 +149,10 @@ export class CommunityService { // Find the community by its uuid // Remove the community from the database - await this.communityRepository.remove(community); // Return success response return new SuccessResponseDto({ - message: `Community with ID ${id} successfully deleted`, + message: `Community with ID ${communityUuid} successfully deleted`, data: null, }); } catch (err) { @@ -164,4 +167,16 @@ export class CommunityService { } } } + + private async createTuyaSpace(name: string): Promise { + 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, + ); + } + } } From 9bd598da31f5e244a935429ad5872a3cfd2581b7 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 29 Oct 2024 16:33:57 +0400 Subject: [PATCH 023/109] added success message --- libs/common/src/dto/success.response.dto.ts | 1 + src/community/services/community.service.ts | 16 +++------------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/libs/common/src/dto/success.response.dto.ts b/libs/common/src/dto/success.response.dto.ts index f21bf78..64cd0c4 100644 --- a/libs/common/src/dto/success.response.dto.ts +++ b/libs/common/src/dto/success.response.dto.ts @@ -23,6 +23,7 @@ export class SuccessResponseDto implements BaseResponseDto { constructor(input: BaseResponseDto) { if (input.statusCode) this.statusCode = input.statusCode; else this.statusCode = 200; + if (input.message) this.message = input.message; if (input.data) this.data = input.data; this.success = true; } diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index ba0e6f9..7080c3e 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -109,15 +109,10 @@ export class CommunityService { try { const { name } = updateCommunityDto; - // Find the community by its UUID - - // Update the community name community.name = name; - // Save the updated community back to the database const updatedCommunity = await this.communityRepository.save(community); - // Return a SuccessResponseDto with the updated community data return new SuccessResponseDto({ message: 'Success update Community', data: updatedCommunity, @@ -146,19 +141,14 @@ export class CommunityService { ); } try { - // Find the community by its uuid + await this.communityRepository.remove(community); - // Remove the community from the database - - // Return success response return new SuccessResponseDto({ - message: `Community with ID ${communityUuid} successfully deleted`, - data: null, + message: `Community with ID ${communityUuid} has been successfully deleted`, }); } catch (err) { - // Catch and handle any errors if (err instanceof HttpException) { - throw err; // If it's an HttpException, rethrow it + throw err; } else { throw new HttpException( 'An error occurred while deleting the community', From a486b24ef8684cc7898f8d9c025607dba2b8d6f2 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 29 Oct 2024 18:13:13 +0400 Subject: [PATCH 024/109] removed get spaces from community --- libs/common/src/constants/controller-route.ts | 10 ++- src/community/community.module.ts | 5 +- .../community-spaces.controller.ts | 36 --------- src/community/controllers/index.ts | 1 - .../services/community-space.service.ts | 79 ------------------- src/community/services/index.ts | 1 - src/space/controllers/space.controller.ts | 17 ++-- src/space/services/space.service.ts | 40 +++++----- 8 files changed, 38 insertions(+), 151 deletions(-) delete mode 100644 src/community/controllers/community-spaces.controller.ts delete mode 100644 src/community/services/community-space.service.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index cee2273..a3c6ffb 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -10,7 +10,7 @@ export class ControllerRoute { }; static COMMUNITY = class { - public static readonly ROUTE = 'community'; + public static readonly ROUTE = 'communities'; static ACTIONS = class { public static readonly GET_COMMUNITY_BY_ID_SUMMARY = 'Get community by community community uuid'; @@ -40,7 +40,7 @@ export class ControllerRoute { }; static COMMUNITY_SPACE = class { - public static readonly ROUTE = 'community/:communityUuid/space'; + public static readonly ROUTE = 'communities/:communityUuid/space'; static ACTIONS = class { public static readonly GET_COMMUNITY_SPACES_HIERARCHY_SUMMARY = 'Fetch hierarchical structure of spaces within a community.'; @@ -111,6 +111,12 @@ export class ControllerRoute { 'Generate a new invitation code for a specific space'; public static readonly CREATE_INVITATION_CODE_SPACE_DESCRIPTION = 'This endpoint generates a new 6-character invitation code for a space identified by its UUID and stores it in the space entity'; + + public static readonly GET_COMMUNITY_SPACES_HIERARCHY_SUMMARY = + 'Fetch hierarchical structure of spaces within a community.'; + + public static readonly GET_COMMUNITY_SPACES_HIERARCHY_DESCRIPTION = + 'retrieves all the spaces associated with a given community, organized into a hierarchical structure.'; }; }; diff --git a/src/community/community.module.ts b/src/community/community.module.ts index c01a9a2..cd89657 100644 --- a/src/community/community.module.ts +++ b/src/community/community.module.ts @@ -8,17 +8,14 @@ import { SpaceTypeRepository } from '@app/common/modules/space/repositories'; import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; import { SpacePermissionService } from '@app/common/helper/services'; -import { CommunitySpaceService } from './services'; -import { CommunitySpaceController } from './controllers'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], - controllers: [CommunityController, CommunitySpaceController], + controllers: [CommunityController], providers: [ CommunityService, - CommunitySpaceService, SpaceRepository, SpaceTypeRepository, UserSpaceRepository, diff --git a/src/community/controllers/community-spaces.controller.ts b/src/community/controllers/community-spaces.controller.ts deleted file mode 100644 index 1897d21..0000000 --- a/src/community/controllers/community-spaces.controller.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Controller, Get, Param, UseGuards } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; -import { GetCommunityParams } from '../dtos/get.community.dto'; -// import { CheckUserCommunityGuard } from 'src/guards/user.community.guard'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; -import { ControllerRoute } from '@app/common/constants/controller-route'; -import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { CommunitySpaceService } from '../services'; - -@ApiTags('Community Module') -@Controller({ - version: '1', - path: ControllerRoute.COMMUNITY_SPACE.ROUTE, -}) -export class CommunitySpaceController { - constructor(private readonly communitySpaceService: CommunitySpaceService) {} - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @ApiOperation({ - summary: - ControllerRoute.COMMUNITY_SPACE.ACTIONS - .GET_COMMUNITY_SPACES_HIERARCHY_SUMMARY, - description: - ControllerRoute.COMMUNITY_SPACE.ACTIONS - .GET_COMMUNITY_SPACES_HIERARCHY_DESCRIPTION, - }) - @Get() - async getCommunityByUuid( - @Param() params: GetCommunityParams, - ): Promise { - return await this.communitySpaceService.getSpacesHierarchyForCommunity( - params.communityUuid, - ); - } -} diff --git a/src/community/controllers/index.ts b/src/community/controllers/index.ts index fd6fd56..b78bbea 100644 --- a/src/community/controllers/index.ts +++ b/src/community/controllers/index.ts @@ -1,2 +1 @@ export * from './community.controller'; -export * from './community-spaces.controller'; diff --git a/src/community/services/community-space.service.ts b/src/community/services/community-space.service.ts deleted file mode 100644 index 54fb4f3..0000000 --- a/src/community/services/community-space.service.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { CommunityRepository } from '@app/common/modules/community/repositories'; -import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; -import { SpaceEntity } from '@app/common/modules/space/entities'; - -@Injectable() -export class CommunitySpaceService { - constructor( - private readonly spaceRepository: SpaceRepository, - private readonly communityRepository: CommunityRepository, - ) {} - - async getSpacesHierarchyForCommunity( - communityId: string, - ): Promise { - const community = await this.communityRepository.findOne({ - where: { uuid: communityId }, - }); - - // If the community doesn't exist, throw a 404 error - if (!community) { - throw new HttpException( - `Community with ID ${communityId} not found`, - HttpStatus.NOT_FOUND, - ); - } - try { - // Get all spaces related to the community, including the parent-child relations - const spaces = await this.spaceRepository.find({ - where: { community: { uuid: communityId } }, - relations: ['parent', 'children'], // Include parent and children relations - }); - - // Organize spaces into a hierarchical structure - const spaceHierarchy = this.buildSpaceHierarchy(spaces); - - return new SuccessResponseDto({ - message: `Spaces in community ${communityId} successfully fetched in hierarchy`, - data: spaceHierarchy, - statusCode: HttpStatus.OK, - }); - } catch (error) { - throw new HttpException( - 'An error occurred while fetching the spaces', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - private buildSpaceHierarchy(spaces: SpaceEntity[]): SpaceEntity[] { - const map = new Map(); - - // Step 1: Create a map of spaces by UUID - spaces.forEach((space) => { - map.set( - space.uuid, - this.spaceRepository.create({ - ...space, - children: [], // Add children if needed - }), - ); - }); - - // Step 2: Organize the hierarchy - const rootSpaces: SpaceEntity[] = []; - spaces.forEach((space) => { - if (space.parent && space.parent.uuid) { - const parent = map.get(space.parent.uuid); - parent?.children?.push(map.get(space.uuid)); - } else { - rootSpaces.push(map.get(space.uuid)); - } - }); - - return rootSpaces; // Return the root spaces with children nested within them - } -} diff --git a/src/community/services/index.ts b/src/community/services/index.ts index 257481b..2408c7c 100644 --- a/src/community/services/index.ts +++ b/src/community/services/index.ts @@ -1,2 +1 @@ export * from './community.service'; -export * from './community-space.service'; diff --git a/src/space/controllers/space.controller.ts b/src/space/controllers/space.controller.ts index 2d70156..cb8cbb8 100644 --- a/src/space/controllers/space.controller.ts +++ b/src/space/controllers/space.controller.ts @@ -9,13 +9,11 @@ import { Param, Post, Put, - Query, UseGuards, } from '@nestjs/common'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { AddSpaceDto, CommunitySpaceParam } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; import { GetSpaceParam } from '../dtos/get.space.param'; @ApiTags('Space Module') @@ -46,15 +44,18 @@ export class SpaceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @ApiOperation({ - summary: ControllerRoute.SPACE.ACTIONS.LIST_SPACE_SUMMARY, - description: ControllerRoute.SPACE.ACTIONS.LIST_SPACE_DESCRIPTION, + summary: + ControllerRoute.SPACE.ACTIONS.GET_COMMUNITY_SPACES_HIERARCHY_SUMMARY, + description: + ControllerRoute.SPACE.ACTIONS.GET_COMMUNITY_SPACES_HIERARCHY_DESCRIPTION, }) @Get() - async list( - @Param() communitySpaceParam: CommunitySpaceParam, - @Query() query: PaginationRequestGetListDto, + async getHierarchy( + @Param() param: CommunitySpaceParam, ): Promise { - return this.spaceService.list(communitySpaceParam.communityUuid, query); + return this.spaceService.getSpacesHierarchyForCommunity( + param.communityUuid, + ); } @ApiBearerAuth() diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 6bb6b12..52267d8 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -10,12 +10,6 @@ import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { SpaceEntity } from '@app/common/modules/space/entities'; -import { - TypeORMCustomModel, - TypeORMCustomModelFindAllQuery, -} from '@app/common/models/typeOrmCustom.model'; -import { SpaceDto } from '@app/common/modules/space/dtos'; -import { PageResponse } from '@app/common/dto/pagination.response.dto'; import { generateRandomString } from '@app/common/helper/randomString'; @Injectable() @@ -76,34 +70,40 @@ export class SpaceService { } } - async list( - communityId: string, - pageable: Partial, + async getSpacesHierarchyForCommunity( + communityUuid: string, ): Promise { const community = await this.communityRepository.findOne({ - where: { uuid: communityId }, + where: { uuid: communityUuid }, }); // If the community doesn't exist, throw a 404 error if (!community) { throw new HttpException( - `Community with ID ${communityId} not found`, + `Community with ID ${communityUuid} not found`, HttpStatus.NOT_FOUND, ); } try { - pageable.modelName = 'space'; - pageable.where = { - community: { uuid: communityId }, - }; + // Get all spaces related to the community, including the parent-child relations + const spaces = await this.spaceRepository.find({ + where: { community: { uuid: communityUuid } }, + relations: ['parent', 'children'], // Include parent and children relations + }); - const customModel = TypeORMCustomModel(this.spaceRepository); + // Organize spaces into a hierarchical structure + const spaceHierarchy = this.buildSpaceHierarchy(spaces); - const { baseResponseDto, paginationResponseDto } = - await customModel.findAll(pageable); - return new PageResponse(baseResponseDto, paginationResponseDto); + return new SuccessResponseDto({ + message: `Spaces in community ${communityUuid} successfully fetched in hierarchy`, + data: spaceHierarchy, + statusCode: HttpStatus.OK, + }); } catch (error) { - throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + throw new HttpException( + 'An error occurred while fetching the spaces', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } From 2422f40a2d06474e83deab9411b17977877f62d1 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 30 Oct 2024 11:28:25 +0400 Subject: [PATCH 025/109] remove unused guard --- .../services/space.permission.service.ts | 5 +- src/community/community.module.ts | 2 - src/guards/building.permission.guard.ts | 36 ---------- src/guards/building.type.guard.ts | 62 ---------------- src/guards/community.type.guard.ts | 63 ----------------- src/guards/floor.permission.guard.ts | 36 ---------- src/guards/room.permission.guard.ts | 36 ---------- src/guards/unit.permission.guard.ts | 7 +- src/guards/user.building.guard.ts | 70 ------------------- src/guards/user.floor.guard.ts | 70 ------------------- src/guards/user.room.guard.ts | 70 ------------------- src/guards/user.unit.guard.ts | 70 ------------------- 12 files changed, 3 insertions(+), 524 deletions(-) delete mode 100644 src/guards/building.permission.guard.ts delete mode 100644 src/guards/building.type.guard.ts delete mode 100644 src/guards/community.type.guard.ts delete mode 100644 src/guards/floor.permission.guard.ts delete mode 100644 src/guards/room.permission.guard.ts delete mode 100644 src/guards/user.building.guard.ts delete mode 100644 src/guards/user.floor.guard.ts delete mode 100644 src/guards/user.room.guard.ts delete mode 100644 src/guards/user.unit.guard.ts diff --git a/libs/common/src/helper/services/space.permission.service.ts b/libs/common/src/helper/services/space.permission.service.ts index 6733aa2..f5a739d 100644 --- a/libs/common/src/helper/services/space.permission.service.ts +++ b/libs/common/src/helper/services/space.permission.service.ts @@ -9,7 +9,6 @@ export class SpacePermissionService { async checkUserPermission( spaceUuid: string, userUuid: string, - type: string, ): Promise { try { const spaceData = await this.spaceRepository.findOne({ @@ -21,12 +20,12 @@ export class SpacePermissionService { }, }, }, - relations: ['spaceType', 'userSpaces', 'userSpaces.user'], + relations: ['userSpaces', 'userSpaces.user'], }); if (!spaceData) { throw new BadRequestException( - `You do not have permission to access this ${type}`, + `You do not have permission to access this space`, ); } } catch (err) { diff --git a/src/community/community.module.ts b/src/community/community.module.ts index cd89657..81c36cf 100644 --- a/src/community/community.module.ts +++ b/src/community/community.module.ts @@ -4,7 +4,6 @@ import { CommunityController } from './controllers/community.controller'; import { ConfigModule } from '@nestjs/config'; import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { SpaceTypeRepository } from '@app/common/modules/space/repositories'; import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; import { SpacePermissionService } from '@app/common/helper/services'; @@ -17,7 +16,6 @@ import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; providers: [ CommunityService, SpaceRepository, - SpaceTypeRepository, UserSpaceRepository, TuyaService, CommunityRepository, diff --git a/src/guards/building.permission.guard.ts b/src/guards/building.permission.guard.ts deleted file mode 100644 index 5d496a9..0000000 --- a/src/guards/building.permission.guard.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { SpaceType } from '@app/common/constants/space-type.enum'; -import { SpacePermissionService } from '@app/common/helper/services/space.permission.service'; -import { - BadRequestException, - CanActivate, - ExecutionContext, - Injectable, -} from '@nestjs/common'; - -@Injectable() -export class BuildingPermissionGuard implements CanActivate { - constructor(private readonly permissionService: SpacePermissionService) {} - - async canActivate(context: ExecutionContext): Promise { - const req = context.switchToHttp().getRequest(); - - try { - const { buildingUuid } = req.params; - const { user } = req; - - if (!buildingUuid) { - throw new BadRequestException('buildingUuid is required'); - } - - await this.permissionService.checkUserPermission( - buildingUuid, - user.uuid, - SpaceType.BUILDING, - ); - - return true; - } catch (error) { - throw error; - } - } -} diff --git a/src/guards/building.type.guard.ts b/src/guards/building.type.guard.ts deleted file mode 100644 index 29fba8a..0000000 --- a/src/guards/building.type.guard.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { - Injectable, - CanActivate, - HttpStatus, - BadRequestException, - ExecutionContext, -} from '@nestjs/common'; - -@Injectable() -export class CheckBuildingTypeGuard implements CanActivate { - constructor(private readonly spaceRepository: SpaceRepository) {} - - async canActivate(context: ExecutionContext): Promise { - const req = context.switchToHttp().getRequest(); - - try { - const { floorName, buildingUuid } = req.body; - - if (!floorName) { - throw new BadRequestException('floorName is required'); - } - - if (!buildingUuid) { - throw new BadRequestException('buildingUuid is required'); - } - - await this.checkBuildingIsBuildingType(buildingUuid); - - return true; - } catch (error) { - this.handleGuardError(error, context); - return false; - } - } - - async checkBuildingIsBuildingType(buildingUuid: string) { - const buildingData = await this.spaceRepository.findOne({ - where: { uuid: buildingUuid }, - relations: ['spaceType'], - }); - if (!buildingData) { - throw new BadRequestException('Invalid building UUID'); - } - } - - private handleGuardError(error: Error, context: ExecutionContext) { - const response = context.switchToHttp().getResponse(); - console.error(error); - - if (error instanceof BadRequestException) { - response - .status(HttpStatus.BAD_REQUEST) - .json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message }); - } else { - response.status(HttpStatus.NOT_FOUND).json({ - statusCode: HttpStatus.NOT_FOUND, - message: 'Building not found', - }); - } - } -} diff --git a/src/guards/community.type.guard.ts b/src/guards/community.type.guard.ts deleted file mode 100644 index f001978..0000000 --- a/src/guards/community.type.guard.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { - CanActivate, - ExecutionContext, - Injectable, - HttpStatus, -} from '@nestjs/common'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { BadRequestException } from '@nestjs/common'; - -@Injectable() -export class CheckCommunityTypeGuard implements CanActivate { - constructor(private readonly spaceRepository: SpaceRepository) {} - - async canActivate(context: ExecutionContext): Promise { - const req = context.switchToHttp().getRequest(); - - try { - const { buildingName, communityUuid } = req.body; - - if (!buildingName) { - throw new BadRequestException('buildingName is required'); - } - - if (!communityUuid) { - throw new BadRequestException('communityUuid is required'); - } - - await this.checkCommunityIsCommunityType(communityUuid); - - return true; - } catch (error) { - this.handleGuardError(error, context); - return false; - } - } - - private async checkCommunityIsCommunityType(communityUuid: string) { - const communityData = await this.spaceRepository.findOne({ - where: { uuid: communityUuid }, - relations: ['spaceType'], - }); - - if (!communityData) { - throw new BadRequestException('Invalid community UUID'); - } - } - - private handleGuardError(error: Error, context: ExecutionContext) { - const response = context.switchToHttp().getResponse(); - console.error(error); - - if (error instanceof BadRequestException) { - response - .status(HttpStatus.BAD_REQUEST) - .json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message }); - } else { - response.status(HttpStatus.NOT_FOUND).json({ - statusCode: HttpStatus.NOT_FOUND, - message: 'Community not found', - }); - } - } -} diff --git a/src/guards/floor.permission.guard.ts b/src/guards/floor.permission.guard.ts deleted file mode 100644 index 7092264..0000000 --- a/src/guards/floor.permission.guard.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { SpaceType } from '@app/common/constants/space-type.enum'; -import { SpacePermissionService } from '@app/common/helper/services/space.permission.service'; -import { - BadRequestException, - CanActivate, - ExecutionContext, - Injectable, -} from '@nestjs/common'; - -@Injectable() -export class FloorPermissionGuard implements CanActivate { - constructor(private readonly permissionService: SpacePermissionService) {} - - async canActivate(context: ExecutionContext): Promise { - const req = context.switchToHttp().getRequest(); - - try { - const { floorUuid } = req.params; - const { user } = req; - - if (!floorUuid) { - throw new BadRequestException('floorUuid is required'); - } - - await this.permissionService.checkUserPermission( - floorUuid, - user.uuid, - SpaceType.FLOOR, - ); - - return true; - } catch (error) { - throw error; - } - } -} diff --git a/src/guards/room.permission.guard.ts b/src/guards/room.permission.guard.ts deleted file mode 100644 index d1e7042..0000000 --- a/src/guards/room.permission.guard.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { SpaceType } from '@app/common/constants/space-type.enum'; -import { SpacePermissionService } from '@app/common/helper/services/space.permission.service'; -import { - BadRequestException, - CanActivate, - ExecutionContext, - Injectable, -} from '@nestjs/common'; - -@Injectable() -export class RoomPermissionGuard implements CanActivate { - constructor(private readonly permissionService: SpacePermissionService) {} - - async canActivate(context: ExecutionContext): Promise { - const req = context.switchToHttp().getRequest(); - - try { - const { roomUuid } = req.params; - const { user } = req; - - if (!roomUuid) { - throw new BadRequestException('roomUuid is required'); - } - - await this.permissionService.checkUserPermission( - roomUuid, - user.uuid, - SpaceType.ROOM, - ); - - return true; - } catch (error) { - throw error; - } - } -} diff --git a/src/guards/unit.permission.guard.ts b/src/guards/unit.permission.guard.ts index fe2b969..9e91494 100644 --- a/src/guards/unit.permission.guard.ts +++ b/src/guards/unit.permission.guard.ts @@ -1,4 +1,3 @@ -import { SpaceType } from '@app/common/constants/space-type.enum'; import { SpacePermissionService } from '@app/common/helper/services/space.permission.service'; import { BadRequestException, @@ -22,11 +21,7 @@ export class UnitPermissionGuard implements CanActivate { throw new BadRequestException('unitUuid is required'); } - await this.permissionService.checkUserPermission( - unitUuid, - user.uuid, - SpaceType.UNIT, - ); + await this.permissionService.checkUserPermission(unitUuid, user.uuid); return true; } catch (error) { diff --git a/src/guards/user.building.guard.ts b/src/guards/user.building.guard.ts deleted file mode 100644 index aa23d89..0000000 --- a/src/guards/user.building.guard.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - CanActivate, - ExecutionContext, - Injectable, - HttpStatus, -} from '@nestjs/common'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { BadRequestException, NotFoundException } from '@nestjs/common'; -import { UserRepository } from '@app/common/modules/user/repositories'; - -@Injectable() -export class CheckUserBuildingGuard implements CanActivate { - constructor( - private readonly spaceRepository: SpaceRepository, - private readonly userRepository: UserRepository, - ) {} - - async canActivate(context: ExecutionContext): Promise { - const req = context.switchToHttp().getRequest(); - - try { - const { userUuid, buildingUuid } = req.body; - - await this.checkUserIsFound(userUuid); - - await this.checkBuildingIsFound(buildingUuid); - - return true; - } catch (error) { - this.handleGuardError(error, context); - return false; - } - } - - private async checkUserIsFound(userUuid: string) { - const userData = await this.userRepository.findOne({ - where: { uuid: userUuid }, - }); - if (!userData) { - throw new NotFoundException('User not found'); - } - } - - private async checkBuildingIsFound(spaceUuid: string) { - const spaceData = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid }, - relations: ['spaceType'], - }); - if (!spaceData) { - throw new NotFoundException('Building not found'); - } - } - - private handleGuardError(error: Error, context: ExecutionContext) { - const response = context.switchToHttp().getResponse(); - if ( - error instanceof BadRequestException || - error instanceof NotFoundException - ) { - response - .status(HttpStatus.NOT_FOUND) - .json({ statusCode: HttpStatus.NOT_FOUND, message: error.message }); - } else { - response.status(HttpStatus.BAD_REQUEST).json({ - statusCode: HttpStatus.BAD_REQUEST, - message: 'invalid userUuid or buildingUuid', - }); - } - } -} diff --git a/src/guards/user.floor.guard.ts b/src/guards/user.floor.guard.ts deleted file mode 100644 index 3c6e45e..0000000 --- a/src/guards/user.floor.guard.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - CanActivate, - ExecutionContext, - Injectable, - HttpStatus, -} from '@nestjs/common'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { BadRequestException, NotFoundException } from '@nestjs/common'; -import { UserRepository } from '@app/common/modules/user/repositories'; -import { SpaceType } from '@app/common/constants/space-type.enum'; - -@Injectable() -export class CheckUserFloorGuard implements CanActivate { - constructor( - private readonly spaceRepository: SpaceRepository, - private readonly userRepository: UserRepository, - ) {} - - async canActivate(context: ExecutionContext): Promise { - const req = context.switchToHttp().getRequest(); - - try { - const { userUuid, floorUuid } = req.body; - - await this.checkUserIsFound(userUuid); - - await this.checkFloorIsFound(floorUuid); - - return true; - } catch (error) { - this.handleGuardError(error, context); - return false; - } - } - - private async checkUserIsFound(userUuid: string) { - const userData = await this.userRepository.findOne({ - where: { uuid: userUuid }, - }); - if (!userData) { - throw new NotFoundException('User not found'); - } - } - - private async checkFloorIsFound(spaceUuid: string) { - const spaceData = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid }, - }); - if (!spaceData) { - throw new NotFoundException('Floor not found'); - } - } - - private handleGuardError(error: Error, context: ExecutionContext) { - const response = context.switchToHttp().getResponse(); - if ( - error instanceof BadRequestException || - error instanceof NotFoundException - ) { - response - .status(HttpStatus.NOT_FOUND) - .json({ statusCode: HttpStatus.NOT_FOUND, message: error.message }); - } else { - response.status(HttpStatus.BAD_REQUEST).json({ - statusCode: HttpStatus.BAD_REQUEST, - message: 'invalid userUuid or floorUuid', - }); - } - } -} diff --git a/src/guards/user.room.guard.ts b/src/guards/user.room.guard.ts deleted file mode 100644 index 9f38712..0000000 --- a/src/guards/user.room.guard.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - CanActivate, - ExecutionContext, - Injectable, - HttpStatus, -} from '@nestjs/common'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { BadRequestException, NotFoundException } from '@nestjs/common'; -import { UserRepository } from '@app/common/modules/user/repositories'; - -@Injectable() -export class CheckUserRoomGuard implements CanActivate { - constructor( - private readonly spaceRepository: SpaceRepository, - private readonly userRepository: UserRepository, - ) {} - - async canActivate(context: ExecutionContext): Promise { - const req = context.switchToHttp().getRequest(); - - try { - const { userUuid, roomUuid } = req.body; - - await this.checkUserIsFound(userUuid); - - await this.checkRoomIsFound(roomUuid); - - return true; - } catch (error) { - this.handleGuardError(error, context); - return false; - } - } - - private async checkUserIsFound(userUuid: string) { - const userData = await this.userRepository.findOne({ - where: { uuid: userUuid }, - }); - if (!userData) { - throw new NotFoundException('User not found'); - } - } - - private async checkRoomIsFound(spaceUuid: string) { - const spaceData = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid }, - relations: ['spaceType'], - }); - if (!spaceData) { - throw new NotFoundException('Room not found'); - } - } - - private handleGuardError(error: Error, context: ExecutionContext) { - const response = context.switchToHttp().getResponse(); - if ( - error instanceof BadRequestException || - error instanceof NotFoundException - ) { - response - .status(HttpStatus.NOT_FOUND) - .json({ statusCode: HttpStatus.NOT_FOUND, message: error.message }); - } else { - response.status(HttpStatus.BAD_REQUEST).json({ - statusCode: HttpStatus.BAD_REQUEST, - message: 'invalid userUuid or roomUuid', - }); - } - } -} diff --git a/src/guards/user.unit.guard.ts b/src/guards/user.unit.guard.ts deleted file mode 100644 index f5f1bfb..0000000 --- a/src/guards/user.unit.guard.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { - CanActivate, - ExecutionContext, - Injectable, - HttpStatus, -} from '@nestjs/common'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { BadRequestException, NotFoundException } from '@nestjs/common'; -import { UserRepository } from '@app/common/modules/user/repositories'; - -@Injectable() -export class CheckUserUnitGuard implements CanActivate { - constructor( - private readonly spaceRepository: SpaceRepository, - private readonly userRepository: UserRepository, - ) {} - - async canActivate(context: ExecutionContext): Promise { - const req = context.switchToHttp().getRequest(); - - try { - const { userUuid, unitUuid } = req.body; - - await this.checkUserIsFound(userUuid); - - await this.checkUnitIsFound(unitUuid); - - return true; - } catch (error) { - this.handleGuardError(error, context); - return false; - } - } - - private async checkUserIsFound(userUuid: string) { - const userData = await this.userRepository.findOne({ - where: { uuid: userUuid }, - }); - if (!userData) { - throw new NotFoundException('User not found'); - } - } - - private async checkUnitIsFound(spaceUuid: string) { - const spaceData = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid }, - relations: ['spaceType'], - }); - if (!spaceData) { - throw new NotFoundException('Unit not found'); - } - } - - private handleGuardError(error: Error, context: ExecutionContext) { - const response = context.switchToHttp().getResponse(); - if ( - error instanceof BadRequestException || - error instanceof NotFoundException - ) { - response - .status(HttpStatus.NOT_FOUND) - .json({ statusCode: HttpStatus.NOT_FOUND, message: error.message }); - } else { - response.status(HttpStatus.BAD_REQUEST).json({ - statusCode: HttpStatus.BAD_REQUEST, - message: 'invalid userUuid or unitUuid', - }); - } - } -} From 295013fd15b2749557a670e4b3866edb27945c36 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 30 Oct 2024 14:42:01 +0400 Subject: [PATCH 026/109] automation service --- .../controllers/automation.controller.ts | 41 +++++++------ src/automation/dtos/automation.param.dto.ts | 11 ++++ .../dtos/delete.automation.param.dto.ts | 18 ++++++ src/automation/dtos/index.ts | 3 + src/automation/dtos/space.param.dto.ts | 11 ++++ src/automation/services/automation.service.ts | 30 ++++----- src/scene/controllers/scene.controller.ts | 35 ++++++----- src/scene/dtos/delete.scene.param.dto.ts | 18 ++++++ src/scene/dtos/index.ts | 3 + src/scene/dtos/scene.param.dto.ts | 11 ++++ src/scene/dtos/space.param.dto.ts | 11 ++++ src/scene/services/scene.service.ts | 61 +++++++++++-------- src/space/space.module.ts | 3 +- 13 files changed, 183 insertions(+), 73 deletions(-) create mode 100644 src/automation/dtos/automation.param.dto.ts create mode 100644 src/automation/dtos/delete.automation.param.dto.ts create mode 100644 src/automation/dtos/space.param.dto.ts create mode 100644 src/scene/dtos/delete.scene.param.dto.ts create mode 100644 src/scene/dtos/scene.param.dto.ts create mode 100644 src/scene/dtos/space.param.dto.ts diff --git a/src/automation/controllers/automation.controller.ts b/src/automation/controllers/automation.controller.ts index af5ee30..e608d96 100644 --- a/src/automation/controllers/automation.controller.ts +++ b/src/automation/controllers/automation.controller.ts @@ -18,6 +18,11 @@ import { } from '../dtos/automation.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { + AutomationParamDto, + DeleteAutomationParamDto, + SpaceParamDto, +} from '../dtos'; @ApiTags('Automation Module') @Controller({ @@ -42,28 +47,29 @@ export class AutomationController { } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get(':unitUuid') - async getAutomationByUnit(@Param('unitUuid') unitUuid: string) { - const automation = - await this.automationService.getAutomationByUnit(unitUuid); + @Get(':spaceUuid') + async getAutomationByUnit(@Param() param: SpaceParamDto) { + const automation = await this.automationService.getAutomationByUnit( + param.spaceUuid, + ); return automation; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get('details/:automationId') - async getAutomationDetails(@Param('automationId') automationId: string) { - const automation = - await this.automationService.getAutomationDetails(automationId); + @Get('details/:automationUuid') + async getAutomationDetails(@Param() param: AutomationParamDto) { + const automation = await this.automationService.getAutomationDetails( + param.automationUuid, + ); return automation; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Delete(':unitUuid/:automationId') - async deleteAutomation( - @Param('unitUuid') unitUuid: string, - @Param('automationId') automationId: string, - ) { - await this.automationService.deleteAutomation(unitUuid, automationId); + async deleteAutomation(@Param() param: DeleteAutomationParamDto) { + await this.automationService.deleteAutomation(param); return { statusCode: HttpStatus.OK, message: 'Automation Deleted Successfully', @@ -74,11 +80,11 @@ export class AutomationController { @Put(':automationId') async updateAutomation( @Body() updateAutomationDto: UpdateAutomationDto, - @Param('automationId') automationId: string, + @Param() param: AutomationParamDto, ) { const automation = await this.automationService.updateAutomation( updateAutomationDto, - automationId, + param.automationUuid, ); return { statusCode: HttpStatus.CREATED, @@ -87,16 +93,17 @@ export class AutomationController { data: automation, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('status/:automationId') async updateAutomationStatus( @Body() updateAutomationStatusDto: UpdateAutomationStatusDto, - @Param('automationId') automationId: string, + @Param() param: AutomationParamDto, ) { await this.automationService.updateAutomationStatus( updateAutomationStatusDto, - automationId, + param.automationUuid, ); return { statusCode: HttpStatus.CREATED, diff --git a/src/automation/dtos/automation.param.dto.ts b/src/automation/dtos/automation.param.dto.ts new file mode 100644 index 0000000..4c21461 --- /dev/null +++ b/src/automation/dtos/automation.param.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; + +export class AutomationParamDto { + @ApiProperty({ + description: 'UUID of the automation', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + automationUuid: string; +} diff --git a/src/automation/dtos/delete.automation.param.dto.ts b/src/automation/dtos/delete.automation.param.dto.ts new file mode 100644 index 0000000..96ee49b --- /dev/null +++ b/src/automation/dtos/delete.automation.param.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; + +export class DeleteAutomationParamDto { + @ApiProperty({ + description: 'UUID of the Space', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + spaceUuid: string; + + @ApiProperty({ + description: 'UUID of the Automation', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + automationUuid: string; +} diff --git a/src/automation/dtos/index.ts b/src/automation/dtos/index.ts index 4cdad58..03a74a0 100644 --- a/src/automation/dtos/index.ts +++ b/src/automation/dtos/index.ts @@ -1 +1,4 @@ export * from './automation.dto'; +export * from './space.param.dto'; +export * from './automation.param.dto'; +export * from './delete.automation.param.dto'; diff --git a/src/automation/dtos/space.param.dto.ts b/src/automation/dtos/space.param.dto.ts new file mode 100644 index 0000000..0631ea1 --- /dev/null +++ b/src/automation/dtos/space.param.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; + +export class SpaceParamDto { + @ApiProperty({ + description: 'UUID of the space', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + spaceUuid: string; +} diff --git a/src/automation/services/automation.service.ts b/src/automation/services/automation.service.ts index 95d9459..88f99ad 100644 --- a/src/automation/services/automation.service.ts +++ b/src/automation/services/automation.service.ts @@ -7,6 +7,7 @@ import { import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddAutomationDto, + DeleteAutomationParamDto, UpdateAutomationDto, UpdateAutomationStatusDto, } from '../dtos'; @@ -217,9 +218,9 @@ export class AutomationService { } } } - async getAutomationDetails(automationId: string, withSpaceId = false) { + async getAutomationDetails(automationUuid: string, withSpaceId = false) { try { - const path = `/v2.0/cloud/scene/rule/${automationId}`; + const path = `/v2.0/cloud/scene/rule/${automationUuid}`; const response = await this.tuya.request({ method: 'GET', path, @@ -305,15 +306,12 @@ export class AutomationService { } } - async deleteAutomation( - unitUuid: string, - automationId: string, - spaceTuyaId = null, - ) { + async deleteAutomation(param: DeleteAutomationParamDto, spaceTuyaId = null) { try { + const { automationUuid, spaceUuid } = param; let unitSpaceTuyaId; if (!spaceTuyaId) { - const unitDetails = await this.getUnitByUuid(unitUuid); + const unitDetails = await this.getUnitByUuid(spaceUuid); unitSpaceTuyaId = unitDetails.spaceTuyaUuid; if (!unitSpaceTuyaId) { throw new BadRequestException('Invalid unit UUID'); @@ -322,7 +320,7 @@ export class AutomationService { unitSpaceTuyaId = spaceTuyaId; } - const path = `/v2.0/cloud/scene/rule?ids=${automationId}&space_id=${unitSpaceTuyaId}`; + const path = `/v2.0/cloud/scene/rule?ids=${automationUuid}&space_id=${unitSpaceTuyaId}`; const response: DeleteAutomationInterface = await this.tuya.request({ method: 'DELETE', path, @@ -347,10 +345,10 @@ export class AutomationService { async updateAutomation( updateAutomationDto: UpdateAutomationDto, - automationId: string, + automationUuid: string, ) { try { - const spaceTuyaId = await this.getAutomationDetails(automationId, true); + const spaceTuyaId = await this.getAutomationDetails(automationUuid, true); if (!spaceTuyaId.spaceId) { throw new HttpException( "Automation doesn't exist", @@ -365,8 +363,12 @@ export class AutomationService { addAutomation, spaceTuyaId.spaceId, ); + const params: DeleteAutomationParamDto = { + spaceUuid: spaceTuyaId.spaceId, + automationUuid: automationUuid, + }; if (newAutomation.id) { - await this.deleteAutomation(null, automationId, spaceTuyaId.spaceId); + await this.deleteAutomation(null, params); return newAutomation; } } catch (err) { @@ -382,7 +384,7 @@ export class AutomationService { } async updateAutomationStatus( updateAutomationStatusDto: UpdateAutomationStatusDto, - automationId: string, + automationUuid: string, ) { try { const unitDetails = await this.getUnitByUuid( @@ -397,7 +399,7 @@ export class AutomationService { method: 'PUT', path, body: { - ids: automationId, + ids: automationUuid, is_enable: updateAutomationStatusDto.isEnable, }, }); diff --git a/src/scene/controllers/scene.controller.ts b/src/scene/controllers/scene.controller.ts index 1186433..408a9d9 100644 --- a/src/scene/controllers/scene.controller.ts +++ b/src/scene/controllers/scene.controller.ts @@ -14,6 +14,7 @@ import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { AddSceneTapToRunDto, UpdateSceneTapToRunDto } from '../dtos/scene.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { DeleteSceneParamDto, SceneParamDto, SpaceParamDto } from '../dtos'; @ApiTags('Scene Module') @Controller({ @@ -36,22 +37,22 @@ export class SceneController { data: tapToRunScene, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get('tap-to-run/:unitUuid') - async getTapToRunSceneByUnit(@Param('unitUuid') unitUuid: string) { - const tapToRunScenes = - await this.sceneService.getTapToRunSceneByUnit(unitUuid); + @Get('tap-to-run/:spaceUuid') + async getTapToRunSceneByUnit(@Param() param: SpaceParamDto) { + const tapToRunScenes = await this.sceneService.getTapToRunSceneByUnit( + param.spaceUuid, + ); return tapToRunScenes; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Delete('tap-to-run/:unitUuid/:sceneId') - async deleteTapToRunScene( - @Param('unitUuid') unitUuid: string, - @Param('sceneId') sceneId: string, - ) { - await this.sceneService.deleteTapToRunScene(unitUuid, sceneId); + async deleteTapToRunScene(@Param() param: DeleteSceneParamDto) { + await this.sceneService.deleteTapToRunScene(param); return { statusCode: HttpStatus.OK, message: 'Scene Deleted Successfully', @@ -60,8 +61,8 @@ export class SceneController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('tap-to-run/trigger/:sceneId') - async triggerTapToRunScene(@Param('sceneId') sceneId: string) { - await this.sceneService.triggerTapToRunScene(sceneId); + async triggerTapToRunScene(@Param() param: SceneParamDto) { + await this.sceneService.triggerTapToRunScene(param.sceneUuid); return { statusCode: HttpStatus.CREATED, success: true, @@ -72,21 +73,23 @@ export class SceneController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('tap-to-run/details/:sceneId') - async getTapToRunSceneDetails(@Param('sceneId') sceneId: string) { - const tapToRunScenes = - await this.sceneService.getTapToRunSceneDetails(sceneId); + async getTapToRunSceneDetails(@Param() param: SceneParamDto) { + const tapToRunScenes = await this.sceneService.getTapToRunSceneDetails( + param.sceneUuid, + ); return tapToRunScenes; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('tap-to-run/:sceneId') async updateTapToRunScene( @Body() updateSceneTapToRunDto: UpdateSceneTapToRunDto, - @Param('sceneId') sceneId: string, + @Param() param: SceneParamDto, ) { const tapToRunScene = await this.sceneService.updateTapToRunScene( updateSceneTapToRunDto, - sceneId, + param.sceneUuid, ); return { statusCode: HttpStatus.CREATED, diff --git a/src/scene/dtos/delete.scene.param.dto.ts b/src/scene/dtos/delete.scene.param.dto.ts new file mode 100644 index 0000000..5b71956 --- /dev/null +++ b/src/scene/dtos/delete.scene.param.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; + +export class DeleteSceneParamDto { + @ApiProperty({ + description: 'UUID of the Space', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + spaceUuid: string; + + @ApiProperty({ + description: 'UUID of the Scene', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + sceneUuid: string; +} diff --git a/src/scene/dtos/index.ts b/src/scene/dtos/index.ts index b1ca9c8..784bd90 100644 --- a/src/scene/dtos/index.ts +++ b/src/scene/dtos/index.ts @@ -1 +1,4 @@ export * from './scene.dto'; +export * from './space.param.dto'; +export * from './scene.param.dto'; +export * from './delete.scene.param.dto'; diff --git a/src/scene/dtos/scene.param.dto.ts b/src/scene/dtos/scene.param.dto.ts new file mode 100644 index 0000000..1163755 --- /dev/null +++ b/src/scene/dtos/scene.param.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; + +export class SceneParamDto { + @ApiProperty({ + description: 'UUID of the Scene', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + sceneUuid: string; +} diff --git a/src/scene/dtos/space.param.dto.ts b/src/scene/dtos/space.param.dto.ts new file mode 100644 index 0000000..0631ea1 --- /dev/null +++ b/src/scene/dtos/space.param.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; + +export class SpaceParamDto { + @ApiProperty({ + description: 'UUID of the space', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + spaceUuid: string; +} diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 5615011..50a55e1 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -5,7 +5,11 @@ import { BadRequestException, } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { AddSceneTapToRunDto, UpdateSceneTapToRunDto } from '../dtos'; +import { + AddSceneTapToRunDto, + DeleteSceneParamDto, + UpdateSceneTapToRunDto, +} from '../dtos'; import { ConfigService } from '@nestjs/config'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { convertKeysToSnakeCase } from '@app/common/helper/snakeCaseConverter'; @@ -44,7 +48,7 @@ export class SceneService { try { let unitSpaceTuyaId; if (!spaceTuyaId) { - const unitDetails = await this.getUnitByUuid( + const unitDetails = await this.getSpaceByUuid( addSceneTapToRunDto.unitUuid, ); unitSpaceTuyaId = unitDetails.spaceTuyaUuid; @@ -102,34 +106,37 @@ export class SceneService { } } } - async getUnitByUuid(unitUuid: string) { + async getSpaceByUuid(spaceUuid: string) { try { - const unit = await this.spaceRepository.findOne({ + const space = await this.spaceRepository.findOne({ where: { - uuid: unitUuid, + uuid: spaceUuid, }, }); - if (!unit) { - throw new BadRequestException('Invalid unit UUID'); + if (!space) { + throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); } return { - uuid: unit.uuid, - createdAt: unit.createdAt, - updatedAt: unit.updatedAt, - name: unit.spaceName, - spaceTuyaUuid: unit.spaceTuyaUuid, + uuid: space.uuid, + createdAt: space.createdAt, + updatedAt: space.updatedAt, + name: space.spaceName, + spaceTuyaUuid: space.spaceTuyaUuid, }; } catch (err) { if (err instanceof BadRequestException) { throw err; // Re-throw BadRequestException } else { - throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + throw new HttpException( + `Space with id ${spaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); } } } - async getTapToRunSceneByUnit(unitUuid: string) { + async getTapToRunSceneByUnit(spaceUuid: string) { try { - const unit = await this.getUnitByUuid(unitUuid); + const unit = await this.getSpaceByUuid(spaceUuid); if (!unit.spaceTuyaUuid) { throw new BadRequestException('Invalid unit UUID'); } @@ -165,15 +172,12 @@ export class SceneService { } } } - async deleteTapToRunScene( - unitUuid: string, - sceneId: string, - spaceTuyaId = null, - ) { + async deleteTapToRunScene(param: DeleteSceneParamDto, spaceTuyaId = null) { + const { spaceUuid, sceneUuid } = param; try { let unitSpaceTuyaId; if (!spaceTuyaId) { - const unitDetails = await this.getUnitByUuid(unitUuid); + const unitDetails = await this.getSpaceByUuid(spaceUuid); unitSpaceTuyaId = unitDetails.spaceTuyaUuid; if (!unitSpaceTuyaId) { throw new BadRequestException('Invalid unit UUID'); @@ -182,7 +186,7 @@ export class SceneService { unitSpaceTuyaId = spaceTuyaId; } - const path = `/v2.0/cloud/scene/rule?ids=${sceneId}&space_id=${unitSpaceTuyaId}`; + const path = `/v2.0/cloud/scene/rule?ids=${sceneUuid}&space_id=${unitSpaceTuyaId}`; const response: DeleteTapToRunSceneInterface = await this.tuya.request({ method: 'DELETE', path, @@ -204,6 +208,7 @@ export class SceneService { } } } + async triggerTapToRunScene(sceneId: string) { try { const path = `/v2.0/cloud/scene/rule/${sceneId}/actions/trigger`; @@ -317,10 +322,10 @@ export class SceneService { } async updateTapToRunScene( updateSceneTapToRunDto: UpdateSceneTapToRunDto, - sceneId: string, + sceneUuid: string, ) { try { - const spaceTuyaId = await this.getTapToRunSceneDetails(sceneId, true); + const spaceTuyaId = await this.getTapToRunSceneDetails(sceneUuid, true); if (!spaceTuyaId.spaceId) { throw new HttpException("Scene doesn't exist", HttpStatus.NOT_FOUND); } @@ -332,8 +337,14 @@ export class SceneService { addSceneTapToRunDto, spaceTuyaId.spaceId, ); + + const param: DeleteSceneParamDto = { + spaceUuid: spaceTuyaId.spaceId, + sceneUuid, + }; + if (newTapToRunScene.id) { - await this.deleteTapToRunScene(null, sceneId, spaceTuyaId.spaceId); + await this.deleteTapToRunScene(null, param); return newTapToRunScene; } } catch (err) { diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 8ca4ae3..6cf128b 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -5,6 +5,7 @@ import { SpaceController, SpaceUserController, SubSpaceController, + SubSpaceDeviceController, } from './controllers'; import { SpaceService, @@ -29,7 +30,7 @@ import { DeviceRepository } from '@app/common/modules/device/repositories'; SpaceController, SpaceUserController, SubSpaceController, - SubSpaceController, + SubSpaceDeviceController, ], providers: [ SpaceService, From adacc60a401e65dc6bdc13e1bce52519b43e1c2c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 30 Oct 2024 19:07:26 +0400 Subject: [PATCH 027/109] get devices --- libs/common/src/constants/controller-route.ts | 11 ++ .../src/integrations/tuya/tuya.service.ts | 17 +++ .../modules/device/entities/device.entity.ts | 2 +- .../modules/space/entities/space.entity.ts | 2 +- src/automation/dtos/automation.dto.ts | 8 +- src/automation/services/automation.service.ts | 55 +++++---- src/device/services/device.service.ts | 6 +- src/group/services/group.service.ts | 20 ++-- src/guards/unit.type.guard.ts | 10 +- src/scene/dtos/scene.dto.ts | 4 +- src/scene/services/scene.service.ts | 32 ++--- .../controllers/space-device.controller.ts | 30 +++++ src/space/services/space-device.service.ts | 112 ++++++++++++++++++ .../subspace/subspace-device.service.ts | 62 +++++++++- src/space/space.module.ts | 4 + 15 files changed, 306 insertions(+), 69 deletions(-) create mode 100644 src/space/controllers/space-device.controller.ts create mode 100644 src/space/services/space-device.service.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index a3c6ffb..2bca1a1 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -136,6 +136,17 @@ export class ControllerRoute { }; }; + static SPACE_DEVICES = class { + public static readonly ROUTE = + '/communities/:communityUuid/spaces/:spaceUuid/devices'; + static ACTIONS = class { + public static readonly LIST_SPACE_DEVICE_SUMMARY = + 'List devices in a space'; + public static readonly LIST_SPACE_DEVICE_DESCRIPTION = + 'Retrieves a list of all devices associated with a specified space.'; + }; + }; + static SUBSPACE = class { public static readonly ROUTE = '/communities/:communityUuid/spaces/:spaceUuid/subspaces'; diff --git a/libs/common/src/integrations/tuya/tuya.service.ts b/libs/common/src/integrations/tuya/tuya.service.ts index 48d8ebe..59efe6f 100644 --- a/libs/common/src/integrations/tuya/tuya.service.ts +++ b/libs/common/src/integrations/tuya/tuya.service.ts @@ -35,4 +35,21 @@ export class TuyaService { ); } } + + async getDeviceDetails(deviceId: string) { + const path = `/v1.1/iot-03/devices/${deviceId}`; + const response = await this.tuya.request({ + method: 'GET', + path, + }); + + if (!response.success) { + throw new HttpException( + `Error fetching device details: ${response.msg}`, + HttpStatus.BAD_REQUEST, + ); + } + + return response.result; + } } diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index fd32ea3..727e290 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -42,7 +42,7 @@ export class DeviceEntity extends AbstractEntity { ) deviceUserNotification: DeviceNotificationEntity[]; - @ManyToOne(() => SpaceEntity, (space) => space.devicesSpaceEntity, { + @ManyToOne(() => SpaceEntity, (space) => space.devices, { nullable: true, }) spaceDevice: SpaceEntity; diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index f9e8290..196a522 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -81,7 +81,7 @@ export class SpaceEntity extends AbstractEntity { () => DeviceEntity, (devicesSpaceEntity) => devicesSpaceEntity.spaceDevice, ) - devicesSpaceEntity: DeviceEntity[]; + devices: DeviceEntity[]; constructor(partial: Partial) { super(); diff --git a/src/automation/dtos/automation.dto.ts b/src/automation/dtos/automation.dto.ts index 05d2936..60104ac 100644 --- a/src/automation/dtos/automation.dto.ts +++ b/src/automation/dtos/automation.dto.ts @@ -114,10 +114,10 @@ class Action { } export class AddAutomationDto { - @ApiProperty({ description: 'Unit ID', required: true }) + @ApiProperty({ description: 'Space ID', required: true }) @IsString() @IsNotEmpty() - public unitUuid: string; + public spaceUuid: string; @ApiProperty({ description: 'Automation name', required: true }) @IsString() @@ -197,10 +197,10 @@ export class UpdateAutomationDto { } } export class UpdateAutomationStatusDto { - @ApiProperty({ description: 'Unit uuid', required: true }) + @ApiProperty({ description: 'Space uuid', required: true }) @IsString() @IsNotEmpty() - public unitUuid: string; + public spaceUuid: string; @ApiProperty({ description: 'Is enable', required: true }) @IsBoolean() diff --git a/src/automation/services/automation.service.ts b/src/automation/services/automation.service.ts index 88f99ad..f25f7c1 100644 --- a/src/automation/services/automation.service.ts +++ b/src/automation/services/automation.service.ts @@ -50,11 +50,15 @@ export class AutomationService { try { let unitSpaceTuyaId; if (!spaceTuyaId) { - const unitDetails = await this.getUnitByUuid(addAutomationDto.unitUuid); + const unitDetails = await this.getUnitByUuid( + addAutomationDto.spaceUuid, + ); unitSpaceTuyaId = unitDetails.spaceTuyaUuid; if (!unitDetails) { - throw new BadRequestException('Invalid unit UUID'); + throw new BadRequestException( + `Invalid space UUID ${addAutomationDto.spaceUuid}`, + ); } } else { unitSpaceTuyaId = spaceTuyaId; @@ -125,22 +129,23 @@ export class AutomationService { } } } - async getUnitByUuid(unitUuid: string) { + async getUnitByUuid(spaceUuid: string) { try { - const unit = await this.spaceRepository.findOne({ + const space = await this.spaceRepository.findOne({ where: { - uuid: unitUuid, + uuid: spaceUuid, }, + relations: ['community'], }); - if (!unit) { - throw new BadRequestException('Invalid unit UUID'); + if (!space) { + throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); } return { - uuid: unit.uuid, - createdAt: unit.createdAt, - updatedAt: unit.updatedAt, - name: unit.spaceName, - spaceTuyaUuid: unit.spaceTuyaUuid, + uuid: space.uuid, + createdAt: space.createdAt, + updatedAt: space.updatedAt, + name: space.spaceName, + spaceTuyaUuid: space.community.externalId, }; } catch (err) { if (err instanceof BadRequestException) { @@ -150,11 +155,11 @@ export class AutomationService { } } } - async getAutomationByUnit(unitUuid: string) { + async getAutomationByUnit(spaceUuid: string) { try { - const unit = await this.getUnitByUuid(unitUuid); + const unit = await this.getUnitByUuid(spaceUuid); if (!unit.spaceTuyaUuid) { - throw new BadRequestException('Invalid unit UUID'); + throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); } const path = `/v2.0/cloud/scene/rule?space_id=${unit.spaceTuyaUuid}&type=automation`; @@ -311,10 +316,10 @@ export class AutomationService { const { automationUuid, spaceUuid } = param; let unitSpaceTuyaId; if (!spaceTuyaId) { - const unitDetails = await this.getUnitByUuid(spaceUuid); - unitSpaceTuyaId = unitDetails.spaceTuyaUuid; + const space = await this.getUnitByUuid(spaceUuid); + unitSpaceTuyaId = space.spaceTuyaUuid; if (!unitSpaceTuyaId) { - throw new BadRequestException('Invalid unit UUID'); + throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); } } else { unitSpaceTuyaId = spaceTuyaId; @@ -357,7 +362,7 @@ export class AutomationService { } const addAutomation = { ...updateAutomationDto, - unitUuid: null, + spaceUuid: null, }; const newAutomation = await this.addAutomation( addAutomation, @@ -387,14 +392,16 @@ export class AutomationService { automationUuid: string, ) { try { - const unitDetails = await this.getUnitByUuid( - updateAutomationStatusDto.unitUuid, + const space = await this.getUnitByUuid( + updateAutomationStatusDto.spaceUuid, ); - if (!unitDetails.spaceTuyaUuid) { - throw new BadRequestException('Invalid unit UUID'); + if (!space.spaceTuyaUuid) { + throw new BadRequestException( + `Invalid space UUID ${updateAutomationStatusDto.spaceUuid}`, + ); } - const path = `/v2.0/cloud/scene/rule/state?space_id=${unitDetails.spaceTuyaUuid}`; + const path = `/v2.0/cloud/scene/rule/state?space_id=${space.spaceTuyaUuid}`; const response: DeleteAutomationInterface = await this.tuya.request({ method: 'PUT', path, diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index bf8edab..a93e52d 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -792,15 +792,15 @@ export class DeviceService { parent: { uuid: SpaceUuid, }, - devicesSpaceEntity: { + devices: { isActive: true, }, }, - relations: ['devicesSpaceEntity', 'devicesSpaceEntity.productDevice'], + relations: ['devices', 'devices.productDevice'], }); const devices = spaces.flatMap((space) => { - return space.devicesSpaceEntity.map((device) => device); + return space.devices.map((device) => device); }); const devicesData = await Promise.all( diff --git a/src/group/services/group.service.ts b/src/group/services/group.service.ts index f549afc..d84edea 100644 --- a/src/group/services/group.service.ts +++ b/src/group/services/group.service.ts @@ -35,13 +35,11 @@ export class GroupService { uuid: unitUuid, }, }, - relations: ['devicesSpaceEntity', 'devicesSpaceEntity.productDevice'], + relations: ['devices', 'devices.productDevice'], }); const groupNames = spaces.flatMap((space) => { - return space.devicesSpaceEntity.map( - (device) => device.productDevice.prodType, - ); + return space.devices.map((device) => device.productDevice.prodType); }); const uniqueGroupNames = [...new Set(groupNames)]; @@ -82,7 +80,7 @@ export class GroupService { parent: { uuid: unitUuid, }, - devicesSpaceEntity: { + devices: { productDevice: { prodType: groupName, }, @@ -95,18 +93,18 @@ export class GroupService { }, }, relations: [ - 'devicesSpaceEntity', - 'devicesSpaceEntity.productDevice', - 'devicesSpaceEntity.spaceDevice', - 'devicesSpaceEntity.permission', - 'devicesSpaceEntity.permission.permissionType', + 'devices', + 'devices.productDevice', + 'devices.spaceDevice', + 'devices.permission', + 'devices.permission.permissionType', ], }); const devices = await Promise.all( spaces.flatMap(async (space) => { return await Promise.all( - space.devicesSpaceEntity.map(async (device) => { + space.devices.map(async (device) => { const deviceDetails = await this.getDeviceDetailsByDeviceIdTuya( device.deviceTuyaUuid, ); diff --git a/src/guards/unit.type.guard.ts b/src/guards/unit.type.guard.ts index d1fa50b..08f0d09 100644 --- a/src/guards/unit.type.guard.ts +++ b/src/guards/unit.type.guard.ts @@ -33,13 +33,13 @@ export class CheckUnitTypeGuard implements CanActivate { } } - async checkFloorIsFloorType(unitUuid: string) { - const unitData = await this.spaceRepository.findOne({ - where: { uuid: unitUuid }, + async checkFloorIsFloorType(spaceUuid: string) { + const space = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid }, }); - if (!unitData) { - throw new BadRequestException('Invalid unit UUID'); + if (!space) { + throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); } } diff --git a/src/scene/dtos/scene.dto.ts b/src/scene/dtos/scene.dto.ts index ca39212..b508fa6 100644 --- a/src/scene/dtos/scene.dto.ts +++ b/src/scene/dtos/scene.dto.ts @@ -64,12 +64,12 @@ class Action { export class AddSceneTapToRunDto { @ApiProperty({ - description: 'Unit UUID', + description: 'Space UUID', required: true, }) @IsString() @IsNotEmpty() - public unitUuid: string; + public spaceUuid: string; @ApiProperty({ description: 'Scene name', diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 50a55e1..155f60f 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -48,12 +48,12 @@ export class SceneService { try { let unitSpaceTuyaId; if (!spaceTuyaId) { - const unitDetails = await this.getSpaceByUuid( - addSceneTapToRunDto.unitUuid, - ); - unitSpaceTuyaId = unitDetails.spaceTuyaUuid; - if (!unitDetails) { - throw new BadRequestException('Invalid unit UUID'); + const space = await this.getSpaceByUuid(addSceneTapToRunDto.spaceUuid); + unitSpaceTuyaId = space.spaceTuyaUuid; + if (!space) { + throw new BadRequestException( + `Invalid space UUID ${addSceneTapToRunDto.spaceUuid}`, + ); } } else { unitSpaceTuyaId = spaceTuyaId; @@ -112,6 +112,7 @@ export class SceneService { where: { uuid: spaceUuid, }, + relations: ['community'], }); if (!space) { throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); @@ -121,10 +122,11 @@ export class SceneService { createdAt: space.createdAt, updatedAt: space.updatedAt, name: space.spaceName, - spaceTuyaUuid: space.spaceTuyaUuid, + spaceTuyaUuid: space.community.externalId, }; } catch (err) { if (err instanceof BadRequestException) { + console.log(err); throw err; // Re-throw BadRequestException } else { throw new HttpException( @@ -136,12 +138,12 @@ export class SceneService { } async getTapToRunSceneByUnit(spaceUuid: string) { try { - const unit = await this.getSpaceByUuid(spaceUuid); - if (!unit.spaceTuyaUuid) { - throw new BadRequestException('Invalid unit UUID'); + const space = await this.getSpaceByUuid(spaceUuid); + if (!space.spaceTuyaUuid) { + throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); } - const path = `/v2.0/cloud/scene/rule?space_id=${unit.spaceTuyaUuid}&type=scene`; + const path = `/v2.0/cloud/scene/rule?space_id=${space.spaceTuyaUuid}&type=scene`; const response: GetTapToRunSceneByUnitInterface = await this.tuya.request( { method: 'GET', @@ -177,10 +179,10 @@ export class SceneService { try { let unitSpaceTuyaId; if (!spaceTuyaId) { - const unitDetails = await this.getSpaceByUuid(spaceUuid); - unitSpaceTuyaId = unitDetails.spaceTuyaUuid; + const space = await this.getSpaceByUuid(spaceUuid); + unitSpaceTuyaId = space.spaceTuyaUuid; if (!unitSpaceTuyaId) { - throw new BadRequestException('Invalid unit UUID'); + throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); } } else { unitSpaceTuyaId = spaceTuyaId; @@ -331,7 +333,7 @@ export class SceneService { } const addSceneTapToRunDto: AddSceneTapToRunDto = { ...updateSceneTapToRunDto, - unitUuid: null, + spaceUuid: null, }; const newTapToRunScene = await this.addTapToRunScene( addSceneTapToRunDto, diff --git a/src/space/controllers/space-device.controller.ts b/src/space/controllers/space-device.controller.ts new file mode 100644 index 0000000..cb0aad3 --- /dev/null +++ b/src/space/controllers/space-device.controller.ts @@ -0,0 +1,30 @@ +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { Controller, Get, Param, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { SubspaceDeviceService } from '../services/space-device.service'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { GetSpaceParam } from '../dtos'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; + +@ApiTags('Space Module') +@Controller({ + version: '1', + path: ControllerRoute.SPACE_DEVICES.ROUTE, +}) +export class SubSpaceDeviceController { + constructor(private readonly spaceDeviceService: SubspaceDeviceService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SPACE_DEVICES.ACTIONS.LIST_SPACE_DEVICE_SUMMARY, + description: + ControllerRoute.SPACE_DEVICES.ACTIONS.LIST_SPACE_DEVICE_DESCRIPTION, + }) + @Get() + async listDevicesInSpace( + @Param() params: GetSpaceParam, + ): Promise { + return await this.spaceDeviceService.listDevicesInSpace(params); + } +} diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts new file mode 100644 index 0000000..eba64a3 --- /dev/null +++ b/src/space/services/space-device.service.ts @@ -0,0 +1,112 @@ +import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { GetDeviceDetailsInterface } from 'src/device/interfaces/get.device.interface'; +import { GetSpaceParam } from '../dtos'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; +import { ProductRepository } from '@app/common/modules/product/repositories'; + +@Injectable() +export class SubspaceDeviceService { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly tuyaService: TuyaService, + private readonly productRepository: ProductRepository, + private readonly communityRepository: CommunityRepository, + ) {} + + async listDevicesInSpace(params: GetSpaceParam): Promise { + const { spaceUuid, communityUuid } = params; + + const space = await this.validateCommunityAndSpace( + communityUuid, + spaceUuid, + ); + + const detailedDevices = await Promise.all( + space.devices.map(async (device) => { + const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya( + device.deviceTuyaUuid, + ); + + return { + uuid: device.uuid, + deviceTuyaUuid: device.deviceTuyaUuid, + productUuid: device.productDevice.uuid, + productType: device.productDevice.prodType, + isActive: device.isActive, + createdAt: device.createdAt, + updatedAt: device.updatedAt, + ...tuyaDetails, + }; + }), + ); + + return new SuccessResponseDto({ + data: detailedDevices, + message: 'Successfully retrieved list of devices', + }); + } + + private async validateCommunityAndSpace( + communityUuid: string, + spaceUuid: string, + ) { + const community = await this.communityRepository.findOne({ + where: { uuid: communityUuid }, + }); + if (!community) { + this.throwNotFound('Community', communityUuid); + } + + const space = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid, community: { uuid: communityUuid } }, + relations: ['devices', 'devices.productDevice'], + }); + if (!space) { + this.throwNotFound('Space', spaceUuid); + } + return space; + } + + private throwNotFound(entity: string, uuid: string) { + throw new HttpException( + `${entity} with ID ${uuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + private async getDeviceDetailsByDeviceIdTuya( + deviceId: string, + ): Promise { + try { + // Fetch details from TuyaService + const tuyaDeviceDetails = + await this.tuyaService.getDeviceDetails(deviceId); + + // Convert keys to camel case + const camelCaseResponse = convertKeysToCamelCase(tuyaDeviceDetails); + + const product = await this.productRepository.findOne({ + where: { + prodId: camelCaseResponse.productId, + }, + }); + + // Exclude specific keys and add `productUuid` + const { ...rest } = camelCaseResponse; + return { + ...rest, + productUuid: product?.uuid, + } as GetDeviceDetailsInterface; + } catch (error) { + throw new HttpException( + 'Error fetching device details from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index 6b02bd9..b7a18b3 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -8,6 +8,10 @@ import { import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos'; import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; +import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { ProductRepository } from '@app/common/modules/product/repositories'; +import { GetDeviceDetailsInterface } from '../../../device/interfaces/get.device.interface'; @Injectable() export class SubspaceDeviceService { @@ -16,6 +20,8 @@ export class SubspaceDeviceService { private readonly communityRepository: CommunityRepository, private readonly subspaceRepository: SubspaceRepository, private readonly deviceRepository: DeviceRepository, + private readonly tuyaService: TuyaService, + private readonly productRepository: ProductRepository, ) {} async listDevicesInSubspace( @@ -26,12 +32,31 @@ export class SubspaceDeviceService { await this.validateCommunityAndSpace(communityUuid, spaceUuid); const subspace = await this.findSubspaceWithDevices(subSpaceUuid); + + const detailedDevices = await Promise.all( + subspace.devices.map(async (device) => { + const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya( + device.deviceTuyaUuid, + ); + + return { + uuid: device.uuid, + deviceTuyaUuid: device.deviceTuyaUuid, + productUuid: device.productDevice.uuid, + productType: device.productDevice.prodType, + isActive: device.isActive, + createdAt: device.createdAt, + updatedAt: device.updatedAt, + ...tuyaDetails, + }; + }), + ); + return new SuccessResponseDto({ - data: subspace.devices, + data: detailedDevices, message: 'Successfully retrieved list of devices', }); } - async associateDeviceToSubspace(params: DeviceSubSpaceParam) { const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid } = params; @@ -91,7 +116,7 @@ export class SubspaceDeviceService { private async findSubspaceWithDevices(subSpaceUuid: string) { const subspace = await this.subspaceRepository.findOne({ where: { uuid: subSpaceUuid }, - relations: ['devices'], + relations: ['devices', 'devices.productDevice'], }); if (!subspace) { this.throwNotFound('Subspace', subSpaceUuid); @@ -125,4 +150,35 @@ export class SubspaceDeviceService { HttpStatus.NOT_FOUND, ); } + + private async getDeviceDetailsByDeviceIdTuya( + deviceId: string, + ): Promise { + try { + // Fetch details from TuyaService + const tuyaDeviceDetails = + await this.tuyaService.getDeviceDetails(deviceId); + + // Convert keys to camel case + const camelCaseResponse = convertKeysToCamelCase(tuyaDeviceDetails); + + const product = await this.productRepository.findOne({ + where: { + prodId: camelCaseResponse.productId, + }, + }); + + // Exclude specific keys and add `productUuid` + const { ...rest } = camelCaseResponse; + return { + ...rest, + productUuid: product?.uuid, + } as GetDeviceDetailsInterface; + } catch (error) { + throw new HttpException( + 'Error fetching device details from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 6cf128b..7d8c7d9 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -23,6 +23,8 @@ import { UserSpaceRepository, } from '@app/common/modules/user/repositories'; import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { ProductRepository } from '@app/common/modules/product/repositories'; @Module({ imports: [ConfigModule, SpaceRepositoryModule], @@ -34,6 +36,8 @@ import { DeviceRepository } from '@app/common/modules/device/repositories'; ], providers: [ SpaceService, + TuyaService, + ProductRepository, SubSpaceService, SubspaceDeviceService, SpaceRepository, From 5a5911ca8af4507dd8ef5db88958c0f1e3552393 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 30 Oct 2024 19:10:13 +0400 Subject: [PATCH 028/109] space device controller --- src/space/controllers/index.ts | 1 + src/space/controllers/space-device.controller.ts | 6 +++--- src/space/services/index.ts | 1 + src/space/services/space-device.service.ts | 2 +- src/space/space.module.ts | 4 ++++ 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/space/controllers/index.ts b/src/space/controllers/index.ts index dff9330..f3b360e 100644 --- a/src/space/controllers/index.ts +++ b/src/space/controllers/index.ts @@ -1,3 +1,4 @@ export * from './space.controller'; export * from './space-user.controller'; +export * from './space-device.controller'; export * from './subspace'; diff --git a/src/space/controllers/space-device.controller.ts b/src/space/controllers/space-device.controller.ts index cb0aad3..160a130 100644 --- a/src/space/controllers/space-device.controller.ts +++ b/src/space/controllers/space-device.controller.ts @@ -1,18 +1,18 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; import { Controller, Get, Param, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; -import { SubspaceDeviceService } from '../services/space-device.service'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { GetSpaceParam } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { SpaceDeviceService } from '../services'; @ApiTags('Space Module') @Controller({ version: '1', path: ControllerRoute.SPACE_DEVICES.ROUTE, }) -export class SubSpaceDeviceController { - constructor(private readonly spaceDeviceService: SubspaceDeviceService) {} +export class SpaceDeviceController { + constructor(private readonly spaceDeviceService: SpaceDeviceService) {} @ApiBearerAuth() @UseGuards(JwtAuthGuard) diff --git a/src/space/services/index.ts b/src/space/services/index.ts index 42e2621..97cfe42 100644 --- a/src/space/services/index.ts +++ b/src/space/services/index.ts @@ -1,3 +1,4 @@ export * from './space.service'; export * from './space-user.service'; +export * from './space-device.service'; export * from './subspace'; diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index eba64a3..9701c6f 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -10,7 +10,7 @@ import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { ProductRepository } from '@app/common/modules/product/repositories'; @Injectable() -export class SubspaceDeviceService { +export class SpaceDeviceService { constructor( private readonly spaceRepository: SpaceRepository, private readonly tuyaService: TuyaService, diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 7d8c7d9..5eb7af8 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -3,11 +3,13 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { SpaceController, + SpaceDeviceController, SpaceUserController, SubSpaceController, SubSpaceDeviceController, } from './controllers'; import { + SpaceDeviceService, SpaceService, SpaceUserService, SubspaceDeviceService, @@ -31,6 +33,7 @@ import { ProductRepository } from '@app/common/modules/product/repositories'; controllers: [ SpaceController, SpaceUserController, + SpaceDeviceController, SubSpaceController, SubSpaceDeviceController, ], @@ -39,6 +42,7 @@ import { ProductRepository } from '@app/common/modules/product/repositories'; TuyaService, ProductRepository, SubSpaceService, + SpaceDeviceService, SubspaceDeviceService, SpaceRepository, DeviceRepository, From 1f0cedfd0931e3c8dadb5f33ba229c67783e3348 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 30 Oct 2024 19:26:27 +0400 Subject: [PATCH 029/109] removed guard --- src/group/controllers/group.controller.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/group/controllers/group.controller.ts b/src/group/controllers/group.controller.ts index 85a4a69..0c2c8b8 100644 --- a/src/group/controllers/group.controller.ts +++ b/src/group/controllers/group.controller.ts @@ -14,13 +14,13 @@ export class GroupController { constructor(private readonly groupService: GroupService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard, UnitPermissionGuard) - @Get(':unitUuid') - async getGroupsBySpaceUuid(@Param('unitUuid') unitUuid: string) { - return await this.groupService.getGroupsByUnitUuid(unitUuid); + @UseGuards(JwtAuthGuard) + @Get(':spaceUuid') + async getGroupsBySpaceUuid(@Param('spaceUuid') spaceUuid: string) { + return await this.groupService.getGroupsByUnitUuid(spaceUuid); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard, UnitPermissionGuard) + @UseGuards(JwtAuthGuard) @Get(':unitUuid/devices/:groupName') async getUnitDevicesByGroupName( @Param('unitUuid') unitUuid: string, From b0ea7cdc25a3d959bb3af422b3dae7c34c3c077b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 30 Oct 2024 22:47:03 +0400 Subject: [PATCH 030/109] removed guards --- src/device/controllers/device.controller.ts | 10 ++++---- src/device/services/device.service.ts | 24 +++++++++---------- src/group/controllers/group.controller.ts | 1 + src/group/services/group.service.ts | 4 +--- .../subspace/subspace-device.service.ts | 5 +--- 5 files changed, 18 insertions(+), 26 deletions(-) diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index e2aa32c..bcffdc6 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -21,8 +21,6 @@ import { BatchFactoryResetDevicesDto, } from '../dtos/control.device.dto'; import { CheckRoomGuard } from 'src/guards/room.guard'; -import { CheckUserHavePermission } from 'src/guards/user.device.permission.guard'; -import { CheckUserHaveControllablePermission } from 'src/guards/user.device.controllable.permission.guard'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { CheckDeviceGuard } from 'src/guards/device.guard'; import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; @@ -80,7 +78,7 @@ export class DeviceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckUserHavePermission) + @UseGuards(JwtAuthGuard) @Get(':deviceUuid') async getDeviceDetailsByDeviceId( @Param('deviceUuid') deviceUuid: string, @@ -93,7 +91,7 @@ export class DeviceController { ); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckUserHavePermission) + @UseGuards(JwtAuthGuard) @Get(':deviceUuid/functions') async getDeviceInstructionByDeviceId( @Param('deviceUuid') deviceUuid: string, @@ -101,14 +99,14 @@ export class DeviceController { return await this.deviceService.getDeviceInstructionByDeviceId(deviceUuid); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckUserHavePermission) + @UseGuards(JwtAuthGuard) @Get(':deviceUuid/functions/status') async getDevicesInstructionStatus(@Param('deviceUuid') deviceUuid: string) { return await this.deviceService.getDevicesInstructionStatus(deviceUuid); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckUserHaveControllablePermission) + @UseGuards(JwtAuthGuard) @Post(':deviceUuid/control') async controlDevice( @Body() controlDeviceDto: ControlDeviceDto, diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index a93e52d..ec773ec 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -569,13 +569,12 @@ export class DeviceService { async getDeviceInstructionByDeviceId( deviceUuid: string, ): Promise { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + + if (!deviceDetails) { + throw new NotFoundException('Device Not Found'); + } try { - const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); - - if (!deviceDetails) { - throw new NotFoundException('Device Not Found'); - } - const response = await this.getDeviceInstructionByDeviceIdTuya( deviceDetails.deviceTuyaUuid, ); @@ -614,12 +613,12 @@ export class DeviceService { } } async getDevicesInstructionStatus(deviceUuid: string) { - try { - const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); - if (!deviceDetails) { - throw new NotFoundException('Device Not Found'); - } + if (!deviceDetails) { + throw new NotFoundException('Device Not Found'); + } + try { const deviceStatus = await this.getDevicesInstructionStatusTuya( deviceDetails.deviceTuyaUuid, ); @@ -630,6 +629,7 @@ export class DeviceService { status: deviceStatus.result[0].status, }; } catch (error) { + console.log(error); throw new HttpException( 'Error fetching device functions status', HttpStatus.INTERNAL_SERVER_ERROR, @@ -887,7 +887,6 @@ export class DeviceService { const spaceHierarchy = await this.getFullSpaceHierarchy( device?.spaceDevice, ); - console.log(device.spaceDevice.community); const orderedHierarchy = spaceHierarchy.reverse(); return { @@ -986,7 +985,6 @@ export class DeviceService { space: SpaceEntity, ): Promise<{ uuid: string; spaceName: string }[]> { try { - console.log('Fetching hierarchy for space:', space.uuid); // Fetch only the relevant spaces, starting with the target space const targetSpace = await this.spaceRepository.findOne({ diff --git a/src/group/controllers/group.controller.ts b/src/group/controllers/group.controller.ts index 0c2c8b8..ed79baf 100644 --- a/src/group/controllers/group.controller.ts +++ b/src/group/controllers/group.controller.ts @@ -19,6 +19,7 @@ export class GroupController { async getGroupsBySpaceUuid(@Param('spaceUuid') spaceUuid: string) { return await this.groupService.getGroupsByUnitUuid(spaceUuid); } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':unitUuid/devices/:groupName') diff --git a/src/group/services/group.service.ts b/src/group/services/group.service.ts index d84edea..e8e39f9 100644 --- a/src/group/services/group.service.ts +++ b/src/group/services/group.service.ts @@ -31,9 +31,7 @@ export class GroupService { try { const spaces = await this.spaceRepository.find({ where: { - parent: { - uuid: unitUuid, - }, + uuid: unitUuid, }, relations: ['devices', 'devices.productDevice'], }); diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index b7a18b3..9b2db68 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -155,11 +155,9 @@ export class SubspaceDeviceService { deviceId: string, ): Promise { try { - // Fetch details from TuyaService const tuyaDeviceDetails = await this.tuyaService.getDeviceDetails(deviceId); - // Convert keys to camel case const camelCaseResponse = convertKeysToCamelCase(tuyaDeviceDetails); const product = await this.productRepository.findOne({ @@ -168,8 +166,7 @@ export class SubspaceDeviceService { }, }); - // Exclude specific keys and add `productUuid` - const { ...rest } = camelCaseResponse; + const { uuid, ...rest } = camelCaseResponse; return { ...rest, productUuid: product?.uuid, From f1c07360cb443703a98bfe5cc2d5456393dc3d6f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 30 Oct 2024 23:50:52 +0400 Subject: [PATCH 031/109] updated spaceUuid --- .../modules/scene/entities/scene.entity.ts | 3 ++- src/automation/services/automation.service.ts | 4 ++-- src/scene/controllers/scene.controller.ts | 9 ++++---- src/scene/services/scene.service.ts | 22 ++++++++++++------- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/libs/common/src/modules/scene/entities/scene.entity.ts b/libs/common/src/modules/scene/entities/scene.entity.ts index 2ade9ad..1e04fd7 100644 --- a/libs/common/src/modules/scene/entities/scene.entity.ts +++ b/libs/common/src/modules/scene/entities/scene.entity.ts @@ -47,7 +47,8 @@ export class SceneEntity extends AbstractEntity { @Column({ nullable: false, }) - unitUuid: string; + spaceUuid: string; + @Column({ nullable: false, }) diff --git a/src/automation/services/automation.service.ts b/src/automation/services/automation.service.ts index f25f7c1..f0c236b 100644 --- a/src/automation/services/automation.service.ts +++ b/src/automation/services/automation.service.ts @@ -192,10 +192,10 @@ export class AutomationService { } } async getTapToRunSceneDetailsTuya( - sceneId: string, + sceneUuid: string, ): Promise { try { - const path = `/v2.0/cloud/scene/rule/${sceneId}`; + const path = `/v2.0/cloud/scene/rule/${sceneUuid}`; const response = await this.tuya.request({ method: 'GET', path, diff --git a/src/scene/controllers/scene.controller.ts b/src/scene/controllers/scene.controller.ts index ecdbe6d..27ae0c7 100644 --- a/src/scene/controllers/scene.controller.ts +++ b/src/scene/controllers/scene.controller.ts @@ -60,7 +60,7 @@ export class SceneController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Delete('tap-to-run/:unitUuid/:sceneId') + @Delete('tap-to-run/:unitUuid/:sceneUuid') async deleteTapToRunScene(@Param() param: DeleteSceneParamDto) { await this.sceneService.deleteTapToRunScene(param); return { @@ -70,7 +70,7 @@ export class SceneController { } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Post('tap-to-run/trigger/:sceneId') + @Post('tap-to-run/trigger/:sceneUuid') async triggerTapToRunScene(@Param() param: SceneParamDto) { await this.sceneService.triggerTapToRunScene(param.sceneUuid); return { @@ -82,7 +82,7 @@ export class SceneController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get('tap-to-run/details/:sceneId') + @Get('tap-to-run/details/:sceneUuid') async getTapToRunSceneDetails(@Param() param: SceneParamDto) { const tapToRunScenes = await this.sceneService.getTapToRunSceneDetails( param.sceneUuid, @@ -92,7 +92,7 @@ export class SceneController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Put('tap-to-run/:sceneId') + @Put('tap-to-run/:sceneUuid') async updateTapToRunScene( @Body() updateSceneTapToRunDto: UpdateSceneTapToRunDto, @Param() param: SceneParamDto, @@ -108,6 +108,7 @@ export class SceneController { data: tapToRunScene, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('icon') diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 566b526..61e6af4 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -113,7 +113,7 @@ export class SceneService { : defaultSceneIcon.uuid, }, showInHomePage: addSceneTapToRunDto.showInHomePage, - unitUuid: spaceUuid ? spaceUuid : addSceneTapToRunDto.spaceUuid, + spaceUuid: spaceUuid ? spaceUuid : addSceneTapToRunDto.spaceUuid, }); } return { @@ -124,7 +124,7 @@ export class SceneService { throw err; // Re-throw BadRequestException } else { throw new HttpException( - err.message || 'Scene not found', + err.message || `Scene not found`, err.status || HttpStatus.NOT_FOUND, ); } @@ -160,13 +160,14 @@ export class SceneService { } } } - async getTapToRunSceneByUnit(unitUuid: string, inHomePage: GetSceneDto) { + + async getTapToRunSceneByUnit(spaceUuid: string, inHomePage: GetSceneDto) { try { const showInHomePage = inHomePage?.showInHomePage; const scenesData = await this.sceneRepository.find({ where: { - unitUuid, + spaceUuid, ...(showInHomePage ? { showInHomePage } : {}), }, }); @@ -180,6 +181,7 @@ export class SceneService { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { actions, ...rest } = sceneData; return { + uuid: scene.uuid, ...rest, }; }), @@ -197,6 +199,7 @@ export class SceneService { } } } + async deleteTapToRunScene(param: DeleteSceneParamDto, spaceTuyaId = null) { const { spaceUuid, sceneUuid } = param; try { @@ -317,7 +320,10 @@ export class SceneService { if (err instanceof BadRequestException) { throw err; // Re-throw BadRequestException } else { - throw new HttpException('Scene not found', HttpStatus.NOT_FOUND); + throw new HttpException( + `Scene not found for ${sceneId}`, + HttpStatus.NOT_FOUND, + ); } } } @@ -375,7 +381,7 @@ export class SceneService { const newTapToRunScene = await this.addTapToRunScene( addSceneTapToRunDto, spaceTuyaId.spaceId, - scene.unitUuid, + scene.spaceUuid, ); const param: DeleteSceneParamDto = { @@ -394,7 +400,7 @@ export class SceneService { sceneIcon: { uuid: addSceneTapToRunDto.iconUuid, }, - unitUuid: scene.unitUuid, + spaceUuid: scene.spaceUuid, }, ); return newTapToRunScene; @@ -404,7 +410,7 @@ export class SceneService { throw err; // Re-throw BadRequestException } else { throw new HttpException( - err.message || 'Scene not found', + err.message || `Scene not found for id ${sceneUuid}`, err.status || HttpStatus.NOT_FOUND, ); } From f28fa627ed3ad8880f46a5048317db3b749c7e53 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 31 Oct 2024 10:14:59 +0400 Subject: [PATCH 032/109] added controller --- libs/common/src/dto/pagination.request.dto.ts | 21 ------------------- .../src/util/buildTypeORMWhereClause.ts | 1 - src/device/controllers/device.controller.ts | 1 + src/device/interfaces/get.device.interface.ts | 15 ++++++++++++- src/device/services/device.service.ts | 16 +++++++------- 5 files changed, 23 insertions(+), 31 deletions(-) diff --git a/libs/common/src/dto/pagination.request.dto.ts b/libs/common/src/dto/pagination.request.dto.ts index 4eff373..a2d011f 100644 --- a/libs/common/src/dto/pagination.request.dto.ts +++ b/libs/common/src/dto/pagination.request.dto.ts @@ -2,7 +2,6 @@ import { IsDate, IsOptional } from 'class-validator'; import { IsPageRequestParam } from '../validators/is-page-request-param.validator'; import { ApiProperty } from '@nestjs/swagger'; import { IsSizeRequestParam } from '../validators/is-size-request-param.validator'; -import { IsSortParam } from '../validators/is-sort-param.validator'; import { Transform } from 'class-transformer'; import { parseToDate } from '../util/parseToDate'; @@ -29,18 +28,6 @@ export class PaginationRequestGetListDto { }) size?: number; - @IsOptional() - @IsSortParam({ - message: - 'Incorrect sorting condition format. Should be like this format propertyId:asc,createdDate:desc', - }) - @ApiProperty({ - name: 'sort', - required: false, - description: 'Sort condition', - }) - sort?: string; - @IsOptional() @ApiProperty({ name: 'name', @@ -49,14 +36,6 @@ export class PaginationRequestGetListDto { }) name?: string; - @IsOptional() - @ApiProperty({ - name: 'include', - required: false, - description: 'Fields to include', - }) - include?: string; - @ApiProperty({ name: 'from', required: false, diff --git a/libs/common/src/util/buildTypeORMWhereClause.ts b/libs/common/src/util/buildTypeORMWhereClause.ts index 99119eb..31b55eb 100644 --- a/libs/common/src/util/buildTypeORMWhereClause.ts +++ b/libs/common/src/util/buildTypeORMWhereClause.ts @@ -6,7 +6,6 @@ export function buildTypeORMWhereClause({ where }) { // Remove extra nesting if `where` is wrapped within an additional `where` property const condition = where.where ? where.where : where; - console.log(condition); const convertToNestedObject = (condition: any): any => { const result = {}; for (const [key, value] of Object.entries(condition)) { diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 398aa4d..5a2fba5 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -138,6 +138,7 @@ export class DeviceController { async getAllDevices() { return await this.deviceService.getAllDevices(); } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('report-logs/:deviceUuid') diff --git a/src/device/interfaces/get.device.interface.ts b/src/device/interfaces/get.device.interface.ts index 93438b9..d003d2d 100644 --- a/src/device/interfaces/get.device.interface.ts +++ b/src/device/interfaces/get.device.interface.ts @@ -1,6 +1,6 @@ export interface GetDeviceDetailsInterface { activeTime: number; - assetId: string; + assetId?: string; category: string; categoryName: string; createTime: number; @@ -13,6 +13,7 @@ export interface GetDeviceDetailsInterface { lon: string; model: string; name: string; + battery?: number; nodeId: string; online: boolean; productId?: string; @@ -23,6 +24,18 @@ export interface GetDeviceDetailsInterface { uuid: string; productType: string; productUuid: string; + spaces?: SpaceInterface[]; + community?: CommunityInterface; +} + +export interface SpaceInterface { + uuid: string; + spaceName: string; +} + +export interface CommunityInterface { + uuid: string; + name: string; } export interface addDeviceInRoomInterface { diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 46a820e..09b905c 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -614,12 +614,12 @@ export class DeviceService { } } async getDevicesInstructionStatus(deviceUuid: string) { - const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); - - if (!deviceDetails) { - throw new NotFoundException('Device Not Found'); - } try { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + + if (!deviceDetails) { + throw new NotFoundException('Device Not Found'); + } const deviceStatus = await this.getDevicesInstructionStatusTuya( deviceDetails.deviceTuyaUuid, ); @@ -902,12 +902,12 @@ export class DeviceService { name: device.spaceDevice.community.name, }, // permissionType: device.permission[0].permissionType.type, - /* ...(await this.getDeviceDetailsByDeviceIdTuya( + ...(await this.getDeviceDetailsByDeviceIdTuya( device.deviceTuyaUuid, - )),*/ + )), uuid: device.uuid, ...(battery && { battery }), - }; + } as GetDeviceDetailsInterface; }), ); From bb03066ccb2abb6625107941d351dc5b5c7b2095 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 31 Oct 2024 10:15:26 +0400 Subject: [PATCH 033/109] unused import --- libs/common/src/util/buildTypeORMWhereClause.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/common/src/util/buildTypeORMWhereClause.ts b/libs/common/src/util/buildTypeORMWhereClause.ts index 31b55eb..87ef32e 100644 --- a/libs/common/src/util/buildTypeORMWhereClause.ts +++ b/libs/common/src/util/buildTypeORMWhereClause.ts @@ -1,5 +1,3 @@ -import { FindOptionsWhere } from 'typeorm'; - export function buildTypeORMWhereClause({ where }) { if (!where) return {}; From 8180d07a042ace48746ab78d7410b9885762c7d8 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 31 Oct 2024 12:01:15 +0400 Subject: [PATCH 034/109] replaced naming unit with space --- .../controllers/automation.controller.ts | 4 +- .../interface/automation.interface.ts | 2 +- src/automation/services/automation.service.ts | 44 +++++++++---------- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/automation/controllers/automation.controller.ts b/src/automation/controllers/automation.controller.ts index e608d96..675cd0b 100644 --- a/src/automation/controllers/automation.controller.ts +++ b/src/automation/controllers/automation.controller.ts @@ -48,8 +48,8 @@ export class AutomationController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':spaceUuid') - async getAutomationByUnit(@Param() param: SpaceParamDto) { - const automation = await this.automationService.getAutomationByUnit( + async getAutomationBySpace(@Param() param: SpaceParamDto) { + const automation = await this.automationService.getAutomationBySpace( param.spaceUuid, ); return automation; diff --git a/src/automation/interface/automation.interface.ts b/src/automation/interface/automation.interface.ts index 298d58c..eed3ef7 100644 --- a/src/automation/interface/automation.interface.ts +++ b/src/automation/interface/automation.interface.ts @@ -5,7 +5,7 @@ export interface AddAutomationInterface { id: string; }; } -export interface GetAutomationByUnitInterface { +export interface GetAutomationBySpaceInterface { success: boolean; msg?: string; result: { diff --git a/src/automation/services/automation.service.ts b/src/automation/services/automation.service.ts index f0c236b..793fa9f 100644 --- a/src/automation/services/automation.service.ts +++ b/src/automation/services/automation.service.ts @@ -20,7 +20,7 @@ import { AutomationDetailsResult, AutomationResponseData, DeleteAutomationInterface, - GetAutomationByUnitInterface, + GetAutomationBySpaceInterface, } from '../interface/automation.interface'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { @@ -48,20 +48,18 @@ export class AutomationService { async addAutomation(addAutomationDto: AddAutomationDto, spaceTuyaId = null) { try { - let unitSpaceTuyaId; + let tuyaSpaceId; if (!spaceTuyaId) { - const unitDetails = await this.getUnitByUuid( - addAutomationDto.spaceUuid, - ); + const space = await this.getSpaceByUuid(addAutomationDto.spaceUuid); - unitSpaceTuyaId = unitDetails.spaceTuyaUuid; - if (!unitDetails) { + tuyaSpaceId = space.spaceTuyaUuid; + if (!space) { throw new BadRequestException( `Invalid space UUID ${addAutomationDto.spaceUuid}`, ); } } else { - unitSpaceTuyaId = spaceTuyaId; + tuyaSpaceId = spaceTuyaId; } const actions = addAutomationDto.actions.map((action) => @@ -100,7 +98,7 @@ export class AutomationService { method: 'POST', path, body: { - space_id: unitSpaceTuyaId, + space_id: tuyaSpaceId, name: addAutomationDto.automationName, effective_time: { ...addAutomationDto.effectiveTime, @@ -129,7 +127,7 @@ export class AutomationService { } } } - async getUnitByUuid(spaceUuid: string) { + async getSpaceByUuid(spaceUuid: string) { try { const space = await this.spaceRepository.findOne({ where: { @@ -151,19 +149,19 @@ export class AutomationService { if (err instanceof BadRequestException) { throw err; // Re-throw BadRequestException } else { - throw new HttpException('Unit not found', HttpStatus.NOT_FOUND); + throw new HttpException('Space not found', HttpStatus.NOT_FOUND); } } } - async getAutomationByUnit(spaceUuid: string) { + async getAutomationBySpace(spaceUuid: string) { try { - const unit = await this.getUnitByUuid(spaceUuid); - if (!unit.spaceTuyaUuid) { + const space = await this.getSpaceByUuid(spaceUuid); + if (!space.spaceTuyaUuid) { throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); } - const path = `/v2.0/cloud/scene/rule?space_id=${unit.spaceTuyaUuid}&type=automation`; - const response: GetAutomationByUnitInterface = await this.tuya.request({ + const path = `/v2.0/cloud/scene/rule?space_id=${space.spaceTuyaUuid}&type=automation`; + const response: GetAutomationBySpaceInterface = await this.tuya.request({ method: 'GET', path, }); @@ -314,18 +312,18 @@ export class AutomationService { async deleteAutomation(param: DeleteAutomationParamDto, spaceTuyaId = null) { try { const { automationUuid, spaceUuid } = param; - let unitSpaceTuyaId; + let tuyaSpaceId; if (!spaceTuyaId) { - const space = await this.getUnitByUuid(spaceUuid); - unitSpaceTuyaId = space.spaceTuyaUuid; - if (!unitSpaceTuyaId) { + const space = await this.getSpaceByUuid(spaceUuid); + tuyaSpaceId = space.spaceTuyaUuid; + if (!tuyaSpaceId) { throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); } } else { - unitSpaceTuyaId = spaceTuyaId; + tuyaSpaceId = spaceTuyaId; } - const path = `/v2.0/cloud/scene/rule?ids=${automationUuid}&space_id=${unitSpaceTuyaId}`; + const path = `/v2.0/cloud/scene/rule?ids=${automationUuid}&space_id=${tuyaSpaceId}`; const response: DeleteAutomationInterface = await this.tuya.request({ method: 'DELETE', path, @@ -392,7 +390,7 @@ export class AutomationService { automationUuid: string, ) { try { - const space = await this.getUnitByUuid( + const space = await this.getSpaceByUuid( updateAutomationStatusDto.spaceUuid, ); if (!space.spaceTuyaUuid) { From c12038f2ef98f11692ccd7dbdcaad9a7bd9e6581 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 31 Oct 2024 12:03:38 +0400 Subject: [PATCH 035/109] changed naming to space --- src/scene/controllers/scene.controller.ts | 2 +- src/scene/services/scene.service.ts | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/scene/controllers/scene.controller.ts b/src/scene/controllers/scene.controller.ts index 27ae0c7..f83f9a2 100644 --- a/src/scene/controllers/scene.controller.ts +++ b/src/scene/controllers/scene.controller.ts @@ -51,7 +51,7 @@ export class SceneController { @Param() param: SpaceParamDto, @Query() inHomePage: GetSceneDto, ) { - const tapToRunScenes = await this.sceneService.getTapToRunSceneByUnit( + const tapToRunScenes = await this.sceneService.getTapToRunSceneBySpace( param.spaceUuid, inHomePage, ); diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 61e6af4..c446f00 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -55,17 +55,17 @@ export class SceneService { spaceUuid?: string, ) { try { - let unitSpaceTuyaId; + let tuyaSpaceId; if (!spaceTuyaId) { const space = await this.getSpaceByUuid(addSceneTapToRunDto.spaceUuid); - unitSpaceTuyaId = space.spaceTuyaUuid; + tuyaSpaceId = space.spaceTuyaUuid; if (!space) { throw new BadRequestException( `Invalid space UUID ${addSceneTapToRunDto.spaceUuid}`, ); } } else { - unitSpaceTuyaId = spaceTuyaId; + tuyaSpaceId = spaceTuyaId; } const actions = addSceneTapToRunDto.actions.map((action) => { @@ -91,7 +91,7 @@ export class SceneService { method: 'POST', path, body: { - space_id: unitSpaceTuyaId, + space_id: tuyaSpaceId, name: addSceneTapToRunDto.sceneName, type: 'scene', decision_expr: addSceneTapToRunDto.decisionExpr, @@ -161,7 +161,7 @@ export class SceneService { } } - async getTapToRunSceneByUnit(spaceUuid: string, inHomePage: GetSceneDto) { + async getTapToRunSceneBySpace(spaceUuid: string, inHomePage: GetSceneDto) { try { const showInHomePage = inHomePage?.showInHomePage; @@ -203,18 +203,18 @@ export class SceneService { async deleteTapToRunScene(param: DeleteSceneParamDto, spaceTuyaId = null) { const { spaceUuid, sceneUuid } = param; try { - let unitSpaceTuyaId; + let tuyaSpaceId; if (!spaceTuyaId) { const space = await this.getSpaceByUuid(spaceUuid); - unitSpaceTuyaId = space.spaceTuyaUuid; - if (!unitSpaceTuyaId) { + tuyaSpaceId = space.spaceTuyaUuid; + if (!tuyaSpaceId) { throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); } } else { - unitSpaceTuyaId = spaceTuyaId; + tuyaSpaceId = spaceTuyaId; } - const path = `/v2.0/cloud/scene/rule?ids=${sceneUuid}&space_id=${unitSpaceTuyaId}`; + const path = `/v2.0/cloud/scene/rule?ids=${sceneUuid}&space_id=${tuyaSpaceId}`; const response: DeleteTapToRunSceneInterface = await this.tuya.request({ method: 'DELETE', path, From 403a0174a93382357d04275732598980699b7150 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 31 Oct 2024 12:36:31 +0400 Subject: [PATCH 036/109] check for scene uuid --- src/scene/services/scene.service.ts | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index c446f00..8fcfe5e 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -3,6 +3,7 @@ import { HttpException, HttpStatus, BadRequestException, + HttpCode, } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { @@ -175,7 +176,7 @@ export class SceneService { const scenes = await Promise.all( scenesData.map(async (scene) => { const sceneData = await this.getTapToRunSceneDetails( - scene.sceneTuyaUuid, + scene.uuid, false, ); // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -262,8 +263,21 @@ export class SceneService { } } async getTapToRunSceneDetails(sceneId: string, withSpaceId = false) { + const scene = await this.sceneRepository.findOne({ + where: { uuid: sceneId }, + relations: ['sceneIcon'], + }); + + if (!scene) { + throw new HttpException( + `Scene with ${sceneId} is not found`, + HttpStatus.NOT_FOUND, + ); + } + try { - const path = `/v2.0/cloud/scene/rule/${sceneId}`; + const sceneTuyaUuid = scene.sceneTuyaUuid; + const path = `/v2.0/cloud/scene/rule/${sceneTuyaUuid}`; const response = await this.tuya.request({ method: 'GET', path, @@ -301,10 +315,7 @@ export class SceneService { } } } - const scene = await this.sceneRepository.findOne({ - where: { sceneTuyaUuid: sceneId }, - relations: ['sceneIcon'], - }); + return { id: responseData.id, name: responseData.name, @@ -320,6 +331,7 @@ export class SceneService { if (err instanceof BadRequestException) { throw err; // Re-throw BadRequestException } else { + console.log(err); throw new HttpException( `Scene not found for ${sceneId}`, HttpStatus.NOT_FOUND, @@ -327,6 +339,7 @@ export class SceneService { } } } + async getTapToRunSceneDetailsTuya( sceneId: string, ): Promise { From 2b6168058a4d374094e4fe31e644a11ae590d78b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 31 Oct 2024 14:39:36 +0400 Subject: [PATCH 037/109] updated delete scene --- .../src/integrations/tuya/tuya.service.ts | 34 ++++++ src/scene/controllers/scene.controller.ts | 5 +- src/scene/scene.module.ts | 2 + src/scene/services/scene.service.ts | 102 ++++++++++++++++-- 4 files changed, 133 insertions(+), 10 deletions(-) diff --git a/libs/common/src/integrations/tuya/tuya.service.ts b/libs/common/src/integrations/tuya/tuya.service.ts index 59efe6f..e37ab4d 100644 --- a/libs/common/src/integrations/tuya/tuya.service.ts +++ b/libs/common/src/integrations/tuya/tuya.service.ts @@ -52,4 +52,38 @@ export class TuyaService { return response.result; } + + async deleteSceneRule(sceneId: string, spaceId: string) { + const path = `/v2.0/cloud/scene/rule?ids=${sceneId}&space_id=${spaceId}`; + const response = await this.tuya.request({ + method: 'DELETE', + path, + }); + + if (response.success) { + return response; + } else { + throw new HttpException( + `Error deleting scene rule: ${response.msg}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getSceneRule(sceneId: string) { + const path = `/v2.0/cloud/scene/rule/${sceneId}`; + const response = await this.tuya.request({ + method: 'GET', + path, + }); + + if (response.success) { + return response; + } else { + throw new HttpException( + `Error fetching scene rule: ${response.msg}`, + HttpStatus.BAD_REQUEST, + ); + } + } } diff --git a/src/scene/controllers/scene.controller.ts b/src/scene/controllers/scene.controller.ts index f83f9a2..bb729f8 100644 --- a/src/scene/controllers/scene.controller.ts +++ b/src/scene/controllers/scene.controller.ts @@ -60,14 +60,15 @@ export class SceneController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Delete('tap-to-run/:unitUuid/:sceneUuid') + @Delete('tap-to-run/:spaceUuid/:sceneUuid') async deleteTapToRunScene(@Param() param: DeleteSceneParamDto) { - await this.sceneService.deleteTapToRunScene(param); + await this.sceneService.deleteScene(param); return { statusCode: HttpStatus.OK, message: 'Scene Deleted Successfully', }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('tap-to-run/trigger/:sceneUuid') diff --git a/src/scene/scene.module.ts b/src/scene/scene.module.ts index cdbf2d4..5649f2a 100644 --- a/src/scene/scene.module.ts +++ b/src/scene/scene.module.ts @@ -12,6 +12,7 @@ import { SceneIconRepository, SceneRepository, } from '@app/common/modules/scene/repositories'; +import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule], @@ -20,6 +21,7 @@ import { SceneService, SpaceRepository, DeviceService, + TuyaService, DeviceRepository, ProductRepository, SceneIconRepository, diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 8fcfe5e..f279fd7 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -3,7 +3,6 @@ import { HttpException, HttpStatus, BadRequestException, - HttpCode, } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { @@ -29,6 +28,8 @@ import { SceneRepository, } from '@app/common/modules/scene/repositories'; import { SceneIconType } from '@app/common/constants/secne-icon-type.enum'; +import { SceneEntity } from '@app/common/modules/scene/entities'; +import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; @Injectable() export class SceneService { @@ -39,6 +40,7 @@ export class SceneService { private readonly sceneIconRepository: SceneIconRepository, private readonly sceneRepository: SceneRepository, private readonly deviceService: DeviceService, + private readonly tuyaService: TuyaService, ) { const accessKey = this.configService.get('auth-config.ACCESS_KEY'); const secretKey = this.configService.get('auth-config.SECRET_KEY'); @@ -121,6 +123,7 @@ export class SceneService { id: response.result.id, }; } catch (err) { + console.log(err); if (err instanceof BadRequestException) { throw err; // Re-throw BadRequestException } else { @@ -131,6 +134,7 @@ export class SceneService { } } } + async getSpaceByUuid(spaceUuid: string) { try { const space = await this.spaceRepository.findOne({ @@ -221,6 +225,8 @@ export class SceneService { path, }); + console.log(path); + if (!response.success) { throw new HttpException('Scene not found', HttpStatus.NOT_FOUND); } else { @@ -229,6 +235,7 @@ export class SceneService { return response; } catch (err) { + console.log(err); if (err instanceof BadRequestException) { throw err; // Re-throw BadRequestException } else { @@ -262,6 +269,7 @@ export class SceneService { } } } + async getTapToRunSceneDetails(sceneId: string, withSpaceId = false) { const scene = await this.sceneRepository.findOne({ where: { uuid: sceneId }, @@ -372,24 +380,35 @@ export class SceneService { } } } + async updateTapToRunScene( updateSceneTapToRunDto: UpdateSceneTapToRunDto, sceneUuid: string, ) { + const scene = await this.sceneRepository.findOne({ + where: { uuid: sceneUuid }, + }); + + if (!scene) { + throw new HttpException( + `Scene with ${sceneUuid} is not found`, + HttpStatus.NOT_FOUND, + ); + } + try { const spaceTuyaId = await this.getTapToRunSceneDetails(sceneUuid, true); + if (!spaceTuyaId.spaceId) { throw new HttpException("Scene doesn't exist", HttpStatus.NOT_FOUND); } + const addSceneTapToRunDto: AddSceneTapToRunDto = { ...updateSceneTapToRunDto, - spaceUuid: null, + spaceUuid: scene.spaceUuid, iconUuid: updateSceneTapToRunDto.iconUuid, showInHomePage: updateSceneTapToRunDto.showInHomePage, }; - const scene = await this.sceneRepository.findOne({ - where: { sceneTuyaUuid: sceneUuid }, - }); const newTapToRunScene = await this.addTapToRunScene( addSceneTapToRunDto, @@ -398,12 +417,14 @@ export class SceneService { ); const param: DeleteSceneParamDto = { - spaceUuid: spaceTuyaId.spaceId, - sceneUuid, + spaceUuid: scene.spaceUuid, + sceneUuid: scene.sceneTuyaUuid, }; + console.log(param); + if (newTapToRunScene.id) { - await this.deleteTapToRunScene(null, param); + await this.deleteTapToRunScene(param, spaceTuyaId.spaceId); await this.sceneRepository.update( { sceneTuyaUuid: sceneUuid }, @@ -419,6 +440,7 @@ export class SceneService { return newTapToRunScene; } } catch (err) { + console.log(err); if (err instanceof BadRequestException) { throw err; // Re-throw BadRequestException } else { @@ -429,6 +451,7 @@ export class SceneService { } } } + async addSceneIcon(addSceneIconDto: AddSceneIconDto) { try { const icon = await this.sceneIconRepository.save({ @@ -463,4 +486,67 @@ export class SceneService { ); } } + + async deleteScene(params: DeleteSceneParamDto) { + try { + const { sceneUuid, spaceUuid } = params; + const scene = await this.findScene(sceneUuid); + const space = await this.getSpaceByUuid(spaceUuid); + if (!space.spaceTuyaUuid) { + throw new HttpException( + `Invalid space UUID: ${spaceUuid}`, + HttpStatus.BAD_REQUEST, + ); + } + const response = await this.delete( + scene.sceneTuyaUuid, + space.spaceTuyaUuid, + ); + return response; + } catch (err) { + if (err instanceof HttpException) { + throw err; // Re-throw existing HttpException + } else { + throw new HttpException( + err.message || `Scene not found for id ${params.sceneUuid}`, + err.status || HttpStatus.NOT_FOUND, + ); + } + } + } + + async findScene(sceneUuid: string): Promise { + const scene = await this.sceneRepository.findOne({ + where: { + uuid: sceneUuid, + }, + }); + + if (!scene) { + throw new HttpException( + `Invalid scene with id ${sceneUuid}`, + HttpStatus.NOT_FOUND, + ); + } + return scene; + } + + async delete(tuyaSceneId: string, tuyaSpaceId: string) { + try { + const response = (await this.tuyaService.deleteSceneRule( + tuyaSceneId, + tuyaSpaceId, + )) as DeleteTapToRunSceneInterface; + return response; + } catch (error) { + if (error instanceof HttpException) { + throw error; + } else { + throw new HttpException( + 'Failed to delete scene rule in Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } } From 7e9894b1d3bb8aab73e884355a12e7c6e5d51306 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sat, 2 Nov 2024 19:36:56 +0400 Subject: [PATCH 038/109] Fixed scene --- libs/common/src/common.module.ts | 2 +- libs/common/src/constants/controller-route.ts | 41 + .../src/integrations/tuya/interfaces/index.ts | 2 + .../interfaces/tap-to-run-action.interface.ts | 11 + .../interfaces/tuya.response.interface.ts | 5 + .../tuya/{ => services}/tuya.service.ts | 47 ++ .../src/util/buildTypeORMIncludeQuery.ts | 2 - src/community/community.module.ts | 2 +- src/community/services/community.service.ts | 2 +- src/scene/controllers/scene.controller.ts | 101 ++- src/scene/dtos/scene.dto.ts | 2 +- src/scene/interface/scene.interface.ts | 17 + src/scene/scene.module.ts | 2 +- src/scene/services/scene.service.ts | 704 +++++++++--------- src/space/controllers/index.ts | 1 + .../controllers/space-scene.controller.ts | 34 + src/space/services/index.ts | 1 + src/space/services/space-device.service.ts | 7 +- src/space/services/space-scene.service.ts | 55 ++ src/space/services/space.service.ts | 25 + .../subspace/subspace-device.service.ts | 2 +- src/space/space.module.ts | 20 +- 22 files changed, 672 insertions(+), 413 deletions(-) create mode 100644 libs/common/src/integrations/tuya/interfaces/index.ts create mode 100644 libs/common/src/integrations/tuya/interfaces/tap-to-run-action.interface.ts create mode 100644 libs/common/src/integrations/tuya/interfaces/tuya.response.interface.ts rename libs/common/src/integrations/tuya/{ => services}/tuya.service.ts (66%) create mode 100644 src/space/controllers/space-scene.controller.ts create mode 100644 src/space/services/space-scene.service.ts diff --git a/libs/common/src/common.module.ts b/libs/common/src/common.module.ts index 08a8fe2..cbe733d 100644 --- a/libs/common/src/common.module.ts +++ b/libs/common/src/common.module.ts @@ -7,7 +7,7 @@ import { ConfigModule } from '@nestjs/config'; import config from './config'; import { EmailService } from './util/email.service'; import { ErrorMessageService } from 'src/error-message/error-message.service'; -import { TuyaService } from './integrations/tuya/tuya.service'; +import { TuyaService } from './integrations/tuya/services/tuya.service'; @Module({ providers: [CommonService, EmailService, ErrorMessageService, TuyaService], exports: [ diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 2bca1a1..7f84bcc 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -80,6 +80,36 @@ export class ControllerRoute { }; }; + static SCENE = class { + public static readonly ROUTE = 'scene'; + static ACTIONS = class { + public static readonly CREATE_TAP_TO_RUN_SCENE_SUMMARY = + 'Create a Tap-to-Run Scene'; + public static readonly CREATE_TAP_TO_RUN_SCENE_DESCRIPTION = + 'Creates a new Tap-to-Run scene in Tuya and stores the scene in the local database.'; + + public static readonly DELETE_TAP_TO_RUN_SCENE_SUMMARY = + 'Delete a Tap-to-Run Scene'; + public static readonly DELETE_TAP_TO_RUN_SCENE_DESCRIPTION = + 'Deletes a Tap-to-Run scene from Tuya and removes it from the local database.'; + + public static readonly TRIGGER_TAP_TO_RUN_SCENE_SUMMARY = + 'Trigger a Tap-to-Run Scene'; + public static readonly TRIGGER_TAP_TO_RUN_SCENE_DESCRIPTION = + 'Triggers an existing Tap-to-Run scene in Tuya by scene UUID, executing its actions immediately.'; + + public static readonly GET_TAP_TO_RUN_SCENE_SUMMARY = + 'Get Tap-to-Run Scene Details'; + public static readonly GET_TAP_TO_RUN_SCENE_DESCRIPTION = + 'Retrieves detailed information of a specific Tap-to-Run scene identified by the scene UUID.'; + + public static readonly UPDATE_TAP_TO_RUN_SCENE_SUMMARY = + 'Update a Tap-to-Run Scene'; + public static readonly UPDATE_TAP_TO_RUN_SCENE_DESCRIPTION = + 'Updates an existing Tap-to-Run scene in Tuya and updates the scene in the local database, reflecting any new configurations or actions.'; + }; + }; + static SPACE = class { public static readonly ROUTE = '/communities/:communityUuid/spaces'; static ACTIONS = class { @@ -120,6 +150,17 @@ export class ControllerRoute { }; }; + static SPACE_SCENE = class { + public static readonly ROUTE = + '/communities/:communityUuid/spaces/:spaceUuid/scenes'; + static ACTIONS = class { + public static readonly GET_TAP_TO_RUN_SCENE_BY_SPACE_SUMMARY = + 'Retrieve Tap-to-Run Scenes by Space'; + public static readonly GET_TAP_TO_RUN_SCENE_BY_SPACE_DESCRIPTION = + 'Fetches all Tap-to-Run scenes associated with a specified space UUID. An optional query parameter can filter the results to show only scenes marked for the homepage display.'; + }; + }; + static SPACE_USER = class { public static readonly ROUTE = '/communities/:communityUuid/spaces/:spaceUuid/user'; diff --git a/libs/common/src/integrations/tuya/interfaces/index.ts b/libs/common/src/integrations/tuya/interfaces/index.ts new file mode 100644 index 0000000..506fc06 --- /dev/null +++ b/libs/common/src/integrations/tuya/interfaces/index.ts @@ -0,0 +1,2 @@ +export * from './tuya.response.interface'; +export * from './tap-to-run-action.interface'; diff --git a/libs/common/src/integrations/tuya/interfaces/tap-to-run-action.interface.ts b/libs/common/src/integrations/tuya/interfaces/tap-to-run-action.interface.ts new file mode 100644 index 0000000..13b8031 --- /dev/null +++ b/libs/common/src/integrations/tuya/interfaces/tap-to-run-action.interface.ts @@ -0,0 +1,11 @@ +export interface ConvertedExecutorProperty { + function_code?: string; + function_value?: any; + delay_seconds?: number; +} + +export interface ConvertedAction { + entity_id: string; + action_executor: string; + executor_property?: ConvertedExecutorProperty; +} diff --git a/libs/common/src/integrations/tuya/interfaces/tuya.response.interface.ts b/libs/common/src/integrations/tuya/interfaces/tuya.response.interface.ts new file mode 100644 index 0000000..3e3a2a9 --- /dev/null +++ b/libs/common/src/integrations/tuya/interfaces/tuya.response.interface.ts @@ -0,0 +1,5 @@ +export interface TuyaResponseInterface { + success: boolean; + msg?: string; + result: boolean; +} diff --git a/libs/common/src/integrations/tuya/tuya.service.ts b/libs/common/src/integrations/tuya/services/tuya.service.ts similarity index 66% rename from libs/common/src/integrations/tuya/tuya.service.ts rename to libs/common/src/integrations/tuya/services/tuya.service.ts index e37ab4d..80205af 100644 --- a/libs/common/src/integrations/tuya/tuya.service.ts +++ b/libs/common/src/integrations/tuya/services/tuya.service.ts @@ -1,6 +1,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; +import { ConvertedAction, TuyaResponseInterface } from '../interfaces'; @Injectable() export class TuyaService { @@ -86,4 +87,50 @@ export class TuyaService { ); } } + + async addTapToRunScene( + spaceId: string, + sceneName: string, + actions: ConvertedAction[], + decisionExpr: string, + ) { + const path = `/v2.0/cloud/scene/rule`; + const response = await this.tuya.request({ + method: 'POST', + path, + body: { + space_id: spaceId, + name: sceneName, + type: 'scene', + decision_expr: decisionExpr, + actions: actions, + }, + }); + + if (response.success) { + return response; + } else { + throw new HttpException( + `Error fetching scene rule: ${response.msg}`, + HttpStatus.BAD_REQUEST, + ); + } + } + + async triggerScene(sceneId: string): Promise { + const path = `/v2.0/cloud/scene/rule/${sceneId}/actions/trigger`; + const response: TuyaResponseInterface = await this.tuya.request({ + method: 'POST', + path, + }); + + if (!response.success) { + throw new HttpException( + response.msg || 'Error triggering scene', + HttpStatus.BAD_REQUEST, + ); + } + + return response; + } } diff --git a/libs/common/src/util/buildTypeORMIncludeQuery.ts b/libs/common/src/util/buildTypeORMIncludeQuery.ts index dda60df..c2d401e 100644 --- a/libs/common/src/util/buildTypeORMIncludeQuery.ts +++ b/libs/common/src/util/buildTypeORMIncludeQuery.ts @@ -36,8 +36,6 @@ export function buildTypeORMIncludeQuery( } }); - console.log(`Including relations for ${modelName}:`, relations); - return relations; } diff --git a/src/community/community.module.ts b/src/community/community.module.ts index 81c36cf..106d9d1 100644 --- a/src/community/community.module.ts +++ b/src/community/community.module.ts @@ -8,7 +8,7 @@ import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; import { SpacePermissionService } from '@app/common/helper/services'; import { CommunityRepository } from '@app/common/modules/community/repositories'; -import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index 7080c3e..e833416 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -10,7 +10,7 @@ import { PageResponse } from '@app/common/dto/pagination.response.dto'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { CommunityDto } from '@app/common/modules/community/dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; -import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; @Injectable() export class CommunityService { diff --git a/src/scene/controllers/scene.controller.ts b/src/scene/controllers/scene.controller.ts index bb729f8..eaf67ec 100644 --- a/src/scene/controllers/scene.controller.ts +++ b/src/scene/controllers/scene.controller.ts @@ -8,24 +8,24 @@ import { Param, Post, Put, - Query, UseGuards, } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { AddSceneIconDto, AddSceneTapToRunDto, - GetSceneDto, UpdateSceneTapToRunDto, } from '../dtos/scene.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; -import { DeleteSceneParamDto, SceneParamDto, SpaceParamDto } from '../dtos'; +import { SceneParamDto } from '../dtos'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { ControllerRoute } from '@app/common/constants/controller-route'; @ApiTags('Scene Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'scene', + path: ControllerRoute.SCENE.ROUTE, }) export class SceneController { constructor(private readonly sceneService: SceneService) {} @@ -33,81 +33,72 @@ export class SceneController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('tap-to-run') - async addTapToRunScene(@Body() addSceneTapToRunDto: AddSceneTapToRunDto) { - const tapToRunScene = - await this.sceneService.addTapToRunScene(addSceneTapToRunDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Scene added successfully', - data: tapToRunScene, - }; + @ApiOperation({ + summary: ControllerRoute.SCENE.ACTIONS.CREATE_TAP_TO_RUN_SCENE_SUMMARY, + description: + ControllerRoute.SCENE.ACTIONS.CREATE_TAP_TO_RUN_SCENE_DESCRIPTION, + }) + async addTapToRunScene( + @Body() addSceneTapToRunDto: AddSceneTapToRunDto, + ): Promise { + return await this.sceneService.createScene(addSceneTapToRunDto); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get('tap-to-run/:spaceUuid') - async getTapToRunSceneByUnit( - @Param() param: SpaceParamDto, - @Query() inHomePage: GetSceneDto, - ) { - const tapToRunScenes = await this.sceneService.getTapToRunSceneBySpace( - param.spaceUuid, - inHomePage, - ); - return tapToRunScenes; + @Delete('tap-to-run/:sceneUuid') + @ApiOperation({ + summary: ControllerRoute.SCENE.ACTIONS.DELETE_TAP_TO_RUN_SCENE_SUMMARY, + description: + ControllerRoute.SCENE.ACTIONS.DELETE_TAP_TO_RUN_SCENE_DESCRIPTION, + }) + async deleteTapToRunScene( + @Param() param: SceneParamDto, + ): Promise { + return await this.sceneService.deleteScene(param); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Delete('tap-to-run/:spaceUuid/:sceneUuid') - async deleteTapToRunScene(@Param() param: DeleteSceneParamDto) { - await this.sceneService.deleteScene(param); - return { - statusCode: HttpStatus.OK, - message: 'Scene Deleted Successfully', - }; - } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Post('tap-to-run/trigger/:sceneUuid') + @Post('tap-to-run/:sceneUuid/trigger') + @ApiOperation({ + summary: ControllerRoute.SCENE.ACTIONS.TRIGGER_TAP_TO_RUN_SCENE_SUMMARY, + description: + ControllerRoute.SCENE.ACTIONS.TRIGGER_TAP_TO_RUN_SCENE_DESCRIPTION, + }) async triggerTapToRunScene(@Param() param: SceneParamDto) { - await this.sceneService.triggerTapToRunScene(param.sceneUuid); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Scene trigger successfully', - }; + return await this.sceneService.triggerTapToRunScene(param.sceneUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get('tap-to-run/details/:sceneUuid') - async getTapToRunSceneDetails(@Param() param: SceneParamDto) { - const tapToRunScenes = await this.sceneService.getTapToRunSceneDetails( - param.sceneUuid, - ); - return tapToRunScenes; + @Get('tap-to-run/:sceneUuid') + @ApiOperation({ + summary: ControllerRoute.SCENE.ACTIONS.GET_TAP_TO_RUN_SCENE_SUMMARY, + description: ControllerRoute.SCENE.ACTIONS.GET_TAP_TO_RUN_SCENE_DESCRIPTION, + }) + async getTapToRunSceneDetails( + @Param() param: SceneParamDto, + ): Promise { + return await this.sceneService.getSceneByUuid(param.sceneUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('tap-to-run/:sceneUuid') + @ApiOperation({ + summary: ControllerRoute.SCENE.ACTIONS.UPDATE_TAP_TO_RUN_SCENE_SUMMARY, + description: + ControllerRoute.SCENE.ACTIONS.UPDATE_TAP_TO_RUN_SCENE_DESCRIPTION, + }) async updateTapToRunScene( @Body() updateSceneTapToRunDto: UpdateSceneTapToRunDto, @Param() param: SceneParamDto, ) { - const tapToRunScene = await this.sceneService.updateTapToRunScene( + return await this.sceneService.updateTapToRunScene( updateSceneTapToRunDto, param.sceneUuid, ); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Scene updated successfully', - data: tapToRunScene, - }; } @ApiBearerAuth() diff --git a/src/scene/dtos/scene.dto.ts b/src/scene/dtos/scene.dto.ts index 03d2517..1062d90 100644 --- a/src/scene/dtos/scene.dto.ts +++ b/src/scene/dtos/scene.dto.ts @@ -36,7 +36,7 @@ class ExecutorProperty { public delaySeconds?: number; } -class Action { +export class Action { @ApiProperty({ description: 'Entity ID', required: true, diff --git a/src/scene/interface/scene.interface.ts b/src/scene/interface/scene.interface.ts index e6508d9..c08c204 100644 --- a/src/scene/interface/scene.interface.ts +++ b/src/scene/interface/scene.interface.ts @@ -1,3 +1,5 @@ +import { Action } from '../dtos'; + export interface AddTapToRunSceneInterface { success: boolean; msg?: string; @@ -25,4 +27,19 @@ export interface SceneDetailsResult { id: string; name: string; type: string; + actions?: any; + status?: string; +} + +export interface SceneDetails { + uuid: string; + sceneTuyaId: string; + name: string; + status: string; + icon?: string; + iconUuid?: string; + showInHome: boolean; + type: string; + actions: Action[]; + spaceId: string; } diff --git a/src/scene/scene.module.ts b/src/scene/scene.module.ts index 5649f2a..680de5f 100644 --- a/src/scene/scene.module.ts +++ b/src/scene/scene.module.ts @@ -12,7 +12,7 @@ import { SceneIconRepository, SceneRepository, } from '@app/common/modules/scene/repositories'; -import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule], diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index f279fd7..930ee61 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -6,19 +6,19 @@ import { } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { + Action, AddSceneIconDto, AddSceneTapToRunDto, - DeleteSceneParamDto, GetSceneDto, + SceneParamDto, UpdateSceneTapToRunDto, } from '../dtos'; -import { ConfigService } from '@nestjs/config'; -import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { convertKeysToSnakeCase } from '@app/common/helper/snakeCaseConverter'; import { DeviceService } from 'src/device/services'; import { AddTapToRunSceneInterface, DeleteTapToRunSceneInterface, + SceneDetails, SceneDetailsResult, } from '../interface/scene.interface'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; @@ -28,354 +28,241 @@ import { SceneRepository, } from '@app/common/modules/scene/repositories'; import { SceneIconType } from '@app/common/constants/secne-icon-type.enum'; -import { SceneEntity } from '@app/common/modules/scene/entities'; -import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { + SceneEntity, + SceneIconEntity, +} from '@app/common/modules/scene/entities'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { HttpStatusCode } from 'axios'; +import { ConvertedAction } from '@app/common/integrations/tuya/interfaces'; @Injectable() export class SceneService { - private tuya: TuyaContext; constructor( - private readonly configService: ConfigService, private readonly spaceRepository: SpaceRepository, private readonly sceneIconRepository: SceneIconRepository, private readonly sceneRepository: SceneRepository, private readonly deviceService: DeviceService, private readonly tuyaService: TuyaService, - ) { - const accessKey = this.configService.get('auth-config.ACCESS_KEY'); - const secretKey = this.configService.get('auth-config.SECRET_KEY'); - const tuyaEuUrl = this.configService.get('tuya-config.TUYA_EU_URL'); - this.tuya = new TuyaContext({ - baseUrl: tuyaEuUrl, - accessKey, - secretKey, - }); - } + ) {} - async addTapToRunScene( + async createScene( + addSceneTapToRunDto: AddSceneTapToRunDto, + ): Promise { + try { + const { spaceUuid } = addSceneTapToRunDto; + + const space = await this.getSpaceByUuid(spaceUuid); + + const scene = await this.create(space.spaceTuyaUuid, addSceneTapToRunDto); + + return new SuccessResponseDto({ + message: `Successfully created new scene with uuid ${scene.uuid}`, + data: scene, + statusCode: HttpStatus.CREATED, + }); + } catch (err) { + console.error( + `Error in createScene for space UUID ${addSceneTapToRunDto.spaceUuid}:`, + err.message, + ); + throw err instanceof HttpException + ? err + : new HttpException( + 'Failed to create scene', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async create( + spaceTuyaUuid: string, + addSceneTapToRunDto: AddSceneTapToRunDto, + ): Promise { + const { iconUuid, showInHomePage, spaceUuid } = addSceneTapToRunDto; + + try { + const [defaultSceneIcon] = await Promise.all([ + this.getDefaultSceneIcon(), + ]); + if (!defaultSceneIcon) { + throw new HttpException( + 'Default scene icon not found', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + const response = await this.createSceneExternalService( + spaceTuyaUuid, + addSceneTapToRunDto, + ); + + const scene = await this.sceneRepository.save({ + sceneTuyaUuid: response.result.id, + sceneIcon: { uuid: iconUuid || defaultSceneIcon.uuid }, + showInHomePage, + spaceUuid, + }); + + return scene; + } catch (err) { + if (err instanceof HttpException) { + throw err; + } else if (err.message?.includes('tuya')) { + throw new HttpException( + 'API error: Failed to create scene', + HttpStatus.BAD_GATEWAY, + ); + } else { + throw new HttpException( + 'Database error: Failed to save scene', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + + async createSceneExternalService( + spaceTuyaUuid: string, addSceneTapToRunDto: AddSceneTapToRunDto, - spaceTuyaId = null, - spaceUuid?: string, ) { + const { sceneName, decisionExpr, actions } = addSceneTapToRunDto; try { - let tuyaSpaceId; - if (!spaceTuyaId) { - const space = await this.getSpaceByUuid(addSceneTapToRunDto.spaceUuid); - tuyaSpaceId = space.spaceTuyaUuid; - if (!space) { - throw new BadRequestException( - `Invalid space UUID ${addSceneTapToRunDto.spaceUuid}`, - ); - } - } else { - tuyaSpaceId = spaceTuyaId; + const formattedActions = await this.prepareActions(actions); + + const response = (await this.tuyaService.addTapToRunScene( + spaceTuyaUuid, + sceneName, + formattedActions, + decisionExpr, + )) as AddTapToRunSceneInterface; + + if (!response.result?.id) { + throw new HttpException( + 'Failed to create scene in Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } - const actions = addSceneTapToRunDto.actions.map((action) => { - return { - ...action, - }; - }); - - const convertedData = convertKeysToSnakeCase(actions); - for (const action of convertedData) { - if (action.action_executor === ActionExecutorEnum.DEVICE_ISSUE) { - const device = await this.deviceService.getDeviceByDeviceUuid( - action.entity_id, - false, - ); - if (device) { - action.entity_id = device.deviceTuyaUuid; - } - } - } - const path = `/v2.0/cloud/scene/rule`; - const response: AddTapToRunSceneInterface = await this.tuya.request({ - method: 'POST', - path, - body: { - space_id: tuyaSpaceId, - name: addSceneTapToRunDto.sceneName, - type: 'scene', - decision_expr: addSceneTapToRunDto.decisionExpr, - actions: convertedData, - }, - }); - if (!response.success) { - throw new HttpException(response.msg, HttpStatus.BAD_REQUEST); - } else { - const defaultSceneIcon = await this.sceneIconRepository.findOne({ - where: { iconType: SceneIconType.Default }, - }); - - await this.sceneRepository.save({ - sceneTuyaUuid: response.result.id, - sceneIcon: { - uuid: addSceneTapToRunDto.iconUuid - ? addSceneTapToRunDto.iconUuid - : defaultSceneIcon.uuid, - }, - showInHomePage: addSceneTapToRunDto.showInHomePage, - spaceUuid: spaceUuid ? spaceUuid : addSceneTapToRunDto.spaceUuid, - }); - } - return { - id: response.result.id, - }; + return response; } catch (err) { - console.log(err); - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException + if (err instanceof HttpException) { + throw err; + } else if (err.message?.includes('tuya')) { + throw new HttpException( + 'API error: Failed to create scene', + HttpStatus.BAD_GATEWAY, + ); } else { throw new HttpException( - err.message || `Scene not found`, - err.status || HttpStatus.NOT_FOUND, + 'An Internal error has been occured', + HttpStatus.INTERNAL_SERVER_ERROR, ); } } } - async getSpaceByUuid(spaceUuid: string) { + async findScenesBySpace(spaceUuid: string, filter: GetSceneDto) { try { - const space = await this.spaceRepository.findOne({ - where: { - uuid: spaceUuid, - }, - relations: ['community'], - }); - if (!space) { - throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); - } - return { - uuid: space.uuid, - createdAt: space.createdAt, - updatedAt: space.updatedAt, - name: space.spaceName, - spaceTuyaUuid: space.community.externalId, - }; - } catch (err) { - if (err instanceof BadRequestException) { - console.log(err); - throw err; // Re-throw BadRequestException - } else { - throw new HttpException( - `Space with id ${spaceUuid} not found`, - HttpStatus.NOT_FOUND, - ); - } - } - } - - async getTapToRunSceneBySpace(spaceUuid: string, inHomePage: GetSceneDto) { - try { - const showInHomePage = inHomePage?.showInHomePage; + await this.getSpaceByUuid(spaceUuid); + const showInHomePage = filter?.showInHomePage; const scenesData = await this.sceneRepository.find({ where: { spaceUuid, - ...(showInHomePage ? { showInHomePage } : {}), + ...(showInHomePage !== undefined ? { showInHomePage } : {}), }, }); + if (!scenesData.length) { + throw new HttpException( + `No scenes found for space UUID ${spaceUuid}`, + HttpStatus.NOT_FOUND, + ); + } + const scenes = await Promise.all( scenesData.map(async (scene) => { - const sceneData = await this.getTapToRunSceneDetails( - scene.uuid, - false, - ); // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { actions, ...rest } = sceneData; - return { - uuid: scene.uuid, - ...rest, - }; + const { actions, ...sceneDetails } = await this.getScene(scene); + + return sceneDetails; }), ); return scenes; } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException( - err.message || 'Scene not found', - err.status || HttpStatus.NOT_FOUND, - ); - } - } - } - - async deleteTapToRunScene(param: DeleteSceneParamDto, spaceTuyaId = null) { - const { spaceUuid, sceneUuid } = param; - try { - let tuyaSpaceId; - if (!spaceTuyaId) { - const space = await this.getSpaceByUuid(spaceUuid); - tuyaSpaceId = space.spaceTuyaUuid; - if (!tuyaSpaceId) { - throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); - } - } else { - tuyaSpaceId = spaceTuyaId; - } - - const path = `/v2.0/cloud/scene/rule?ids=${sceneUuid}&space_id=${tuyaSpaceId}`; - const response: DeleteTapToRunSceneInterface = await this.tuya.request({ - method: 'DELETE', - path, - }); - - console.log(path); - - if (!response.success) { - throw new HttpException('Scene not found', HttpStatus.NOT_FOUND); - } else { - await this.sceneRepository.delete({ sceneTuyaUuid: sceneUuid }); - } - - return response; - } catch (err) { - console.log(err); - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException( - err.message || 'Scene not found', - err.status || HttpStatus.NOT_FOUND, - ); - } - } - } - - async triggerTapToRunScene(sceneId: string) { - try { - const path = `/v2.0/cloud/scene/rule/${sceneId}/actions/trigger`; - const response: DeleteTapToRunSceneInterface = await this.tuya.request({ - method: 'POST', - path, - }); - if (!response.success) { - throw new HttpException(response.msg, HttpStatus.BAD_REQUEST); - } - return response; - } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException( - err.message || 'Scene not found', - err.status || HttpStatus.NOT_FOUND, - ); - } - } - } - - async getTapToRunSceneDetails(sceneId: string, withSpaceId = false) { - const scene = await this.sceneRepository.findOne({ - where: { uuid: sceneId }, - relations: ['sceneIcon'], - }); - - if (!scene) { - throw new HttpException( - `Scene with ${sceneId} is not found`, - HttpStatus.NOT_FOUND, + console.error( + `Error fetching Tap-to-Run scenes for space UUID ${spaceUuid}:`, + err.message, ); - } - try { - const sceneTuyaUuid = scene.sceneTuyaUuid; - const path = `/v2.0/cloud/scene/rule/${sceneTuyaUuid}`; - const response = await this.tuya.request({ - method: 'GET', - path, - }); - if (!response.success) { - throw new HttpException(response.msg, HttpStatus.BAD_REQUEST); - } - const responseData = convertKeysToCamelCase(response.result); - const actions = responseData.actions.map((action) => { - return { - ...action, - }; - }); - - for (const action of actions) { - if (action.actionExecutor === ActionExecutorEnum.DEVICE_ISSUE) { - const device = await this.deviceService.getDeviceByDeviceTuyaUuid( - action.entityId, - ); - - if (device) { - action.entityId = device.uuid; - } - } else if ( - action.actionExecutor !== ActionExecutorEnum.DEVICE_ISSUE && - action.actionExecutor !== ActionExecutorEnum.DELAY - ) { - const sceneDetails = await this.getTapToRunSceneDetailsTuya( - action.entityId, - ); - - if (sceneDetails.id) { - action.name = sceneDetails.name; - action.type = sceneDetails.type; - } - } - } - - return { - id: responseData.id, - name: responseData.name, - status: responseData.status, - icon: scene.sceneIcon?.icon, - iconUuid: scene.sceneIcon?.uuid, - showInHome: scene.showInHomePage, - type: 'tap_to_run', - actions: actions, - ...(withSpaceId && { spaceId: responseData.spaceId }), - }; - } catch (err) { if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException + throw err; } else { - console.log(err); throw new HttpException( - `Scene not found for ${sceneId}`, - HttpStatus.NOT_FOUND, + 'An error occurred while retrieving scenes', + HttpStatus.INTERNAL_SERVER_ERROR, ); } } } - async getTapToRunSceneDetailsTuya( + async triggerTapToRunScene(sceneUuid: string) { + try { + const scene = await this.findScene(sceneUuid); + await this.tuyaService.triggerScene(scene.sceneTuyaUuid); + return new SuccessResponseDto({ + message: `Scene with ID ${sceneUuid} triggered successfully`, + }); + } catch (err) { + if (err instanceof HttpException) { + throw err; + } else { + throw new HttpException( + err.message || 'Scene not found', + err.status || HttpStatus.NOT_FOUND, + ); + } + } + } + + async fetchSceneDetailsFromTuya( sceneId: string, ): Promise { try { - const path = `/v2.0/cloud/scene/rule/${sceneId}`; - const response = await this.tuya.request({ - method: 'GET', - path, - }); - - if (!response.success) { - throw new HttpException(response.msg, HttpStatus.BAD_REQUEST); - } + const response = await this.tuyaService.getSceneRule(sceneId); const camelCaseResponse = convertKeysToCamelCase(response); - const { id, name, type } = camelCaseResponse.result; + const { + id, + name, + type, + status, + actions: tuyaActions = [], + } = camelCaseResponse.result; + + const actions = tuyaActions.map((action) => ({ ...action })); return { id, name, type, + status, + actions, } as SceneDetailsResult; } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException + console.error( + `Error fetching scene details for scene ID ${sceneId}:`, + err, + ); + if (err instanceof HttpException) { + throw err; } else { throw new HttpException( - 'Scene not found for Tuya', - HttpStatus.NOT_FOUND, + 'An error occurred while fetching scene details from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, ); } } @@ -385,23 +272,9 @@ export class SceneService { updateSceneTapToRunDto: UpdateSceneTapToRunDto, sceneUuid: string, ) { - const scene = await this.sceneRepository.findOne({ - where: { uuid: sceneUuid }, - }); - - if (!scene) { - throw new HttpException( - `Scene with ${sceneUuid} is not found`, - HttpStatus.NOT_FOUND, - ); - } - try { - const spaceTuyaId = await this.getTapToRunSceneDetails(sceneUuid, true); - - if (!spaceTuyaId.spaceId) { - throw new HttpException("Scene doesn't exist", HttpStatus.NOT_FOUND); - } + const scene = await this.findScene(sceneUuid); + const space = await this.getSpaceByUuid(scene.spaceUuid); const addSceneTapToRunDto: AddSceneTapToRunDto = { ...updateSceneTapToRunDto, @@ -410,38 +283,38 @@ export class SceneService { showInHomePage: updateSceneTapToRunDto.showInHomePage, }; - const newTapToRunScene = await this.addTapToRunScene( + const createdTuyaSceneResponse = await this.createSceneExternalService( + space.spaceTuyaUuid, addSceneTapToRunDto, - spaceTuyaId.spaceId, - scene.spaceUuid, ); + const newSceneTuyaUuid = createdTuyaSceneResponse.result?.id; - const param: DeleteSceneParamDto = { - spaceUuid: scene.spaceUuid, - sceneUuid: scene.sceneTuyaUuid, - }; - - console.log(param); - - if (newTapToRunScene.id) { - await this.deleteTapToRunScene(param, spaceTuyaId.spaceId); - - await this.sceneRepository.update( - { sceneTuyaUuid: sceneUuid }, - { - sceneTuyaUuid: newTapToRunScene.id, - showInHomePage: addSceneTapToRunDto.showInHomePage, - sceneIcon: { - uuid: addSceneTapToRunDto.iconUuid, - }, - spaceUuid: scene.spaceUuid, - }, + if (!newSceneTuyaUuid) { + throw new HttpException( + `Failed to create a external new scene`, + HttpStatus.BAD_GATEWAY, ); - return newTapToRunScene; } + + await this.delete(scene.sceneTuyaUuid, space.spaceTuyaUuid); + + const updatedScene = await this.sceneRepository.update( + { uuid: sceneUuid }, + { + sceneTuyaUuid: newSceneTuyaUuid, + showInHomePage: addSceneTapToRunDto.showInHomePage, + sceneIcon: { + uuid: addSceneTapToRunDto.iconUuid, + }, + spaceUuid: scene.spaceUuid, + }, + ); + return new SuccessResponseDto({ + data: updatedScene, + message: `Scene with ID ${sceneUuid} updated successfully`, + }); } catch (err) { - console.log(err); - if (err instanceof BadRequestException) { + if (err instanceof HttpException) { throw err; // Re-throw BadRequestException } else { throw new HttpException( @@ -469,6 +342,7 @@ export class SceneService { ); } } + async getAllIcons() { try { const icons = await this.sceneIconRepository.find(); @@ -487,25 +361,99 @@ export class SceneService { } } - async deleteScene(params: DeleteSceneParamDto) { + async getSceneByUuid(sceneUuid: string): Promise { try { - const { sceneUuid, spaceUuid } = params; const scene = await this.findScene(sceneUuid); - const space = await this.getSpaceByUuid(spaceUuid); - if (!space.spaceTuyaUuid) { + const sceneDetails = await this.getScene(scene); + + return new SuccessResponseDto({ + data: sceneDetails, + message: `Scene details for ${sceneUuid} retrieved successfully`, + }); + } catch (error) { + console.error( + `Error fetching scene details for sceneUuid ${sceneUuid}:`, + error, + ); + + if (error instanceof HttpException) { + throw error; + } else { throw new HttpException( - `Invalid space UUID: ${spaceUuid}`, - HttpStatus.BAD_REQUEST, + 'An error occurred while retrieving scene details', + HttpStatus.INTERNAL_SERVER_ERROR, ); } - const response = await this.delete( + } + } + + async getScene(scene: SceneEntity): Promise { + try { + const { actions, name, status } = await this.fetchSceneDetailsFromTuya( scene.sceneTuyaUuid, - space.spaceTuyaUuid, ); - return response; + + for (const action of actions) { + if (action.actionExecutor === ActionExecutorEnum.DEVICE_ISSUE) { + const device = await this.deviceService.getDeviceByDeviceTuyaUuid( + action.entityId, + ); + + if (device) { + action.entityId = device.uuid; + } + } else if ( + action.actionExecutor !== ActionExecutorEnum.DEVICE_ISSUE && + action.actionExecutor !== ActionExecutorEnum.DELAY + ) { + const sceneDetails = await this.fetchSceneDetailsFromTuya( + action.entityId, + ); + + if (sceneDetails.id) { + action.name = sceneDetails.name; + action.type = sceneDetails.type; + } + } + } + return { + uuid: scene.uuid, + sceneTuyaId: scene.sceneTuyaUuid, + name, + status, + icon: scene.sceneIcon?.icon, + iconUuid: scene.sceneIcon?.uuid, + showInHome: scene.showInHomePage, + type: 'tap_to_run', + actions, + spaceId: scene.spaceUuid, + }; + } catch (err) { + if (err instanceof BadRequestException) { + throw err; + } else { + console.log(err); + throw new HttpException( + `An error occurred while retrieving scene details for ${scene.uuid}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + + async deleteScene(params: SceneParamDto): Promise { + try { + const { sceneUuid } = params; + const scene = await this.findScene(sceneUuid); + const space = await this.getSpaceByUuid(scene.spaceUuid); + + await this.delete(scene.sceneTuyaUuid, space.spaceTuyaUuid); + return new SuccessResponseDto({ + message: `Scene with ID ${sceneUuid} deleted successfully`, + }); } catch (err) { if (err instanceof HttpException) { - throw err; // Re-throw existing HttpException + throw err; } else { throw new HttpException( err.message || `Scene not found for id ${params.sceneUuid}`, @@ -517,9 +465,8 @@ export class SceneService { async findScene(sceneUuid: string): Promise { const scene = await this.sceneRepository.findOne({ - where: { - uuid: sceneUuid, - }, + where: { uuid: sceneUuid }, + relations: ['sceneIcon'], }); if (!scene) { @@ -549,4 +496,73 @@ export class SceneService { } } } + + private async prepareActions(actions: Action[]): Promise { + const convertedData = convertKeysToSnakeCase(actions) as ConvertedAction[]; + + await Promise.all( + convertedData.map(async (action) => { + if (action.action_executor === ActionExecutorEnum.DEVICE_ISSUE) { + const device = await this.deviceService.getDeviceByDeviceUuid( + action.entity_id, + false, + ); + if (device) { + action.entity_id = device.deviceTuyaUuid; + } + } + }), + ); + + return convertedData; + } + + private async getDefaultSceneIcon(): Promise { + const defaultIcon = await this.sceneIconRepository.findOne({ + where: { iconType: SceneIconType.Default }, + }); + return defaultIcon; + } + + async getSpaceByUuid(spaceUuid: string) { + try { + const space = await this.spaceRepository.findOne({ + where: { + uuid: spaceUuid, + }, + relations: ['community'], + }); + + if (!space) { + throw new HttpException( + `Invalid space UUID ${spaceUuid}`, + HttpStatusCode.BadRequest, + ); + } + + if (!space.community.externalId) { + throw new HttpException( + `Space doesn't have any association with tuya${spaceUuid}`, + HttpStatusCode.BadRequest, + ); + } + return { + uuid: space.uuid, + createdAt: space.createdAt, + updatedAt: space.updatedAt, + name: space.spaceName, + spaceTuyaUuid: space.community.externalId, + }; + } catch (err) { + if (err instanceof BadRequestException) { + console.log(err); + throw err; + } else { + throw new HttpException( + `Space with id ${spaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + } + } } diff --git a/src/space/controllers/index.ts b/src/space/controllers/index.ts index f3b360e..f9587fb 100644 --- a/src/space/controllers/index.ts +++ b/src/space/controllers/index.ts @@ -1,4 +1,5 @@ export * from './space.controller'; export * from './space-user.controller'; export * from './space-device.controller'; +export * from './space-scene.controller'; export * from './subspace'; diff --git a/src/space/controllers/space-scene.controller.ts b/src/space/controllers/space-scene.controller.ts new file mode 100644 index 0000000..5517362 --- /dev/null +++ b/src/space/controllers/space-scene.controller.ts @@ -0,0 +1,34 @@ +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { SpaceSceneService } from '../services'; +import { GetSceneDto } from '../../scene/dtos'; +import { GetSpaceParam } from '../dtos'; + +@ApiTags('Space Module') +@Controller({ + version: '1', + path: ControllerRoute.SPACE_SCENE.ROUTE, +}) +export class SpaceSceneController { + constructor(private readonly sceneService: SpaceSceneService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: + ControllerRoute.SPACE_SCENE.ACTIONS.GET_TAP_TO_RUN_SCENE_BY_SPACE_SUMMARY, + description: + ControllerRoute.SPACE_SCENE.ACTIONS + .GET_TAP_TO_RUN_SCENE_BY_SPACE_DESCRIPTION, + }) + @Get() + async getTapToRunSceneByUnit( + @Param() params: GetSpaceParam, + @Query() inHomePage: GetSceneDto, + ): Promise { + return await this.sceneService.getScenes(params, inHomePage); + } +} diff --git a/src/space/services/index.ts b/src/space/services/index.ts index 97cfe42..c4e9405 100644 --- a/src/space/services/index.ts +++ b/src/space/services/index.ts @@ -2,3 +2,4 @@ export * from './space.service'; export * from './space-user.service'; export * from './space-device.service'; export * from './subspace'; +export * from './space-scene.service'; diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index 9701c6f..1be3ea7 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -1,4 +1,4 @@ -import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; @@ -51,10 +51,7 @@ export class SpaceDeviceService { }); } - private async validateCommunityAndSpace( - communityUuid: string, - spaceUuid: string, - ) { + async validateCommunityAndSpace(communityUuid: string, spaceUuid: string) { const community = await this.communityRepository.findOne({ where: { uuid: communityUuid }, }); diff --git a/src/space/services/space-scene.service.ts b/src/space/services/space-scene.service.ts new file mode 100644 index 0000000..ffc10e0 --- /dev/null +++ b/src/space/services/space-scene.service.ts @@ -0,0 +1,55 @@ +import { + BadRequestException, + HttpException, + HttpStatus, + Injectable, +} from '@nestjs/common'; +import { GetSpaceParam } from '../dtos'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { SpaceService } from './space.service'; +import { SceneService } from '../../scene/services'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { GetSceneDto } from '../../scene/dtos'; + +@Injectable() +export class SpaceSceneService { + constructor( + private readonly spaceSevice: SpaceService, + private readonly sceneSevice: SceneService, + ) {} + + async getScenes( + params: GetSpaceParam, + getSceneDto: GetSceneDto, + ): Promise { + try { + const { spaceUuid, communityUuid } = params; + + await this.spaceSevice.validateCommunityAndSpace( + communityUuid, + spaceUuid, + ); + + const scenes = await this.sceneSevice.findScenesBySpace( + spaceUuid, + getSceneDto, + ); + + return new SuccessResponseDto({ + message: `Scenes retrieved successfully for space ${spaceUuid}`, + data: scenes, + }); + } catch (error) { + console.error('Error retrieving scenes:', error); + + if (error instanceof BadRequestException) { + throw new HttpException(error.message, HttpStatus.BAD_REQUEST); + } else { + throw new HttpException( + 'An error occurred while retrieving scenes', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } +} diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 52267d8..78d45d7 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -357,4 +357,29 @@ export class SpaceService { return rootSpaces; // Return the root spaces with children nested within them } + + async validateCommunityAndSpace(communityUuid: string, spaceUuid: string) { + const community = await this.communityRepository.findOne({ + where: { uuid: communityUuid }, + }); + if (!community) { + this.throwNotFound('Community', communityUuid); + } + + const space = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid, community: { uuid: communityUuid } }, + relations: ['devices', 'devices.productDevice'], + }); + if (!space) { + this.throwNotFound('Space', spaceUuid); + } + return space; + } + + private throwNotFound(entity: string, uuid: string) { + throw new HttpException( + `${entity} with ID ${uuid} not found`, + HttpStatus.NOT_FOUND, + ); + } } diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index 9b2db68..06464b4 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -9,7 +9,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; -import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { ProductRepository } from '@app/common/modules/product/repositories'; import { GetDeviceDetailsInterface } from '../../../device/interfaces/get.device.interface'; diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 5eb7af8..cf6b766 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -7,9 +7,11 @@ import { SpaceUserController, SubSpaceController, SubSpaceDeviceController, + SpaceSceneController, } from './controllers'; import { SpaceDeviceService, + SpaceSceneService, SpaceService, SpaceUserService, SubspaceDeviceService, @@ -25,8 +27,16 @@ import { UserSpaceRepository, } from '@app/common/modules/user/repositories'; import { DeviceRepository } from '@app/common/modules/device/repositories'; -import { TuyaService } from '@app/common/integrations/tuya/tuya.service'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { ProductRepository } from '@app/common/modules/product/repositories'; +import { SceneService } from '../scene/services'; +import { + SceneIconRepository, + SceneRepository, +} from '@app/common/modules/scene/repositories'; +import { DeviceService } from 'src/device/services'; +import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service'; +import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories'; @Module({ imports: [ConfigModule, SpaceRepositoryModule], @@ -36,6 +46,7 @@ import { ProductRepository } from '@app/common/modules/product/repositories'; SpaceDeviceController, SubSpaceController, SubSpaceDeviceController, + SpaceSceneController, ], providers: [ SpaceService, @@ -51,6 +62,13 @@ import { ProductRepository } from '@app/common/modules/product/repositories'; UserSpaceRepository, UserRepository, SpaceUserService, + SpaceSceneService, + SceneService, + SceneIconRepository, + SceneRepository, + DeviceService, + DeviceStatusFirebaseService, + DeviceStatusLogRepository, ], exports: [SpaceService], }) From 015b1394286743459bd9b524bb88493538c8c1da Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sat, 2 Nov 2024 23:09:21 +0400 Subject: [PATCH 039/109] add automation --- .../tuya/services/tuya.service.ts | 41 ++++++ src/automation/automation.module.ts | 2 + src/automation/services/automation.service.ts | 130 +++++++++--------- src/space/services/space-device.service.ts | 62 +++++---- 4 files changed, 145 insertions(+), 90 deletions(-) diff --git a/libs/common/src/integrations/tuya/services/tuya.service.ts b/libs/common/src/integrations/tuya/services/tuya.service.ts index 80205af..9aabb9a 100644 --- a/libs/common/src/integrations/tuya/services/tuya.service.ts +++ b/libs/common/src/integrations/tuya/services/tuya.service.ts @@ -133,4 +133,45 @@ export class TuyaService { return response; } + + async createAutomation( + spaceId: string, + automationName: string, + effectiveTime: any, + decisionExpr: string, + conditions: any[], + actions: any[], + ) { + const path = `/v2.0/cloud/scene/rule`; + + try { + const response = await this.tuya.request({ + method: 'POST', + path, + body: { + space_id: spaceId, + name: automationName, + effective_time: { + ...effectiveTime, + timezone_id: 'Asia/Dubai', + }, + type: 'automation', + decision_expr: decisionExpr, + conditions: conditions, + actions: actions, + }, + }); + + if (!response.success) { + throw new HttpException(response.msg, HttpStatus.BAD_REQUEST); + } + + return response.result; + } catch (error) { + throw new HttpException( + error.message || 'Failed to create automation in Tuya', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/automation/automation.module.ts b/src/automation/automation.module.ts index 49c3b02..3a3d7db 100644 --- a/src/automation/automation.module.ts +++ b/src/automation/automation.module.ts @@ -8,12 +8,14 @@ import { DeviceService } from 'src/device/services'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { ProductRepository } from '@app/common/modules/product/repositories'; import { DeviceStatusFirebaseModule } from '@app/common/firebase/devices-status/devices-status.module'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule], controllers: [AutomationController], providers: [ AutomationService, + TuyaService, SpaceRepository, DeviceService, DeviceRepository, diff --git a/src/automation/services/automation.service.ts b/src/automation/services/automation.service.ts index 793fa9f..a34eb49 100644 --- a/src/automation/services/automation.service.ts +++ b/src/automation/services/automation.service.ts @@ -16,9 +16,11 @@ import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { convertKeysToSnakeCase } from '@app/common/helper/snakeCaseConverter'; import { DeviceService } from 'src/device/services'; import { + Action, AddAutomationInterface, AutomationDetailsResult, AutomationResponseData, + Condition, DeleteAutomationInterface, GetAutomationBySpaceInterface, } from '../interface/automation.interface'; @@ -27,6 +29,7 @@ import { ActionExecutorEnum, EntityTypeEnum, } from '@app/common/constants/automation.enum'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; @Injectable() export class AutomationService { @@ -35,6 +38,7 @@ export class AutomationService { private readonly configService: ConfigService, private readonly spaceRepository: SpaceRepository, private readonly deviceService: DeviceService, + private readonly tuyaService: TuyaService, ) { const accessKey = this.configService.get('auth-config.ACCESS_KEY'); const secretKey = this.configService.get('auth-config.SECRET_KEY'); @@ -48,77 +52,39 @@ export class AutomationService { async addAutomation(addAutomationDto: AddAutomationDto, spaceTuyaId = null) { try { - let tuyaSpaceId; - if (!spaceTuyaId) { - const space = await this.getSpaceByUuid(addAutomationDto.spaceUuid); + const { automationName, effectiveTime, decisionExpr } = addAutomationDto; + const space = await this.getSpaceByUuid(addAutomationDto.spaceUuid); - tuyaSpaceId = space.spaceTuyaUuid; - if (!space) { - throw new BadRequestException( - `Invalid space UUID ${addAutomationDto.spaceUuid}`, - ); - } - } else { - tuyaSpaceId = spaceTuyaId; - } - - const actions = addAutomationDto.actions.map((action) => - convertKeysToSnakeCase(action), - ); - const conditions = addAutomationDto.conditions.map((condition) => - convertKeysToSnakeCase(condition), + const actions = await this.processEntities( + addAutomationDto.actions, + 'actionExecutor', + { [ActionExecutorEnum.DEVICE_ISSUE]: true }, + this.deviceService, ); - for (const action of actions) { - if (action.action_executor === ActionExecutorEnum.DEVICE_ISSUE) { - const device = await this.deviceService.getDeviceByDeviceUuid( - action.entity_id, - false, - ); - if (device) { - action.entity_id = device.deviceTuyaUuid; - } - } - } + const conditions = await this.processEntities( + addAutomationDto.conditions, + 'entityType', + { [EntityTypeEnum.DEVICE_REPORT]: true }, + this.deviceService, + ); - for (const condition of conditions) { - if (condition.entity_type === EntityTypeEnum.DEVICE_REPORT) { - const device = await this.deviceService.getDeviceByDeviceUuid( - condition.entity_id, - false, - ); - if (device) { - condition.entity_id = device.deviceTuyaUuid; - } - } - } + const response = (await this.tuyaService.createAutomation( + space.spaceTuyaUuid, + automationName, + effectiveTime, + decisionExpr, + conditions, + actions, + )) as AddAutomationInterface; - const path = `/v2.0/cloud/scene/rule`; - const response: AddAutomationInterface = await this.tuya.request({ - method: 'POST', - path, - body: { - space_id: tuyaSpaceId, - name: addAutomationDto.automationName, - effective_time: { - ...addAutomationDto.effectiveTime, - timezone_id: 'Asia/Dubai', - }, - type: 'automation', - decision_expr: addAutomationDto.decisionExpr, - conditions: conditions, - actions: actions, - }, - }); - if (!response.success) { - throw new HttpException(response.msg, HttpStatus.BAD_REQUEST); - } return { - id: response.result.id, + id: response?.result.id, }; } catch (err) { + console.log(err); if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException + throw err; } else { throw new HttpException( err.message || 'Automation not found', @@ -127,6 +93,7 @@ export class AutomationService { } } } + async getSpaceByUuid(spaceUuid: string) { try { const space = await this.spaceRepository.findOne({ @@ -189,6 +156,7 @@ export class AutomationService { } } } + async getTapToRunSceneDetailsTuya( sceneUuid: string, ): Promise { @@ -425,4 +393,42 @@ export class AutomationService { } } } + + async processEntities( + entities: T[], // Accepts either Action[] or Condition[] + lookupKey: keyof T, // The key to look up, specific to T + entityTypeOrExecutorMap: { + [key in ActionExecutorEnum | EntityTypeEnum]?: boolean; + }, + deviceService: { + getDeviceByDeviceUuid: ( + id: string, + flag: boolean, + ) => Promise<{ deviceTuyaUuid: string } | null>; + }, + ): Promise { + // Returns the same type as provided in the input + return Promise.all( + entities.map(async (entity) => { + // Convert keys to snake case (assuming a utility function exists) + const processedEntity = convertKeysToSnakeCase(entity) as T; + + // Check if entity needs device UUID lookup + const key = processedEntity[lookupKey]; + if ( + entityTypeOrExecutorMap[key as ActionExecutorEnum | EntityTypeEnum] + ) { + const device = await deviceService.getDeviceByDeviceUuid( + processedEntity.entityId, + false, + ); + if (device) { + processedEntity.entityId = device.deviceTuyaUuid; + } + } + + return processedEntity; + }), + ); + } } diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index 1be3ea7..9d9eac8 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -20,35 +20,42 @@ export class SpaceDeviceService { async listDevicesInSpace(params: GetSpaceParam): Promise { const { spaceUuid, communityUuid } = params; + try { + const space = await this.validateCommunityAndSpace( + communityUuid, + spaceUuid, + ); - const space = await this.validateCommunityAndSpace( - communityUuid, - spaceUuid, - ); + const detailedDevices = await Promise.all( + space.devices.map(async (device) => { + const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya( + device.deviceTuyaUuid, + ); - const detailedDevices = await Promise.all( - space.devices.map(async (device) => { - const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya( - device.deviceTuyaUuid, - ); + return { + uuid: device.uuid, + deviceTuyaUuid: device.deviceTuyaUuid, + productUuid: device.productDevice.uuid, + productType: device.productDevice.prodType, + isActive: device.isActive, + createdAt: device.createdAt, + updatedAt: device.updatedAt, + ...tuyaDetails, + }; + }), + ); - return { - uuid: device.uuid, - deviceTuyaUuid: device.deviceTuyaUuid, - productUuid: device.productDevice.uuid, - productType: device.productDevice.prodType, - isActive: device.isActive, - createdAt: device.createdAt, - updatedAt: device.updatedAt, - ...tuyaDetails, - }; - }), - ); - - return new SuccessResponseDto({ - data: detailedDevices, - message: 'Successfully retrieved list of devices', - }); + return new SuccessResponseDto({ + data: detailedDevices, + message: 'Successfully retrieved list of devices', + }); + } catch (error) { + console.error('Error listing devices in space:', error); + throw new HttpException( + error.message || 'Failed to retrieve devices in space', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } } async validateCommunityAndSpace(communityUuid: string, spaceUuid: string) { @@ -80,7 +87,6 @@ export class SpaceDeviceService { deviceId: string, ): Promise { try { - // Fetch details from TuyaService const tuyaDeviceDetails = await this.tuyaService.getDeviceDetails(deviceId); @@ -101,7 +107,7 @@ export class SpaceDeviceService { } as GetDeviceDetailsInterface; } catch (error) { throw new HttpException( - 'Error fetching device details from Tuya', + `Error fetching device details from Tuya for device id ${deviceId}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } From a133f78bb33189c22149a5da7129fe78e155fa43 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 4 Nov 2024 12:20:08 +0400 Subject: [PATCH 040/109] fixed endpoint for disassociate --- .../subspace/subspace-device.controller.ts | 15 +++- .../subspace/subspace-device.service.ts | 86 ++++++++++++++----- 2 files changed, 77 insertions(+), 24 deletions(-) diff --git a/src/space/controllers/subspace/subspace-device.controller.ts b/src/space/controllers/subspace/subspace-device.controller.ts index 46c4863..189d490 100644 --- a/src/space/controllers/subspace/subspace-device.controller.ts +++ b/src/space/controllers/subspace/subspace-device.controller.ts @@ -1,6 +1,13 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; -import { Controller, Get, Param, Post, UseGuards } from '@nestjs/common'; +import { + Controller, + Delete, + Get, + Param, + Post, + UseGuards, +} from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos'; import { SubspaceDeviceService } from 'src/space/services'; @@ -55,10 +62,12 @@ export class SubSpaceDeviceController { ControllerRoute.SUBSPACE_DEVICE.ACTIONS .DISASSOCIATE_SUBSPACE_DEVICE_DESCRIPTION, }) - @Post('/:deviceUuid') + @Delete('/:deviceUuid') async disassociateDeviceFromSubspace( @Param() params: DeviceSubSpaceParam, ): Promise { - return await this.subspaceDeviceService.associateDeviceToSubspace(params); + return await this.subspaceDeviceService.disassociateDeviceFromSubspace( + params, + ); } } diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index 06464b4..3f14a50 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -57,40 +57,84 @@ export class SubspaceDeviceService { message: 'Successfully retrieved list of devices', }); } - async associateDeviceToSubspace(params: DeviceSubSpaceParam) { + + async associateDeviceToSubspace( + params: DeviceSubSpaceParam, + ): Promise { const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid } = params; + try { + await this.validateCommunityAndSpace(communityUuid, spaceUuid); - await this.validateCommunityAndSpace(communityUuid, spaceUuid); + const subspace = await this.findSubspace(subSpaceUuid); + const device = await this.findDevice(deviceUuid); - const subspace = await this.findSubspace(subSpaceUuid); - const device = await this.findDevice(deviceUuid); + device.subspace = subspace; - device.subspace = subspace; - await this.deviceRepository.save(device); + console.log( + 'Starting to save device to subspace:', + new Date(), + device.subspace, + ); + const newDevice = await this.deviceRepository.save(device); + console.log('Device saved to subspace:', new Date(), newDevice); - return new SuccessResponseDto({ - data: device, - message: 'Successfully associated device to subspace', - }); + return new SuccessResponseDto({ + data: device, + message: 'Successfully associated device to subspace', + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } else { + throw new HttpException( + 'Failed to associate device to subspace', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } - async disassociateDeviceFromSubspace(params: DeviceSubSpaceParam) { + async disassociateDeviceFromSubspace( + params: DeviceSubSpaceParam, + ): Promise { const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid } = params; + try { + await this.validateCommunityAndSpace(communityUuid, spaceUuid); - await this.validateCommunityAndSpace(communityUuid, spaceUuid); + const subspace = await this.findSubspace(subSpaceUuid); + const device = await this.findDevice(deviceUuid); - await this.findSubspace(subSpaceUuid); - const device = await this.findDevice(deviceUuid); + if (!device.subspace || device.subspace.uuid !== subspace.uuid) { + throw new HttpException( + 'Device is not associated with the specified subspace', + HttpStatus.BAD_REQUEST, + ); + } - device.subspace = null; - await this.deviceRepository.save(device); + device.subspace = null; - return new SuccessResponseDto({ - data: device, - message: 'Successfully dissociated device from subspace', - }); + console.log( + 'Starting to save device with null subspace:', + new Date(), + device.subspace, + ); + const updatedDevice = await this.deviceRepository.save(device); + + return new SuccessResponseDto({ + data: updatedDevice, + message: 'Successfully dissociated device from subspace', + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } else { + throw new HttpException( + 'Failed to dissociate device from subspace', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } - // Helper method to validate community and space private async validateCommunityAndSpace( communityUuid: string, From 8cbe910f4caa3fdff53de99c8342a9d71ea67093 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 4 Nov 2024 19:43:49 +0400 Subject: [PATCH 041/109] fixed automation dto --- src/automation/dtos/automation.param.dto.ts | 9 +++++---- src/scene/services/scene.service.ts | 6 +++--- src/space/services/space-scene.service.ts | 11 +++-------- .../services/subspace/subspace-device.service.ts | 8 +------- 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/automation/dtos/automation.param.dto.ts b/src/automation/dtos/automation.param.dto.ts index 4c21461..e0b3990 100644 --- a/src/automation/dtos/automation.param.dto.ts +++ b/src/automation/dtos/automation.param.dto.ts @@ -1,11 +1,12 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsUUID } from 'class-validator'; +import { IsNotEmpty, IsString } from 'class-validator'; export class AutomationParamDto { @ApiProperty({ - description: 'UUID of the automation', - example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + description: 'TuyaId of the automation', + example: 'SfFi2Tbn09btes84', }) - @IsUUID() + @IsString() + @IsNotEmpty() automationUuid: string; } diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 930ee61..ded9408 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -157,7 +157,7 @@ export class SceneService { ); } else { throw new HttpException( - 'An Internal error has been occured', + `An Internal error has been occured ${err}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -178,7 +178,7 @@ export class SceneService { if (!scenesData.length) { throw new HttpException( - `No scenes found for space UUID ${spaceUuid}`, + `No scenes found for space UUID ${spaceUuid} with showInHomePage ${showInHomePage} `, HttpStatus.NOT_FOUND, ); } @@ -199,7 +199,7 @@ export class SceneService { err.message, ); - if (err instanceof BadRequestException) { + if (err instanceof HttpException) { throw err; } else { throw new HttpException( diff --git a/src/space/services/space-scene.service.ts b/src/space/services/space-scene.service.ts index ffc10e0..ac26889 100644 --- a/src/space/services/space-scene.service.ts +++ b/src/space/services/space-scene.service.ts @@ -1,9 +1,4 @@ -import { - BadRequestException, - HttpException, - HttpStatus, - Injectable, -} from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { GetSpaceParam } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SpaceService } from './space.service'; @@ -42,8 +37,8 @@ export class SpaceSceneService { } catch (error) { console.error('Error retrieving scenes:', error); - if (error instanceof BadRequestException) { - throw new HttpException(error.message, HttpStatus.BAD_REQUEST); + if (error instanceof HttpException) { + throw error; } else { throw new HttpException( 'An error occurred while retrieving scenes', diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index 3f14a50..01c6f8a 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -112,12 +112,6 @@ export class SubspaceDeviceService { } device.subspace = null; - - console.log( - 'Starting to save device with null subspace:', - new Date(), - device.subspace, - ); const updatedDevice = await this.deviceRepository.save(device); return new SuccessResponseDto({ @@ -217,7 +211,7 @@ export class SubspaceDeviceService { } as GetDeviceDetailsInterface; } catch (error) { throw new HttpException( - 'Error fetching device details from Tuya', + `Error fetching device details from Tuya for device uuid ${deviceId}.`, HttpStatus.INTERNAL_SERVER_ERROR, ); } From 6e2f3bb8b6f05f77859b1b7607c992148c4ac534 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 4 Nov 2024 20:40:27 +0400 Subject: [PATCH 042/109] excluded tuya device id from response --- src/space/services/space-device.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index 9d9eac8..6f0f183 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -100,7 +100,7 @@ export class SpaceDeviceService { }); // Exclude specific keys and add `productUuid` - const { ...rest } = camelCaseResponse; + const { uuid, ...rest } = camelCaseResponse; return { ...rest, productUuid: product?.uuid, From 0d000e4b60d010a0882b3829861331946111084e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 4 Nov 2024 20:57:32 +0400 Subject: [PATCH 043/109] updated group module --- src/group/controllers/group.controller.ts | 13 ++++++------- src/group/services/group.service.ts | 17 ++++++++--------- src/space/services/space-device.service.ts | 1 + 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/group/controllers/group.controller.ts b/src/group/controllers/group.controller.ts index ed79baf..3098da0 100644 --- a/src/group/controllers/group.controller.ts +++ b/src/group/controllers/group.controller.ts @@ -2,7 +2,6 @@ import { GroupService } from '../services/group.service'; import { Controller, Get, UseGuards, Param, Req } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; -import { UnitPermissionGuard } from 'src/guards/unit.permission.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @ApiTags('Group Module') @@ -17,21 +16,21 @@ export class GroupController { @UseGuards(JwtAuthGuard) @Get(':spaceUuid') async getGroupsBySpaceUuid(@Param('spaceUuid') spaceUuid: string) { - return await this.groupService.getGroupsByUnitUuid(spaceUuid); + return await this.groupService.getGroupsBySpaceUuid(spaceUuid); } - + @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get(':unitUuid/devices/:groupName') + @Get(':spaceUuid/devices/:groupName') async getUnitDevicesByGroupName( - @Param('unitUuid') unitUuid: string, + @Param('spaceUuid') spaceUuid: string, @Param('groupName') groupName: string, @Req() req: any, ) { const userUuid = req.user.uuid; - return await this.groupService.getUnitDevicesByGroupName( - unitUuid, + return await this.groupService.getSpaceDevicesByGroupName( + spaceUuid, groupName, userUuid, ); diff --git a/src/group/services/group.service.ts b/src/group/services/group.service.ts index e8e39f9..9759232 100644 --- a/src/group/services/group.service.ts +++ b/src/group/services/group.service.ts @@ -27,11 +27,11 @@ export class GroupService { }); } - async getGroupsByUnitUuid(unitUuid: string) { + async getGroupsBySpaceUuid(spaceUuid: string) { try { const spaces = await this.spaceRepository.find({ where: { - uuid: unitUuid, + uuid: spaceUuid, }, relations: ['devices', 'devices.productDevice'], }); @@ -61,23 +61,21 @@ export class GroupService { ); } catch (error) { throw new HttpException( - 'This unit does not have any groups', + 'This space does not have any groups', HttpStatus.NOT_FOUND, ); } } - async getUnitDevicesByGroupName( - unitUuid: string, + async getSpaceDevicesByGroupName( + spaceUuid: string, groupName: string, userUuid: string, ) { try { const spaces = await this.spaceRepository.find({ where: { - parent: { - uuid: unitUuid, - }, + uuid: spaceUuid, devices: { productDevice: { prodType: groupName, @@ -122,8 +120,9 @@ export class GroupService { throw new HttpException('No devices found', HttpStatus.NOT_FOUND); return devices.flat(); // Flatten the array since flatMap was used } catch (error) { + console.log(error); throw new HttpException( - 'This unit does not have any devices for the specified group name', + 'This space does not have any devices for the specified group name', HttpStatus.NOT_FOUND, ); } diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index 6f0f183..b806c94 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -100,6 +100,7 @@ export class SpaceDeviceService { }); // Exclude specific keys and add `productUuid` + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { uuid, ...rest } = camelCaseResponse; return { ...rest, From aad794b2cded2b2ebc950787eedc528de9b9c61a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 4 Nov 2024 22:10:05 +0400 Subject: [PATCH 044/109] fixed automation dtos --- src/automation/controllers/automation.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/automation/controllers/automation.controller.ts b/src/automation/controllers/automation.controller.ts index 675cd0b..8e83ef3 100644 --- a/src/automation/controllers/automation.controller.ts +++ b/src/automation/controllers/automation.controller.ts @@ -77,7 +77,7 @@ export class AutomationController { } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Put(':automationId') + @Put(':automationUuid') async updateAutomation( @Body() updateAutomationDto: UpdateAutomationDto, @Param() param: AutomationParamDto, @@ -96,7 +96,7 @@ export class AutomationController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Put('status/:automationId') + @Put('status/:automationUuid') async updateAutomationStatus( @Body() updateAutomationStatusDto: UpdateAutomationStatusDto, @Param() param: AutomationParamDto, From a3eaa0fa3c13802471c133a2b984ab6630d4223c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 5 Nov 2024 00:48:54 +0400 Subject: [PATCH 045/109] Fixed automation bugs --- .../tuya/interfaces/automation.interface.ts | 9 + .../src/integrations/tuya/interfaces/index.ts | 1 + .../interfaces/tuya.response.interface.ts | 2 +- .../tuya/services/tuya.service.ts | 76 ++++-- .../controllers/automation.controller.ts | 12 +- src/automation/dtos/automation.dto.ts | 2 +- .../dtos/delete.automation.param.dto.ts | 9 +- .../interface/automation.interface.ts | 11 + src/automation/services/automation.service.ts | 248 ++++++++++-------- .../subspace/subspace-device.service.ts | 8 +- 10 files changed, 227 insertions(+), 151 deletions(-) create mode 100644 libs/common/src/integrations/tuya/interfaces/automation.interface.ts diff --git a/libs/common/src/integrations/tuya/interfaces/automation.interface.ts b/libs/common/src/integrations/tuya/interfaces/automation.interface.ts new file mode 100644 index 0000000..fb061c2 --- /dev/null +++ b/libs/common/src/integrations/tuya/interfaces/automation.interface.ts @@ -0,0 +1,9 @@ +import { TuyaResponseInterface } from './tuya.response.interface'; + +export interface AddTuyaResponseInterface extends TuyaResponseInterface { + result: { + id: string; + }; + t?: number; + tid?: string; +} diff --git a/libs/common/src/integrations/tuya/interfaces/index.ts b/libs/common/src/integrations/tuya/interfaces/index.ts index 506fc06..06eff87 100644 --- a/libs/common/src/integrations/tuya/interfaces/index.ts +++ b/libs/common/src/integrations/tuya/interfaces/index.ts @@ -1,2 +1,3 @@ export * from './tuya.response.interface'; export * from './tap-to-run-action.interface'; +export * from './automation.interface'; diff --git a/libs/common/src/integrations/tuya/interfaces/tuya.response.interface.ts b/libs/common/src/integrations/tuya/interfaces/tuya.response.interface.ts index 3e3a2a9..141613d 100644 --- a/libs/common/src/integrations/tuya/interfaces/tuya.response.interface.ts +++ b/libs/common/src/integrations/tuya/interfaces/tuya.response.interface.ts @@ -1,5 +1,5 @@ export interface TuyaResponseInterface { success: boolean; msg?: string; - result: boolean; + result: boolean | { id: string }; } diff --git a/libs/common/src/integrations/tuya/services/tuya.service.ts b/libs/common/src/integrations/tuya/services/tuya.service.ts index 9aabb9a..ff69aa9 100644 --- a/libs/common/src/integrations/tuya/services/tuya.service.ts +++ b/libs/common/src/integrations/tuya/services/tuya.service.ts @@ -1,7 +1,11 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; -import { ConvertedAction, TuyaResponseInterface } from '../interfaces'; +import { + AddTuyaResponseInterface, + ConvertedAction, + TuyaResponseInterface, +} from '../interfaces'; @Injectable() export class TuyaService { @@ -144,32 +148,72 @@ export class TuyaService { ) { const path = `/v2.0/cloud/scene/rule`; + const response: AddTuyaResponseInterface = await this.tuya.request({ + method: 'POST', + path, + body: { + space_id: spaceId, + name: automationName, + effective_time: { + ...effectiveTime, + timezone_id: 'Asia/Dubai', + }, + type: 'automation', + decision_expr: decisionExpr, + conditions: conditions, + actions: actions, + }, + }); + + if (!response.success) { + throw new HttpException(response.msg, HttpStatus.BAD_REQUEST); + } + + return response; + } + + async deleteAutomation(spaceId: string, automationUuid: string) { + const path = `/v2.0/cloud/scene/rule?ids=${automationUuid}&space_id=${spaceId}`; + const response = await this.tuya.request({ + method: 'DELETE', + path, + }); + + if (!response.success) { + throw new HttpException( + 'Failed to delete automation', + HttpStatus.NOT_FOUND, + ); + } + + return response; + } + + async updateAutomationState( + spaceId: string, + automationUuid: string, + isEnable: boolean, + ) { + const path = `/v2.0/cloud/scene/rule/state?space_id=${spaceId}`; + try { - const response = await this.tuya.request({ - method: 'POST', + const response: TuyaResponseInterface = await this.tuya.request({ + method: 'PUT', path, body: { - space_id: spaceId, - name: automationName, - effective_time: { - ...effectiveTime, - timezone_id: 'Asia/Dubai', - }, - type: 'automation', - decision_expr: decisionExpr, - conditions: conditions, - actions: actions, + ids: automationUuid, + is_enable: isEnable, }, }); if (!response.success) { - throw new HttpException(response.msg, HttpStatus.BAD_REQUEST); + throw new HttpException('Automation not found', HttpStatus.NOT_FOUND); } - return response.result; + return response; } catch (error) { throw new HttpException( - error.message || 'Failed to create automation in Tuya', + error.message || 'Failed to update automation state', error.status || HttpStatus.INTERNAL_SERVER_ERROR, ); } diff --git a/src/automation/controllers/automation.controller.ts b/src/automation/controllers/automation.controller.ts index 8e83ef3..49a5012 100644 --- a/src/automation/controllers/automation.controller.ts +++ b/src/automation/controllers/automation.controller.ts @@ -18,11 +18,7 @@ import { } from '../dtos/automation.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; -import { - AutomationParamDto, - DeleteAutomationParamDto, - SpaceParamDto, -} from '../dtos'; +import { AutomationParamDto, SpaceParamDto } from '../dtos'; @ApiTags('Automation Module') @Controller({ @@ -45,6 +41,7 @@ export class AutomationController { data: automation, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':spaceUuid') @@ -67,14 +64,15 @@ export class AutomationController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Delete(':unitUuid/:automationId') - async deleteAutomation(@Param() param: DeleteAutomationParamDto) { + @Delete(':automationUuid') + async deleteAutomation(@Param() param: AutomationParamDto) { await this.automationService.deleteAutomation(param); return { statusCode: HttpStatus.OK, message: 'Automation Deleted Successfully', }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put(':automationUuid') diff --git a/src/automation/dtos/automation.dto.ts b/src/automation/dtos/automation.dto.ts index 60104ac..6241cce 100644 --- a/src/automation/dtos/automation.dto.ts +++ b/src/automation/dtos/automation.dto.ts @@ -10,7 +10,7 @@ import { } from 'class-validator'; import { Type } from 'class-transformer'; -class EffectiveTime { +export class EffectiveTime { @ApiProperty({ description: 'Start time', required: true }) @IsString() @IsNotEmpty() diff --git a/src/automation/dtos/delete.automation.param.dto.ts b/src/automation/dtos/delete.automation.param.dto.ts index 96ee49b..6421a80 100644 --- a/src/automation/dtos/delete.automation.param.dto.ts +++ b/src/automation/dtos/delete.automation.param.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsUUID } from 'class-validator'; +import { IsNotEmpty, IsString, IsUUID } from 'class-validator'; export class DeleteAutomationParamDto { @ApiProperty({ @@ -10,9 +10,10 @@ export class DeleteAutomationParamDto { spaceUuid: string; @ApiProperty({ - description: 'UUID of the Automation', - example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + description: 'TuyaId of the automation', + example: 'SfFi2Tbn09btes84', }) - @IsUUID() + @IsString() + @IsNotEmpty() automationUuid: string; } diff --git a/src/automation/interface/automation.interface.ts b/src/automation/interface/automation.interface.ts index eed3ef7..9d7f40f 100644 --- a/src/automation/interface/automation.interface.ts +++ b/src/automation/interface/automation.interface.ts @@ -1,3 +1,5 @@ +import { EffectiveTime } from '../dtos'; + export interface AddAutomationInterface { success: boolean; msg?: string; @@ -48,3 +50,12 @@ export interface AutomationDetailsResult { name: string; type: string; } + +export interface AddAutomationParams { + actions: Action[]; + conditions: Condition[]; + automationName: string; + effectiveTime: EffectiveTime; + decisionExpr: string; + spaceTuyaId: string; +} diff --git a/src/automation/services/automation.service.ts b/src/automation/services/automation.service.ts index a34eb49..468a4ae 100644 --- a/src/automation/services/automation.service.ts +++ b/src/automation/services/automation.service.ts @@ -7,7 +7,7 @@ import { import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddAutomationDto, - DeleteAutomationParamDto, + AutomationParamDto, UpdateAutomationDto, UpdateAutomationStatusDto, } from '../dtos'; @@ -17,11 +17,10 @@ import { convertKeysToSnakeCase } from '@app/common/helper/snakeCaseConverter'; import { DeviceService } from 'src/device/services'; import { Action, - AddAutomationInterface, + AddAutomationParams, AutomationDetailsResult, AutomationResponseData, Condition, - DeleteAutomationInterface, GetAutomationBySpaceInterface, } from '../interface/automation.interface'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; @@ -30,6 +29,7 @@ import { EntityTypeEnum, } from '@app/common/constants/automation.enum'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; +import { ConvertedAction } from '@app/common/integrations/tuya/interfaces'; @Injectable() export class AutomationService { @@ -50,39 +50,26 @@ export class AutomationService { }); } - async addAutomation(addAutomationDto: AddAutomationDto, spaceTuyaId = null) { + async addAutomation(addAutomationDto: AddAutomationDto) { try { - const { automationName, effectiveTime, decisionExpr } = addAutomationDto; - const space = await this.getSpaceByUuid(addAutomationDto.spaceUuid); - - const actions = await this.processEntities( - addAutomationDto.actions, - 'actionExecutor', - { [ActionExecutorEnum.DEVICE_ISSUE]: true }, - this.deviceService, - ); - - const conditions = await this.processEntities( - addAutomationDto.conditions, - 'entityType', - { [EntityTypeEnum.DEVICE_REPORT]: true }, - this.deviceService, - ); - - const response = (await this.tuyaService.createAutomation( - space.spaceTuyaUuid, + const { automationName, effectiveTime, decisionExpr, - conditions, actions, - )) as AddAutomationInterface; - - return { - id: response?.result.id, - }; + conditions, + } = addAutomationDto; + const space = await this.getSpaceByUuid(addAutomationDto.spaceUuid); + const response = await this.add({ + automationName, + effectiveTime, + decisionExpr, + actions, + conditions, + spaceTuyaId: space.spaceTuyaUuid, + }); + return response; } catch (err) { - console.log(err); if (err instanceof BadRequestException) { throw err; } else { @@ -94,6 +81,33 @@ export class AutomationService { } } + async add(params: AddAutomationParams) { + try { + const formattedActions = await this.prepareActions(params.actions); + const formattedCondition = await this.prepareConditions( + params.conditions, + ); + + const response = await this.tuyaService.createAutomation( + params.spaceTuyaId, + params.automationName, + params.effectiveTime, + params.decisionExpr, + formattedCondition, + formattedActions, + ); + + return { + id: response?.result.id, + }; + } catch (error) { + throw new HttpException( + error.message || 'Failed to add automation', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getSpaceByUuid(spaceUuid: string) { try { const space = await this.spaceRepository.findOne({ @@ -120,6 +134,7 @@ export class AutomationService { } } } + async getAutomationBySpace(spaceUuid: string) { try { const space = await this.getSpaceByUuid(spaceUuid); @@ -277,34 +292,45 @@ export class AutomationService { } } - async deleteAutomation(param: DeleteAutomationParamDto, spaceTuyaId = null) { + async deleteAutomation(param: AutomationParamDto) { try { - const { automationUuid, spaceUuid } = param; - let tuyaSpaceId; - if (!spaceTuyaId) { - const space = await this.getSpaceByUuid(spaceUuid); - tuyaSpaceId = space.spaceTuyaUuid; - if (!tuyaSpaceId) { - throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); - } - } else { - tuyaSpaceId = spaceTuyaId; + const { automationUuid } = param; + + const automation = await this.getAutomationDetails(automationUuid, true); + + if (!automation && !automation.spaceId) { + throw new HttpException( + `Invalid automationid ${automationUuid}`, + HttpStatus.BAD_REQUEST, + ); } - - const path = `/v2.0/cloud/scene/rule?ids=${automationUuid}&space_id=${tuyaSpaceId}`; - const response: DeleteAutomationInterface = await this.tuya.request({ - method: 'DELETE', - path, - }); - - if (!response.success) { - throw new HttpException('Automation not found', HttpStatus.NOT_FOUND); - } - + const response = this.tuyaService.deleteAutomation( + automation.spaceId, + automationUuid, + ); return response; } catch (err) { - if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException + if (err instanceof HttpException) { + throw err; + } else { + throw new HttpException( + err.message || 'Automation not found', + err.status || HttpStatus.NOT_FOUND, + ); + } + } + } + + async delete(tuyaSpaceId: string, automationUuid: string) { + try { + const response = await this.tuyaService.deleteAutomation( + tuyaSpaceId, + automationUuid, + ); + return response; + } catch (err) { + if (err instanceof HttpException) { + throw err; } else { throw new HttpException( err.message || 'Automation not found', @@ -318,28 +344,28 @@ export class AutomationService { updateAutomationDto: UpdateAutomationDto, automationUuid: string, ) { + const { actions, conditions, automationName, effectiveTime, decisionExpr } = + updateAutomationDto; try { - const spaceTuyaId = await this.getAutomationDetails(automationUuid, true); - if (!spaceTuyaId.spaceId) { + const automation = await this.getAutomationDetails(automationUuid, true); + if (!automation.spaceId) { throw new HttpException( "Automation doesn't exist", HttpStatus.NOT_FOUND, ); } - const addAutomation = { - ...updateAutomationDto, - spaceUuid: null, - }; - const newAutomation = await this.addAutomation( - addAutomation, - spaceTuyaId.spaceId, - ); - const params: DeleteAutomationParamDto = { - spaceUuid: spaceTuyaId.spaceId, - automationUuid: automationUuid, - }; + + const newAutomation = await this.add({ + actions, + conditions, + automationName, + effectiveTime, + decisionExpr, + spaceTuyaId: automation.spaceId, + }); + if (newAutomation.id) { - await this.deleteAutomation(null, params); + await this.delete(automation.spaceId, automationUuid); return newAutomation; } } catch (err) { @@ -357,29 +383,21 @@ export class AutomationService { updateAutomationStatusDto: UpdateAutomationStatusDto, automationUuid: string, ) { + const { isEnable, spaceUuid } = updateAutomationStatusDto; try { - const space = await this.getSpaceByUuid( - updateAutomationStatusDto.spaceUuid, - ); + const space = await this.getSpaceByUuid(spaceUuid); if (!space.spaceTuyaUuid) { - throw new BadRequestException( - `Invalid space UUID ${updateAutomationStatusDto.spaceUuid}`, + throw new HttpException( + `Invalid space UUID ${spaceUuid}`, + HttpStatus.NOT_FOUND, ); } - const path = `/v2.0/cloud/scene/rule/state?space_id=${space.spaceTuyaUuid}`; - const response: DeleteAutomationInterface = await this.tuya.request({ - method: 'PUT', - path, - body: { - ids: automationUuid, - is_enable: updateAutomationStatusDto.isEnable, - }, - }); - - if (!response.success) { - throw new HttpException('Automation not found', HttpStatus.NOT_FOUND); - } + const response = await this.tuyaService.updateAutomationState( + space.spaceTuyaUuid, + automationUuid, + isEnable, + ); return response; } catch (err) { @@ -394,41 +412,41 @@ export class AutomationService { } } - async processEntities( - entities: T[], // Accepts either Action[] or Condition[] - lookupKey: keyof T, // The key to look up, specific to T - entityTypeOrExecutorMap: { - [key in ActionExecutorEnum | EntityTypeEnum]?: boolean; - }, - deviceService: { - getDeviceByDeviceUuid: ( - id: string, - flag: boolean, - ) => Promise<{ deviceTuyaUuid: string } | null>; - }, - ): Promise { - // Returns the same type as provided in the input - return Promise.all( - entities.map(async (entity) => { - // Convert keys to snake case (assuming a utility function exists) - const processedEntity = convertKeysToSnakeCase(entity) as T; + private async prepareActions(actions: Action[]): Promise { + const convertedData = convertKeysToSnakeCase(actions) as ConvertedAction[]; - // Check if entity needs device UUID lookup - const key = processedEntity[lookupKey]; - if ( - entityTypeOrExecutorMap[key as ActionExecutorEnum | EntityTypeEnum] - ) { - const device = await deviceService.getDeviceByDeviceUuid( - processedEntity.entityId, + await Promise.all( + convertedData.map(async (action) => { + if (action.action_executor === ActionExecutorEnum.DEVICE_ISSUE) { + const device = await this.deviceService.getDeviceByDeviceUuid( + action.entity_id, false, ); if (device) { - processedEntity.entityId = device.deviceTuyaUuid; + action.entity_id = device.deviceTuyaUuid; } } - - return processedEntity; }), ); + + return convertedData; + } + + private async prepareConditions(conditions: Condition[]) { + const convertedData = convertKeysToSnakeCase(conditions); + await Promise.all( + convertedData.map(async (condition) => { + if (condition.entity_type === EntityTypeEnum.DEVICE_REPORT) { + const device = await this.deviceService.getDeviceByDeviceUuid( + condition.entity_id, + false, + ); + if (device) { + condition.entity_id = device.deviceTuyaUuid; + } + } + }), + ); + return convertedData; } } diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index 01c6f8a..3b453fd 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -70,16 +70,10 @@ export class SubspaceDeviceService { device.subspace = subspace; - console.log( - 'Starting to save device to subspace:', - new Date(), - device.subspace, - ); const newDevice = await this.deviceRepository.save(device); - console.log('Device saved to subspace:', new Date(), newDevice); return new SuccessResponseDto({ - data: device, + data: newDevice, message: 'Successfully associated device to subspace', }); } catch (error) { From cde9e8b602b4cdd1cd7dcdd88d79df5b56879057 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 5 Nov 2024 11:48:57 +0400 Subject: [PATCH 046/109] Fixed user space permission --- src/space/controllers/space.controller.ts | 3 +- src/space/dtos/add.space.dto.ts | 40 ++++++ .../controllers/user-space.controller.ts | 38 +++++- src/users/dtos/add.space.dto.ts | 68 ++++++++++ src/users/dtos/index.ts | 1 + src/users/services/user-space.service.ts | 118 +++++++++++++++++- src/users/user.module.ts | 8 ++ 7 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 src/users/dtos/add.space.dto.ts diff --git a/src/space/controllers/space.controller.ts b/src/space/controllers/space.controller.ts index cb8cbb8..e0615eb 100644 --- a/src/space/controllers/space.controller.ts +++ b/src/space/controllers/space.controller.ts @@ -111,6 +111,7 @@ export class SpaceController { return this.spaceService.getSpacesHierarchyForSpace(params.spaceUuid); } + //should it be post? @ApiBearerAuth() @UseGuards(JwtAuthGuard) @ApiOperation({ @@ -118,7 +119,7 @@ export class SpaceController { description: ControllerRoute.SPACE.ACTIONS.CREATE_INVITATION_CODE_SPACE_DESCRIPTION, }) - @Post(':spaceUuid/invitation-code') + @Get(':spaceUuid/invitation-code') async generateSpaceInvitationCode( @Param() params: GetSpaceParam, ): Promise { diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index 9d383bd..a33bb34 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -33,3 +33,43 @@ export class AddSpaceDto { @IsBoolean() isPrivate: boolean; } + +export class AddUserSpaceDto { + @ApiProperty({ + description: 'spaceUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public spaceUuid: string; + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} + +export class AddUserSpaceUsingCodeDto { + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; + @ApiProperty({ + description: 'inviteCode', + required: true, + }) + @IsString() + @IsNotEmpty() + public inviteCode: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/users/controllers/user-space.controller.ts b/src/users/controllers/user-space.controller.ts index 519a033..df36a5a 100644 --- a/src/users/controllers/user-space.controller.ts +++ b/src/users/controllers/user-space.controller.ts @@ -1,11 +1,20 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; -import { Controller, Get, Param, UseGuards } from '@nestjs/common'; +import { + Body, + Controller, + Get, + HttpException, + HttpStatus, + Param, + Post, + UseGuards, +} from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { UserSpaceService } from '../services'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { UserParamDto } from '../dtos'; +import { AddUserSpaceUsingCodeDto, UserParamDto } from '../dtos'; @ApiTags('User Module') @Controller({ @@ -27,4 +36,29 @@ export class UserSpaceController { ): Promise { return this.userSpaceService.getSpacesForUser(params.userUuid); } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('/verify-code') + async verifyCodeAndAddUserSpace( + @Body() dto: AddUserSpaceUsingCodeDto, + @Param() params: UserParamDto, + ) { + try { + await this.userSpaceService.verifyCodeAndAddUserSpace( + dto, + params.userUuid, + ); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'user space added successfully', + }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/users/dtos/add.space.dto.ts b/src/users/dtos/add.space.dto.ts new file mode 100644 index 0000000..635cec9 --- /dev/null +++ b/src/users/dtos/add.space.dto.ts @@ -0,0 +1,68 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IsBoolean, + IsNotEmpty, + IsOptional, + IsString, + IsUUID, +} from 'class-validator'; + +export class AddSpaceDto { + @ApiProperty({ + description: 'Name of the space (e.g., Floor 1, Unit 101)', + example: 'Unit 101', + }) + @IsString() + @IsNotEmpty() + spaceName: string; + + @ApiProperty({ + description: 'UUID of the parent space (if any, for hierarchical spaces)', + example: 'f5d7e9c3-44bc-4b12-88f1-1b3cda84752e', + required: false, + }) + @IsUUID() + @IsOptional() + parentUuid?: string; + + @ApiProperty({ + description: 'Indicates whether the space is private or public', + example: false, + default: false, + }) + @IsBoolean() + isPrivate: boolean; +} + +export class AddUserSpaceDto { + @ApiProperty({ + description: 'spaceUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public spaceUuid: string; + @ApiProperty({ + description: 'userUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public userUuid: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} + +export class AddUserSpaceUsingCodeDto { + @ApiProperty({ + description: 'inviteCode', + required: true, + }) + @IsString() + @IsNotEmpty() + public inviteCode: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/users/dtos/index.ts b/src/users/dtos/index.ts index 5ca1ea7..83bb739 100644 --- a/src/users/dtos/index.ts +++ b/src/users/dtos/index.ts @@ -1,3 +1,4 @@ export * from './update.user.dto'; export * from './user-community-param.dto'; export * from './user-param.dto'; +export * from './add.space.dto'; diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index 68e6f00..ca3b8d9 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -2,10 +2,20 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { AddUserSpaceDto, AddUserSpaceUsingCodeDto } from '../dtos'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; +import { UserDevicePermissionService } from 'src/user-device-permission/services'; +import { PermissionType } from '@app/common/constants/permission-type.enum'; +import { SpaceEntity } from '@app/common/modules/space/entities'; @Injectable() export class UserSpaceService { - constructor(private readonly userSpaceRepository: UserSpaceRepository) {} + constructor( + private readonly userSpaceRepository: UserSpaceRepository, + private readonly spaceRepository: SpaceRepository, + private readonly userDevicePermissionService: UserDevicePermissionService, + ) {} async getSpacesForUser(userUuid: string): Promise { const userSpaces = await this.userSpaceRepository.find({ @@ -25,4 +35,110 @@ export class UserSpaceService { message: `Spaces for user ${userUuid} retrieved successfully`, }); } + + async verifyCodeAndAddUserSpace( + params: AddUserSpaceUsingCodeDto, + userUuid: string, + ) { + try { + const space = await this.findUnitByInviteCode(params.inviteCode); + + await this.addUserToSpace(userUuid, space.uuid); + + await this.clearUnitInvitationCode(space.uuid); + + const deviceUUIDs = await this.getDeviceUUIDsForSpace(space.uuid); + + await this.addUserPermissionsToDevices(userUuid, deviceUUIDs); + } catch (err) { + throw new HttpException( + 'Invalid invitation code', + HttpStatus.BAD_REQUEST, + ); + } + } + + private async findUnitByInviteCode(inviteCode: string): Promise { + const space = await this.spaceRepository.findOneOrFail({ + where: { + invitationCode: inviteCode, + }, + }); + + return space; + } + + private async addUserToSpace(userUuid: string, spaceUuid: string) { + const user = await this.addUserSpace({ userUuid, spaceUuid }); + + if (user.uuid) { + return user; + } else { + throw new HttpException( + 'Failed to add user to unit', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async addUserSpace(addUserSpaceDto: AddUserSpaceDto) { + try { + return await this.userSpaceRepository.save({ + user: { uuid: addUserSpaceDto.userUuid }, + space: { uuid: addUserSpaceDto.spaceUuid }, + }); + } catch (err) { + if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) { + throw new HttpException( + 'User already belongs to this unit', + HttpStatus.BAD_REQUEST, + ); + } + throw new HttpException( + err.message || 'Internal Server Error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private async clearUnitInvitationCode(spaceUuid: string) { + await this.spaceRepository.update( + { uuid: spaceUuid }, + { invitationCode: null }, + ); + } + + private async getDeviceUUIDsForSpace( + unitUuid: string, + ): Promise<{ uuid: string }[]> { + const devices = await this.spaceRepository.find({ + where: { uuid: unitUuid }, + relations: ['devicesSpaceEntity', 'devicesSpaceEntity.productDevice'], + }); + + const allDevices = devices.flatMap((space) => space.devices); + + return allDevices.map((device) => ({ uuid: device.uuid })); + } + + private async addUserPermissionsToDevices( + userUuid: string, + deviceUUIDs: { uuid: string }[], + ): Promise { + const permissionPromises = deviceUUIDs.map(async (device) => { + try { + await this.userDevicePermissionService.addUserPermission({ + userUuid, + deviceUuid: device.uuid, + permissionType: PermissionType.CONTROLLABLE, + }); + } catch (error) { + console.error( + `Failed to add permission for device ${device.uuid}: ${error.message}`, + ); + } + }); + + await Promise.all(permissionPromises); + } } diff --git a/src/users/user.module.ts b/src/users/user.module.ts index d65ee68..20bd06f 100644 --- a/src/users/user.module.ts +++ b/src/users/user.module.ts @@ -12,6 +12,10 @@ import { UserSpaceController } from './controllers'; import { CommunityModule } from 'src/community/community.module'; import { UserSpaceService } from './services'; import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { UserDevicePermissionService } from 'src/user-device-permission/services'; +import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; +import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; @Module({ imports: [ConfigModule, CommunityModule], @@ -20,9 +24,13 @@ import { CommunityRepository } from '@app/common/modules/community/repositories' UserService, UserRepository, RegionRepository, + SpaceRepository, TimeZoneRepository, UserSpaceRepository, CommunityRepository, + UserDevicePermissionService, + DeviceUserPermissionRepository, + PermissionTypeRepository, UserSpaceService, ], exports: [UserService], From b572143f04c0a75e6466d3810e837d4427544499 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 5 Nov 2024 12:25:42 +0400 Subject: [PATCH 047/109] add subspace relation --- src/space/services/subspace/subspace-device.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index 3b453fd..0e57bda 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -100,7 +100,7 @@ export class SubspaceDeviceService { if (!device.subspace || device.subspace.uuid !== subspace.uuid) { throw new HttpException( - 'Device is not associated with the specified subspace', + `Device ${deviceUuid} is not associated with the specified subspace ${subSpaceUuid} `, HttpStatus.BAD_REQUEST, ); } @@ -169,6 +169,7 @@ export class SubspaceDeviceService { private async findDevice(deviceUuid: string) { const device = await this.deviceRepository.findOne({ where: { uuid: deviceUuid }, + relations: ['subspace'], }); if (!device) { this.throwNotFound('Device', deviceUuid); From a39c28eedae81ee31fbfbe49467e17ea085d261d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 5 Nov 2024 13:04:50 +0400 Subject: [PATCH 048/109] fixed showInHomePage --- src/scene/services/scene.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index ded9408..bd83ac1 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -172,7 +172,7 @@ export class SceneService { const scenesData = await this.sceneRepository.find({ where: { spaceUuid, - ...(showInHomePage !== undefined ? { showInHomePage } : {}), + ...(showInHomePage ? { showInHomePage } : {}), }, }); From 7e07d2216b0a738a3861796b5a648fe8c5efe612 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 5 Nov 2024 15:51:14 +0400 Subject: [PATCH 049/109] added sceneIcon --- src/scene/services/scene.service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index bd83ac1..1818463 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -174,6 +174,7 @@ export class SceneService { spaceUuid, ...(showInHomePage ? { showInHomePage } : {}), }, + relations: ['sceneIcon'], }); if (!scenesData.length) { @@ -469,6 +470,8 @@ export class SceneService { relations: ['sceneIcon'], }); + console.log(scene); + if (!scene) { throw new HttpException( `Invalid scene with id ${sceneUuid}`, From 560520ff6e58f83feaa72dc1de2893385462994f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 5 Nov 2024 15:56:22 +0400 Subject: [PATCH 050/109] removed logging --- src/space/services/subspace/subspace-device.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index 0e57bda..1dac1ee 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -199,6 +199,7 @@ export class SubspaceDeviceService { }, }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { uuid, ...rest } = camelCaseResponse; return { ...rest, From 6df2f11371f8ea96df93a677cf750e15a9a7ac49 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 6 Nov 2024 09:44:17 +0400 Subject: [PATCH 051/109] removed log --- src/scene/services/scene.service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 1818463..e480d2f 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -470,8 +470,6 @@ export class SceneService { relations: ['sceneIcon'], }); - console.log(scene); - if (!scene) { throw new HttpException( `Invalid scene with id ${sceneUuid}`, From cd7e7181ba49057409af190cbe715e73319961af Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 6 Nov 2024 19:03:00 +0400 Subject: [PATCH 052/109] fixed user invite --- src/users/services/user-space.service.ts | 48 +++++++++++++++--------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index ca3b8d9..d6945c9 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -41,7 +41,7 @@ export class UserSpaceService { userUuid: string, ) { try { - const space = await this.findUnitByInviteCode(params.inviteCode); + const space = await this.findSpaceByInviteCode(params.inviteCode); await this.addUserToSpace(userUuid, space.uuid); @@ -51,31 +51,43 @@ export class UserSpaceService { await this.addUserPermissionsToDevices(userUuid, deviceUUIDs); } catch (err) { + if (err instanceof HttpException) { + throw err; + } else { + throw new HttpException( + `An unexpected error occurred: ${err.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + + private async findSpaceByInviteCode( + inviteCode: string, + ): Promise { + try { + const space = await this.spaceRepository.findOneOrFail({ + where: { + invitationCode: inviteCode, + }, + }); + return space; + } catch (error) { throw new HttpException( - 'Invalid invitation code', - HttpStatus.BAD_REQUEST, + 'Space with the provided invite code not found', + HttpStatus.NOT_FOUND, ); } } - private async findUnitByInviteCode(inviteCode: string): Promise { - const space = await this.spaceRepository.findOneOrFail({ - where: { - invitationCode: inviteCode, - }, - }); - - return space; - } - private async addUserToSpace(userUuid: string, spaceUuid: string) { - const user = await this.addUserSpace({ userUuid, spaceUuid }); + try { + const user = await this.addUserSpace({ userUuid, spaceUuid }); - if (user.uuid) { return user; - } else { + } catch (error) { throw new HttpException( - 'Failed to add user to unit', + `An error occurred while adding user to space ${spaceUuid} : ${error.message}}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -113,7 +125,7 @@ export class UserSpaceService { ): Promise<{ uuid: string }[]> { const devices = await this.spaceRepository.find({ where: { uuid: unitUuid }, - relations: ['devicesSpaceEntity', 'devicesSpaceEntity.productDevice'], + relations: ['devices', 'devices.productDevice'], }); const allDevices = devices.flatMap((space) => space.devices); From 2edf65ab0729de63b1f7b07d2f37ce821beccbaa Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 6 Nov 2024 22:46:56 +0400 Subject: [PATCH 053/109] added delete scene from database --- src/scene/services/scene.service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index e480d2f..6083a02 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -449,6 +449,9 @@ export class SceneService { const space = await this.getSpaceByUuid(scene.spaceUuid); await this.delete(scene.sceneTuyaUuid, space.spaceTuyaUuid); + + await this.sceneRepository.remove(scene); + return new SuccessResponseDto({ message: `Scene with ID ${sceneUuid} deleted successfully`, }); From af6aefca9c17d8008e3c01afd34a099277650f86 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 7 Nov 2024 10:41:54 +0400 Subject: [PATCH 054/109] On empty list, return empty array. Instead of error --- libs/common/src/seed/services/scene.icon.seeder.ts | 2 -- src/scene/services/scene.service.ts | 5 +---- src/users/services/user-space.service.ts | 7 ++----- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/libs/common/src/seed/services/scene.icon.seeder.ts b/libs/common/src/seed/services/scene.icon.seeder.ts index 3f363ce..3d698eb 100644 --- a/libs/common/src/seed/services/scene.icon.seeder.ts +++ b/libs/common/src/seed/services/scene.icon.seeder.ts @@ -14,8 +14,6 @@ export class SceneIconSeeder { }); if (defaultSceneIconData.length <= 0) { - console.log('Creating default scene icon...'); - await this.createDefaultSceneIcon(); } } catch (err) { diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 6083a02..75cfb25 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -178,10 +178,7 @@ export class SceneService { }); if (!scenesData.length) { - throw new HttpException( - `No scenes found for space UUID ${spaceUuid} with showInHomePage ${showInHomePage} `, - HttpStatus.NOT_FOUND, - ); + return []; } const scenes = await Promise.all( diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index d6945c9..1c7ac6e 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -18,16 +18,13 @@ export class UserSpaceService { ) {} async getSpacesForUser(userUuid: string): Promise { - const userSpaces = await this.userSpaceRepository.find({ + let userSpaces = await this.userSpaceRepository.find({ where: { user: { uuid: userUuid } }, relations: ['space', 'space.community'], }); if (!userSpaces || userSpaces.length === 0) { - throw new HttpException( - `No spaces found for user with ID ${userUuid}`, - HttpStatus.NOT_FOUND, - ); + userSpaces = []; } return new SuccessResponseDto({ From c7c1a6ddc9aeee70d16203eb204400abc6958f96 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 11 Nov 2024 11:26:16 +0400 Subject: [PATCH 055/109] removed duplicated code --- src/space/services/space-device.service.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index b806c94..df79a5e 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -38,7 +38,6 @@ export class SpaceDeviceService { productUuid: device.productDevice.uuid, productType: device.productDevice.prodType, isActive: device.isActive, - createdAt: device.createdAt, updatedAt: device.updatedAt, ...tuyaDetails, }; @@ -93,18 +92,11 @@ export class SpaceDeviceService { // Convert keys to camel case const camelCaseResponse = convertKeysToCamelCase(tuyaDeviceDetails); - const product = await this.productRepository.findOne({ - where: { - prodId: camelCaseResponse.productId, - }, - }); - // Exclude specific keys and add `productUuid` // eslint-disable-next-line @typescript-eslint/no-unused-vars const { uuid, ...rest } = camelCaseResponse; return { ...rest, - productUuid: product?.uuid, } as GetDeviceDetailsInterface; } catch (error) { throw new HttpException( From a1f74938fccf929c6edd6e468f320fe60da4d637 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 15 Nov 2024 13:27:20 +0400 Subject: [PATCH 056/109] removed unused entity --- libs/common/src/database/database.module.ts | 2 - .../src/modules/space/dtos/space.dto.ts | 10 ---- .../modules/space/entities/space.entity.ts | 29 +++-------- .../space/repositories/space.repository.ts | 10 +--- .../modules/space/space.repository.module.ts | 6 +-- libs/common/src/seed/seeder.module.ts | 4 -- .../src/seed/services/seeder.service.ts | 3 -- .../src/seed/services/space.type.seeder.ts | 52 ------------------- 8 files changed, 11 insertions(+), 105 deletions(-) delete mode 100644 libs/common/src/seed/services/space.type.seeder.ts diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 44e69c9..1f30866 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -9,7 +9,6 @@ import { ProductEntity } from '../modules/product/entities'; import { DeviceEntity } from '../modules/device/entities'; import { PermissionTypeEntity } from '../modules/permission/entities'; import { SpaceEntity, SubspaceEntity } from '../modules/space/entities'; -import { SpaceTypeEntity } from '../modules/space/entities'; import { UserSpaceEntity } from '../modules/user/entities'; import { DeviceUserPermissionEntity } from '../modules/device/entities'; import { UserRoleEntity } from '../modules/user/entities'; @@ -47,7 +46,6 @@ import { SceneEntity, SceneIconEntity } from '../modules/scene/entities'; CommunityEntity, SpaceEntity, SubspaceEntity, - SpaceTypeEntity, UserSpaceEntity, DeviceUserPermissionEntity, UserRoleEntity, diff --git a/libs/common/src/modules/space/dtos/space.dto.ts b/libs/common/src/modules/space/dtos/space.dto.ts index 04072fd..98706d0 100644 --- a/libs/common/src/modules/space/dtos/space.dto.ts +++ b/libs/common/src/modules/space/dtos/space.dto.ts @@ -21,13 +21,3 @@ export class SpaceDto { @IsNotEmpty() public invitationCode: string; } - -export class SpaceTypeDto { - @IsString() - @IsNotEmpty() - public uuid: string; - - @IsString() - @IsNotEmpty() - public type: string; -} diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 196a522..04ebf83 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -6,33 +6,13 @@ import { OneToMany, Unique, } from 'typeorm'; -import { SpaceDto, SpaceTypeDto } from '../dtos'; +import { SpaceDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { UserSpaceEntity } from '../../user/entities'; import { DeviceEntity } from '../../device/entities'; import { CommunityEntity } from '../../community/entities'; import { SubspaceEntity } from './subspace.entity'; -@Entity({ name: 'space-type' }) -export class SpaceTypeEntity extends AbstractEntity { - @Column({ - type: 'uuid', - default: () => 'gen_random_uuid()', - nullable: false, - }) - public uuid: string; - - @Column({ - nullable: false, - }) - type: string; - - constructor(partial: Partial) { - super(); - Object.assign(this, partial); - } -} - @Entity({ name: 'space' }) @Unique(['invitationCode']) export class SpaceEntity extends AbstractEntity { @@ -77,6 +57,13 @@ export class SpaceEntity extends AbstractEntity { }) subspaces?: SubspaceEntity[]; + // Position columns + @Column({ type: 'float', nullable: false, default: 0 }) + public x: number; // X coordinate for position + + @Column({ type: 'float', nullable: false, default: 0 }) + public y: number; // Y coordinate for position + @OneToMany( () => DeviceEntity, (devicesSpaceEntity) => devicesSpaceEntity.spaceDevice, diff --git a/libs/common/src/modules/space/repositories/space.repository.ts b/libs/common/src/modules/space/repositories/space.repository.ts index 5e0962c..2806c0e 100644 --- a/libs/common/src/modules/space/repositories/space.repository.ts +++ b/libs/common/src/modules/space/repositories/space.repository.ts @@ -1,6 +1,6 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; -import { SpaceEntity, SpaceTypeEntity, SubspaceEntity } from '../entities'; +import { SpaceEntity, SubspaceEntity } from '../entities'; @Injectable() export class SpaceRepository extends Repository { @@ -8,14 +8,6 @@ export class SpaceRepository extends Repository { super(SpaceEntity, dataSource.createEntityManager()); } } - -@Injectable() -export class SpaceTypeRepository extends Repository { - constructor(private dataSource: DataSource) { - super(SpaceTypeEntity, dataSource.createEntityManager()); - } -} - @Injectable() export class SubspaceRepository extends Repository { constructor(private dataSource: DataSource) { diff --git a/libs/common/src/modules/space/space.repository.module.ts b/libs/common/src/modules/space/space.repository.module.ts index ea3c606..90916c2 100644 --- a/libs/common/src/modules/space/space.repository.module.ts +++ b/libs/common/src/modules/space/space.repository.module.ts @@ -1,13 +1,11 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { SpaceEntity, SpaceTypeEntity, SubspaceEntity } from './entities'; +import { SpaceEntity, SubspaceEntity } from './entities'; @Module({ providers: [], exports: [], controllers: [], - imports: [ - TypeOrmModule.forFeature([SpaceEntity, SpaceTypeEntity, SubspaceEntity]), - ], + imports: [TypeOrmModule.forFeature([SpaceEntity, SubspaceEntity])], }) export class SpaceRepositoryModule {} diff --git a/libs/common/src/seed/seeder.module.ts b/libs/common/src/seed/seeder.module.ts index 0c89c5c..eeb245b 100644 --- a/libs/common/src/seed/seeder.module.ts +++ b/libs/common/src/seed/seeder.module.ts @@ -7,8 +7,6 @@ import { ConfigModule } from '@nestjs/config'; import { RoleTypeRepositoryModule } from '../modules/role-type/role.type.repository.module'; import { RoleTypeRepository } from '../modules/role-type/repositories'; import { RoleTypeSeeder } from './services/role.type.seeder'; -import { SpaceTypeRepository } from '../modules/space/repositories'; -import { SpaceTypeSeeder } from './services/space.type.seeder'; import { SpaceRepositoryModule } from '../modules/space/space.repository.module'; import { SuperAdminSeeder } from './services/supper.admin.seeder'; import { UserRepository } from '../modules/user/repositories'; @@ -25,11 +23,9 @@ import { SceneIconRepository } from '../modules/scene/repositories'; providers: [ PermissionTypeSeeder, RoleTypeSeeder, - SpaceTypeSeeder, SeederService, PermissionTypeRepository, RoleTypeRepository, - SpaceTypeRepository, SuperAdminSeeder, UserRepository, UserRoleRepository, diff --git a/libs/common/src/seed/services/seeder.service.ts b/libs/common/src/seed/services/seeder.service.ts index e627e44..09fb91d 100644 --- a/libs/common/src/seed/services/seeder.service.ts +++ b/libs/common/src/seed/services/seeder.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import { PermissionTypeSeeder } from './permission.type.seeder'; import { RoleTypeSeeder } from './role.type.seeder'; -import { SpaceTypeSeeder } from './space.type.seeder'; import { SuperAdminSeeder } from './supper.admin.seeder'; import { RegionSeeder } from './regions.seeder'; import { TimeZoneSeeder } from './timezone.seeder'; @@ -11,7 +10,6 @@ export class SeederService { constructor( private readonly permissionTypeSeeder: PermissionTypeSeeder, private readonly roleTypeSeeder: RoleTypeSeeder, - private readonly spaceTypeSeeder: SpaceTypeSeeder, private readonly regionSeeder: RegionSeeder, private readonly timeZoneSeeder: TimeZoneSeeder, private readonly superAdminSeeder: SuperAdminSeeder, @@ -21,7 +19,6 @@ export class SeederService { async seed() { await this.permissionTypeSeeder.addPermissionTypeDataIfNotFound(); await this.roleTypeSeeder.addRoleTypeDataIfNotFound(); - await this.spaceTypeSeeder.addSpaceTypeDataIfNotFound(); await this.regionSeeder.addRegionDataIfNotFound(); await this.timeZoneSeeder.addTimeZoneDataIfNotFound(); await this.superAdminSeeder.createSuperAdminIfNotFound(); diff --git a/libs/common/src/seed/services/space.type.seeder.ts b/libs/common/src/seed/services/space.type.seeder.ts deleted file mode 100644 index 56c2ee8..0000000 --- a/libs/common/src/seed/services/space.type.seeder.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { SpaceType } from '../../constants/space-type.enum'; -import { SpaceTypeRepository } from '../../modules/space/repositories'; - -@Injectable() -export class SpaceTypeSeeder { - constructor(private readonly spaceTypeRepository: SpaceTypeRepository) {} - - async addSpaceTypeDataIfNotFound(): Promise { - try { - const existingSpaceTypes = await this.spaceTypeRepository.find(); - - const spaceTypeNames = existingSpaceTypes.map((pt) => pt.type); - - const missingSpaceTypes = []; - if (!spaceTypeNames.includes(SpaceType.COMMUNITY)) { - missingSpaceTypes.push(SpaceType.COMMUNITY); - } - if (!spaceTypeNames.includes(SpaceType.BUILDING)) { - missingSpaceTypes.push(SpaceType.BUILDING); - } - if (!spaceTypeNames.includes(SpaceType.FLOOR)) { - missingSpaceTypes.push(SpaceType.FLOOR); - } - if (!spaceTypeNames.includes(SpaceType.UNIT)) { - missingSpaceTypes.push(SpaceType.UNIT); - } - if (!spaceTypeNames.includes(SpaceType.ROOM)) { - missingSpaceTypes.push(SpaceType.ROOM); - } - if (missingSpaceTypes.length > 0) { - await this.addSpaceTypeData(missingSpaceTypes); - } - } catch (err) { - console.error('Error while checking space type data:', err); - throw err; - } - } - - private async addSpaceTypeData(spaceTypes: string[]): Promise { - try { - const spaceTypeEntities = spaceTypes.map((type) => ({ - type, - })); - - await this.spaceTypeRepository.save(spaceTypeEntities); - } catch (err) { - console.error('Error while adding space type data:', err); - throw err; - } - } -} From eb916b79b44ce20ef93cfe9001d3de43f5d3b4f8 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 16 Nov 2024 23:05:06 -0600 Subject: [PATCH 057/109] finished 4scene configration endpoints --- libs/common/src/common.module.ts | 10 +- libs/common/src/constants/four-scene.enum.ts | 6 + libs/common/src/database/database.module.ts | 2 + .../modules/device/entities/device.entity.ts | 14 +- .../src/modules/scene-device/dtos/index.ts | 1 + .../scene-device/dtos/scene-device.dto.ts | 20 +++ .../modules/scene-device/entities/index.ts | 1 + .../entities/scene-device.entity.ts | 50 ++++++ .../scene-device/repositories/index.ts | 1 + .../repositories/scene-device.repository.ts | 10 ++ .../scene-device.repository.module.ts | 11 ++ .../modules/scene/entities/scene.entity.ts | 4 + src/automation/automation.module.ts | 10 ++ src/automation/services/automation.service.ts | 21 +++ src/device/controllers/device.controller.ts | 38 ++++- src/device/device.module.ts | 14 ++ src/device/dtos/add.device.dto.ts | 27 +++- src/device/dtos/control.device.dto.ts | 9 ++ src/device/services/device.service.ts | 149 +++++++++++++++++- src/door-lock/door.lock.module.ts | 12 ++ src/group/services/group.service.ts | 1 - src/scene/scene.module.ts | 2 + src/scene/services/scene.service.ts | 17 +- src/space/space.module.ts | 2 + .../visitor-password.module.ts | 12 ++ 25 files changed, 429 insertions(+), 15 deletions(-) create mode 100644 libs/common/src/constants/four-scene.enum.ts create mode 100644 libs/common/src/modules/scene-device/dtos/index.ts create mode 100644 libs/common/src/modules/scene-device/dtos/scene-device.dto.ts create mode 100644 libs/common/src/modules/scene-device/entities/index.ts create mode 100644 libs/common/src/modules/scene-device/entities/scene-device.entity.ts create mode 100644 libs/common/src/modules/scene-device/repositories/index.ts create mode 100644 libs/common/src/modules/scene-device/repositories/scene-device.repository.ts create mode 100644 libs/common/src/modules/scene-device/scene-device.repository.module.ts diff --git a/libs/common/src/common.module.ts b/libs/common/src/common.module.ts index cbe733d..de0780a 100644 --- a/libs/common/src/common.module.ts +++ b/libs/common/src/common.module.ts @@ -8,8 +8,15 @@ import config from './config'; import { EmailService } from './util/email.service'; import { ErrorMessageService } from 'src/error-message/error-message.service'; import { TuyaService } from './integrations/tuya/services/tuya.service'; +import { SceneDeviceRepository } from './modules/scene-device/repositories'; @Module({ - providers: [CommonService, EmailService, ErrorMessageService, TuyaService], + providers: [ + CommonService, + EmailService, + ErrorMessageService, + TuyaService, + SceneDeviceRepository, + ], exports: [ CommonService, TuyaService, @@ -17,6 +24,7 @@ import { TuyaService } from './integrations/tuya/services/tuya.service'; AuthModule, EmailService, ErrorMessageService, + SceneDeviceRepository, ], imports: [ ConfigModule.forRoot({ diff --git a/libs/common/src/constants/four-scene.enum.ts b/libs/common/src/constants/four-scene.enum.ts new file mode 100644 index 0000000..7f43d70 --- /dev/null +++ b/libs/common/src/constants/four-scene.enum.ts @@ -0,0 +1,6 @@ +export enum FourSceneSwitchesEnum { + Scene_1 = 'scene_1', + Scene_2 = 'scene_2', + Scene_3 = 'scene_3', + Scene_4 = 'scene_4', +} diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 44e69c9..868ef5a 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -22,6 +22,7 @@ import { VisitorPasswordEntity } from '../modules/visitor-password/entities'; import { CommunityEntity } from '../modules/community/entities'; import { DeviceStatusLogEntity } from '../modules/device-status-log/entities'; import { SceneEntity, SceneIconEntity } from '../modules/scene/entities'; +import { SceneDeviceEntity } from '../modules/scene-device/entities'; @Module({ imports: [ @@ -60,6 +61,7 @@ import { SceneEntity, SceneIconEntity } from '../modules/scene/entities'; DeviceStatusLogEntity, SceneEntity, SceneIconEntity, + SceneDeviceEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index 727e290..9a75950 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -1,4 +1,12 @@ -import { Column, Entity, ManyToOne, OneToMany, Unique, Index, JoinColumn } from 'typeorm'; +import { + Column, + Entity, + ManyToOne, + OneToMany, + Unique, + Index, + JoinColumn, +} from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceDto, DeviceUserPermissionDto } from '../dtos/device.dto'; import { SpaceEntity, SubspaceEntity } from '../../space/entities'; @@ -6,6 +14,7 @@ import { ProductEntity } from '../../product/entities'; import { UserEntity } from '../../user/entities'; import { DeviceNotificationDto } from '../dtos'; import { PermissionTypeEntity } from '../../permission/entities'; +import { SceneDeviceEntity } from '../../scene-device/entities'; @Entity({ name: 'device' }) @Unique(['deviceTuyaUuid']) @@ -62,6 +71,9 @@ export class DeviceEntity extends AbstractEntity { @Column({ nullable: false }) uuid: string; + @OneToMany(() => SceneDeviceEntity, (sceneDevice) => sceneDevice.device, {}) + sceneDevices: SceneDeviceEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/scene-device/dtos/index.ts b/libs/common/src/modules/scene-device/dtos/index.ts new file mode 100644 index 0000000..96f03a1 --- /dev/null +++ b/libs/common/src/modules/scene-device/dtos/index.ts @@ -0,0 +1 @@ +export * from './scene-device.dto'; diff --git a/libs/common/src/modules/scene-device/dtos/scene-device.dto.ts b/libs/common/src/modules/scene-device/dtos/scene-device.dto.ts new file mode 100644 index 0000000..fd7e26a --- /dev/null +++ b/libs/common/src/modules/scene-device/dtos/scene-device.dto.ts @@ -0,0 +1,20 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +export class SceneDeviceDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public deviceUuid: string; + @IsString() + @IsNotEmpty() + public sceneUuid: string; + @IsString() + @IsNotEmpty() + public switchName: string; + @IsString() + @IsNotEmpty() + public automationTuyaUuid: string; +} diff --git a/libs/common/src/modules/scene-device/entities/index.ts b/libs/common/src/modules/scene-device/entities/index.ts new file mode 100644 index 0000000..337a094 --- /dev/null +++ b/libs/common/src/modules/scene-device/entities/index.ts @@ -0,0 +1 @@ +export * from './scene-device.entity'; diff --git a/libs/common/src/modules/scene-device/entities/scene-device.entity.ts b/libs/common/src/modules/scene-device/entities/scene-device.entity.ts new file mode 100644 index 0000000..537a627 --- /dev/null +++ b/libs/common/src/modules/scene-device/entities/scene-device.entity.ts @@ -0,0 +1,50 @@ +import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import { SceneDeviceDto } from '../dtos'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { FourSceneSwitchesEnum } from '@app/common/constants/four-scene.enum'; +import { DeviceEntity } from '../../device/entities'; +import { SceneEntity } from '../../scene/entities'; + +@Entity({ name: 'scene-device' }) +export class SceneDeviceEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', + nullable: false, + }) + public uuid: string; + + @ManyToOne(() => DeviceEntity, (device) => device.sceneDevices, { + nullable: false, + }) + @JoinColumn({ name: 'device_uuid' }) + device: DeviceEntity; + + @Column({ + nullable: false, + }) + sceneUuid: string; + + @Column({ + nullable: false, + type: 'enum', + enum: FourSceneSwitchesEnum, + }) + switchName: FourSceneSwitchesEnum; + + @Column({ + nullable: false, + }) + automationTuyaUuid: string; + + @ManyToOne(() => SceneEntity, (scene) => scene.sceneDevices, { + nullable: false, + }) + @JoinColumn({ name: 'scene_uuid' }) + scene: SceneEntity; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/scene-device/repositories/index.ts b/libs/common/src/modules/scene-device/repositories/index.ts new file mode 100644 index 0000000..f10ccb5 --- /dev/null +++ b/libs/common/src/modules/scene-device/repositories/index.ts @@ -0,0 +1 @@ +export * from './scene-device.repository'; diff --git a/libs/common/src/modules/scene-device/repositories/scene-device.repository.ts b/libs/common/src/modules/scene-device/repositories/scene-device.repository.ts new file mode 100644 index 0000000..1d7915c --- /dev/null +++ b/libs/common/src/modules/scene-device/repositories/scene-device.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { SceneDeviceEntity } from '../entities'; + +@Injectable() +export class SceneDeviceRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SceneDeviceEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/scene-device/scene-device.repository.module.ts b/libs/common/src/modules/scene-device/scene-device.repository.module.ts new file mode 100644 index 0000000..96fa8a7 --- /dev/null +++ b/libs/common/src/modules/scene-device/scene-device.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { SceneDeviceEntity } from './entities'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [TypeOrmModule.forFeature([SceneDeviceEntity])], +}) +export class SceneDeviceRepositoryModule {} diff --git a/libs/common/src/modules/scene/entities/scene.entity.ts b/libs/common/src/modules/scene/entities/scene.entity.ts index 1e04fd7..58a880a 100644 --- a/libs/common/src/modules/scene/entities/scene.entity.ts +++ b/libs/common/src/modules/scene/entities/scene.entity.ts @@ -2,6 +2,7 @@ import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'; import { SceneDto, SceneIconDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SceneIconType } from '@app/common/constants/secne-icon-type.enum'; +import { SceneDeviceEntity } from '../../scene-device/entities'; // Define SceneIconEntity before SceneEntity @Entity({ name: 'scene-icon' }) @@ -59,6 +60,9 @@ export class SceneEntity extends AbstractEntity { }) sceneIcon: SceneIconEntity; + @OneToMany(() => SceneDeviceEntity, (sceneDevice) => sceneDevice.scene) + sceneDevices: SceneDeviceEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/src/automation/automation.module.ts b/src/automation/automation.module.ts index 3a3d7db..0a05f0e 100644 --- a/src/automation/automation.module.ts +++ b/src/automation/automation.module.ts @@ -9,6 +9,12 @@ import { DeviceRepository } from '@app/common/modules/device/repositories'; import { ProductRepository } from '@app/common/modules/product/repositories'; import { DeviceStatusFirebaseModule } from '@app/common/firebase/devices-status/devices-status.module'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; +import { SceneService } from 'src/scene/services'; +import { + SceneIconRepository, + SceneRepository, +} from '@app/common/modules/scene/repositories'; +import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule], @@ -20,6 +26,10 @@ import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service DeviceService, DeviceRepository, ProductRepository, + SceneService, + SceneIconRepository, + SceneRepository, + SceneDeviceRepository, ], exports: [AutomationService], }) diff --git a/src/automation/services/automation.service.ts b/src/automation/services/automation.service.ts index 468a4ae..27d1b6a 100644 --- a/src/automation/services/automation.service.ts +++ b/src/automation/services/automation.service.ts @@ -30,6 +30,7 @@ import { } from '@app/common/constants/automation.enum'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { ConvertedAction } from '@app/common/integrations/tuya/interfaces'; +import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; @Injectable() export class AutomationService { @@ -39,6 +40,7 @@ export class AutomationService { private readonly spaceRepository: SpaceRepository, private readonly deviceService: DeviceService, private readonly tuyaService: TuyaService, + private readonly sceneDeviceRepository: SceneDeviceRepository, ) { const accessKey = this.configService.get('auth-config.ACCESS_KEY'); const secretKey = this.configService.get('auth-config.SECRET_KEY'); @@ -304,6 +306,16 @@ export class AutomationService { HttpStatus.BAD_REQUEST, ); } + const existingSceneDevice = await this.sceneDeviceRepository.findOne({ + where: { automationTuyaUuid: automationUuid }, + }); + + if (existingSceneDevice) { + await this.sceneDeviceRepository.delete({ + automationTuyaUuid: automationUuid, + }); + } + const response = this.tuyaService.deleteAutomation( automation.spaceId, automationUuid, @@ -323,6 +335,15 @@ export class AutomationService { async delete(tuyaSpaceId: string, automationUuid: string) { try { + const existingSceneDevice = await this.sceneDeviceRepository.findOne({ + where: { automationTuyaUuid: automationUuid }, + }); + + if (existingSceneDevice) { + await this.sceneDeviceRepository.delete({ + automationTuyaUuid: automationUuid, + }); + } const response = await this.tuyaService.deleteAutomation( tuyaSpaceId, automationUuid, diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 5a2fba5..15bf622 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -12,13 +12,18 @@ import { Put, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; -import { AddDeviceDto, UpdateDeviceInSpaceDto } from '../dtos/add.device.dto'; +import { + AddDeviceDto, + AddSceneToFourSceneDeviceDto, + UpdateDeviceInSpaceDto, +} from '../dtos/add.device.dto'; import { GetDeviceLogsDto } from '../dtos/get.device.dto'; import { ControlDeviceDto, BatchControlDevicesDto, BatchStatusDevicesDto, BatchFactoryResetDevicesDto, + GetSceneFourSceneDeviceDto, } from '../dtos/control.device.dto'; import { CheckRoomGuard } from 'src/guards/room.guard'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; @@ -184,4 +189,35 @@ export class DeviceController { powerClampUuid, ); } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('four-scene/:deviceUuid') + async addSceneToFourSceneDevice( + @Param('deviceUuid') deviceUuid: string, + @Body() addSceneToFourSceneDeviceDto: AddSceneToFourSceneDeviceDto, + ) { + const device = await this.deviceService.addSceneToFourSceneDevice( + deviceUuid, + addSceneToFourSceneDeviceDto, + ); + + return { + statusCode: HttpStatus.CREATED, + success: true, + message: `scene added successfully to device ${deviceUuid}`, + data: device, + }; + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('four-scene/:deviceUuid') + async getSceneFourSceneDevice( + @Param('deviceUuid') deviceUuid: string, + @Query() getSceneFourSceneDeviceDto: GetSceneFourSceneDeviceDto, + ) { + return await this.deviceService.getSceneFourSceneDevice( + deviceUuid, + getSceneFourSceneDeviceDto, + ); + } } diff --git a/src/device/device.module.ts b/src/device/device.module.ts index 5d17318..39b1be6 100644 --- a/src/device/device.module.ts +++ b/src/device/device.module.ts @@ -11,9 +11,18 @@ import { SpaceRepository } from '@app/common/modules/space/repositories'; import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { UserRepository } from '@app/common/modules/user/repositories'; import { DeviceStatusFirebaseModule } from '@app/common/firebase/devices-status/devices-status.module'; +import { SpaceModule } from 'src/space/space.module'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; +import { SceneService } from 'src/scene/services'; +import { + SceneIconRepository, + SceneRepository, +} from '@app/common/modules/scene/repositories'; +import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; @Module({ imports: [ ConfigModule, + SpaceModule, ProductRepositoryModule, DeviceRepositoryModule, DeviceStatusFirebaseModule, @@ -27,6 +36,11 @@ import { DeviceStatusFirebaseModule } from '@app/common/firebase/devices-status/ SpaceRepository, DeviceRepository, UserRepository, + TuyaService, + SceneService, + SceneIconRepository, + SceneRepository, + SceneDeviceRepository, ], exports: [DeviceService], }) diff --git a/src/device/dtos/add.device.dto.ts b/src/device/dtos/add.device.dto.ts index bff522f..b5884a8 100644 --- a/src/device/dtos/add.device.dto.ts +++ b/src/device/dtos/add.device.dto.ts @@ -1,5 +1,6 @@ +import { FourSceneSwitchesEnum } from '@app/common/constants/four-scene.enum'; import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; export class AddDeviceDto { @ApiProperty({ @@ -35,3 +36,27 @@ export class UpdateDeviceInSpaceDto { @IsNotEmpty() public spaceUuid: string; } +export class AddSceneToFourSceneDeviceDto { + @ApiProperty({ + description: 'switchName', + required: true, + }) + @IsEnum(FourSceneSwitchesEnum) + @IsNotEmpty() + switchName: FourSceneSwitchesEnum; + + @ApiProperty({ + description: 'sceneUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public sceneUuid: string; + @ApiProperty({ + description: 'spaceUuid', + required: true, + }) + @IsString() + @IsNotEmpty() + public spaceUuid: string; +} diff --git a/src/device/dtos/control.device.dto.ts b/src/device/dtos/control.device.dto.ts index d917a96..7a1e900 100644 --- a/src/device/dtos/control.device.dto.ts +++ b/src/device/dtos/control.device.dto.ts @@ -54,3 +54,12 @@ export class BatchFactoryResetDevicesDto { @IsNotEmpty() public devicesUuid: [string]; } +export class GetSceneFourSceneDeviceDto { + @ApiProperty({ + description: 'switchName', + required: true, + }) + @IsString() + @IsNotEmpty() + public switchName: string; +} diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 09b905c..a402973 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -5,10 +5,16 @@ import { HttpStatus, NotFoundException, BadRequestException, + forwardRef, + Inject, } from '@nestjs/common'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { ConfigService } from '@nestjs/config'; -import { AddDeviceDto, UpdateDeviceInSpaceDto } from '../dtos/add.device.dto'; +import { + AddDeviceDto, + AddSceneToFourSceneDeviceDto, + UpdateDeviceInSpaceDto, +} from '../dtos/add.device.dto'; import { DeviceInstructionResponse, GetDeviceDetailsFunctionsInterface, @@ -28,6 +34,7 @@ import { BatchFactoryResetDevicesDto, BatchStatusDevicesDto, ControlDeviceDto, + GetSceneFourSceneDeviceDto, } from '../dtos/control.device.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { DeviceRepository } from '@app/common/modules/device/repositories'; @@ -40,6 +47,11 @@ import { DeviceStatuses } from '@app/common/constants/device-status.enum'; import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; import { BatteryStatus } from '@app/common/constants/battery-status.enum'; import { SpaceEntity } from '@app/common/modules/space/entities'; +import { SceneService } from 'src/scene/services'; +import { AddAutomationDto } from 'src/automation/dtos'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; +import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; +import { FourSceneSwitchesEnum } from '@app/common/constants/four-scene.enum'; @Injectable() export class DeviceService { @@ -47,9 +59,13 @@ export class DeviceService { constructor( private readonly configService: ConfigService, private readonly deviceRepository: DeviceRepository, + private readonly sceneDeviceRepository: SceneDeviceRepository, private readonly productRepository: ProductRepository, private readonly deviceStatusFirebaseService: DeviceStatusFirebaseService, private readonly spaceRepository: SpaceRepository, + @Inject(forwardRef(() => SceneService)) + private readonly sceneService: SceneService, + private readonly tuyaService: TuyaService, ) { const accessKey = this.configService.get('auth-config.ACCESS_KEY'); const secretKey = this.configService.get('auth-config.SECRET_KEY'); @@ -630,7 +646,6 @@ export class DeviceService { status: deviceStatus.result[0].status, }; } catch (error) { - console.log(error); throw new HttpException( 'Error fetching device functions status', HttpStatus.INTERNAL_SERVER_ERROR, @@ -1136,4 +1151,134 @@ export class DeviceService { return descendants; } + async addSceneToFourSceneDevice( + deviceUuid: string, + addSceneToFourSceneDeviceDto: AddSceneToFourSceneDeviceDto, + ) { + try { + const { spaceUuid, sceneUuid, switchName } = addSceneToFourSceneDeviceDto; + + if (!spaceUuid || !sceneUuid || !switchName) { + throw new BadRequestException('Missing required fields in DTO'); + } + + const [sceneData, spaceData, deviceData] = await Promise.all([ + this.sceneService.findScene(sceneUuid), + this.sceneService.getSpaceByUuid(spaceUuid), + this.getDeviceByDeviceUuid(deviceUuid), + ]); + + // Generate a shorter automation name (e.g., "Auto_ABC123_169") + const shortUuid = deviceUuid.slice(0, 6); // First 6 characters of the UUID + const timestamp = Math.floor(Date.now() / 1000); // Current timestamp in seconds + const automationName = `Auto_${shortUuid}_${timestamp}`; + + const addAutomationData: AddAutomationDto = { + spaceUuid: spaceData.spaceTuyaUuid, + automationName, + decisionExpr: 'and', + effectiveTime: { + start: '00:00', + end: '23:59', + loops: '1111111', + }, + conditions: [ + { + code: 1, + entityId: deviceData.deviceTuyaUuid, + entityType: 'device_report', + expr: { + comparator: '==', + statusCode: switchName, + statusValue: 'scene', + }, + }, + ], + actions: [ + { + actionExecutor: 'rule_trigger', + entityId: sceneData.sceneTuyaUuid, + }, + ], + }; + + // Create automation + const automation = await this.tuyaService.createAutomation( + addAutomationData.spaceUuid, + addAutomationData.automationName, + addAutomationData.effectiveTime, + addAutomationData.decisionExpr, + addAutomationData.conditions, + addAutomationData.actions, + ); + + if (automation.success) { + const existingSceneDevice = await this.sceneDeviceRepository.findOne({ + where: { + device: { uuid: deviceUuid }, + scene: { uuid: sceneUuid }, + switchName: switchName, + }, + relations: ['scene', 'device'], + }); + + if (existingSceneDevice) { + await this.tuyaService.deleteAutomation( + spaceData.spaceTuyaUuid, + existingSceneDevice.automationTuyaUuid, + ); + + existingSceneDevice.automationTuyaUuid = automation.result.id; + existingSceneDevice.scene = sceneData; + existingSceneDevice.device = deviceData; + existingSceneDevice.switchName = switchName; + + return await this.sceneDeviceRepository.save(existingSceneDevice); + } else { + const sceneDevice = await this.sceneDeviceRepository.save({ + scene: sceneData, + device: deviceData, + automationTuyaUuid: automation.result.id, + switchName: switchName, + }); + return sceneDevice; + } + } + } catch (err) { + const errorMessage = err.message || 'Error creating automation'; + const errorStatus = err.status || HttpStatus.INTERNAL_SERVER_ERROR; + throw new HttpException(errorMessage, errorStatus); + } + } + async getSceneFourSceneDevice( + deviceUuid: string, + getSceneFourSceneDeviceDto: GetSceneFourSceneDeviceDto, + ) { + try { + const sceneDevice = await this.sceneDeviceRepository.findOne({ + where: { + device: { uuid: deviceUuid }, + switchName: + getSceneFourSceneDeviceDto.switchName as FourSceneSwitchesEnum, // Cast the string to the enum + }, + relations: ['device', 'scene'], + }); + if (sceneDevice.uuid) { + const SceneDetails = await this.sceneService.getSceneByUuid( + sceneDevice.scene.uuid, + ); + return { + switchName: sceneDevice.switchName, + createdAt: sceneDevice.createdAt, + updatedAt: sceneDevice.updatedAt, + deviceUuid: sceneDevice.device.uuid, + scene: { + ...SceneDetails.data, + }, + }; + } + } catch (error) { + throw new HttpException('Scene device not found', HttpStatus.NOT_FOUND); + } + } } diff --git a/src/door-lock/door.lock.module.ts b/src/door-lock/door.lock.module.ts index d5f070e..9bbc0d5 100644 --- a/src/door-lock/door.lock.module.ts +++ b/src/door-lock/door.lock.module.ts @@ -11,6 +11,13 @@ import { ProductRepository } from '@app/common/modules/product/repositories'; import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; +import { SceneService } from 'src/scene/services'; +import { + SceneIconRepository, + SceneRepository, +} from '@app/common/modules/scene/repositories'; +import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; @Module({ imports: [ConfigModule, DeviceRepositoryModule], controllers: [DoorLockController], @@ -24,6 +31,11 @@ import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log DeviceStatusFirebaseService, SpaceRepository, DeviceStatusLogRepository, + TuyaService, + SceneService, + SceneIconRepository, + SceneRepository, + SceneDeviceRepository, ], exports: [DoorLockService], }) diff --git a/src/group/services/group.service.ts b/src/group/services/group.service.ts index 9759232..049e82d 100644 --- a/src/group/services/group.service.ts +++ b/src/group/services/group.service.ts @@ -120,7 +120,6 @@ export class GroupService { throw new HttpException('No devices found', HttpStatus.NOT_FOUND); return devices.flat(); // Flatten the array since flatMap was used } catch (error) { - console.log(error); throw new HttpException( 'This space does not have any devices for the specified group name', HttpStatus.NOT_FOUND, diff --git a/src/scene/scene.module.ts b/src/scene/scene.module.ts index 680de5f..9da0156 100644 --- a/src/scene/scene.module.ts +++ b/src/scene/scene.module.ts @@ -13,6 +13,7 @@ import { SceneRepository, } from '@app/common/modules/scene/repositories'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; +import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule], @@ -26,6 +27,7 @@ import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service ProductRepository, SceneIconRepository, SceneRepository, + SceneDeviceRepository, ], exports: [SceneService], }) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 08d05be..9c5791b 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -3,6 +3,8 @@ import { HttpException, HttpStatus, BadRequestException, + forwardRef, + Inject, } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { @@ -14,7 +16,6 @@ import { UpdateSceneTapToRunDto, } from '../dtos'; import { convertKeysToSnakeCase } from '@app/common/helper/snakeCaseConverter'; -import { DeviceService } from 'src/device/services'; import { AddTapToRunSceneInterface, DeleteTapToRunSceneInterface, @@ -37,6 +38,7 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { HttpStatusCode } from 'axios'; import { ConvertedAction } from '@app/common/integrations/tuya/interfaces'; +import { DeviceService } from 'src/device/services'; @Injectable() export class SceneService { @@ -44,8 +46,9 @@ export class SceneService { private readonly spaceRepository: SpaceRepository, private readonly sceneIconRepository: SceneIconRepository, private readonly sceneRepository: SceneRepository, - private readonly deviceService: DeviceService, private readonly tuyaService: TuyaService, + @Inject(forwardRef(() => DeviceService)) + private readonly deviceService: DeviceService, ) {} async createScene( @@ -178,7 +181,10 @@ export class SceneService { }); if (!scenesData.length) { - return []; + throw new HttpException( + `No scenes found for space UUID ${spaceUuid} with showInHomePage ${showInHomePage} `, + HttpStatus.NOT_FOUND, + ); } const scenes = await Promise.all( @@ -438,7 +444,6 @@ export class SceneService { if (err instanceof BadRequestException) { throw err; } else { - console.log(err); throw new HttpException( `An error occurred while retrieving scene details for ${scene.uuid}`, HttpStatus.INTERNAL_SERVER_ERROR, @@ -454,9 +459,6 @@ export class SceneService { const space = await this.getSpaceByUuid(scene.spaceUuid); await this.delete(scene.sceneTuyaUuid, space.spaceTuyaUuid); - - await this.sceneRepository.remove(scene); - return new SuccessResponseDto({ message: `Scene with ID ${sceneUuid} deleted successfully`, }); @@ -564,7 +566,6 @@ export class SceneService { }; } catch (err) { if (err instanceof BadRequestException) { - console.log(err); throw err; } else { throw new HttpException( diff --git a/src/space/space.module.ts b/src/space/space.module.ts index cf6b766..c840bd9 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -37,6 +37,7 @@ import { import { DeviceService } from 'src/device/services'; import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service'; import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories'; +import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; @Module({ imports: [ConfigModule, SpaceRepositoryModule], @@ -69,6 +70,7 @@ import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log DeviceService, DeviceStatusFirebaseService, DeviceStatusLogRepository, + SceneDeviceRepository, ], exports: [SpaceService], }) diff --git a/src/vistor-password/visitor-password.module.ts b/src/vistor-password/visitor-password.module.ts index 990e1e7..e768ad5 100644 --- a/src/vistor-password/visitor-password.module.ts +++ b/src/vistor-password/visitor-password.module.ts @@ -13,6 +13,13 @@ import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status import { SpaceRepository } from '@app/common/modules/space/repositories'; import { VisitorPasswordRepository } from '@app/common/modules/visitor-password/repositories'; import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; +import { SceneService } from 'src/scene/services'; +import { + SceneIconRepository, + SceneRepository, +} from '@app/common/modules/scene/repositories'; +import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; @Module({ imports: [ConfigModule, DeviceRepositoryModule, DoorLockModule], controllers: [VisitorPasswordController], @@ -27,6 +34,11 @@ import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log DeviceRepository, VisitorPasswordRepository, DeviceStatusLogRepository, + TuyaService, + SceneService, + SceneIconRepository, + SceneRepository, + SceneDeviceRepository, ], exports: [VisitorPasswordService], }) From c7a698629231ebae69b75d5d9479ed3bb4b5a9aa Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 16 Nov 2024 23:18:20 -0600 Subject: [PATCH 058/109] finished get mac address api --- src/device/interfaces/get.device.interface.ts | 6 +++++ src/device/services/device.service.ts | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/device/interfaces/get.device.interface.ts b/src/device/interfaces/get.device.interface.ts index d003d2d..3215364 100644 --- a/src/device/interfaces/get.device.interface.ts +++ b/src/device/interfaces/get.device.interface.ts @@ -90,3 +90,9 @@ export interface GetPowerClampFunctionsStatusInterface { success: boolean; msg: string; } +export interface GetMacAddressInterface { + uuid: string; + mac: string; + sn: string; + id: string; +} diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index a402973..81ae501 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -20,6 +20,7 @@ import { GetDeviceDetailsFunctionsInterface, GetDeviceDetailsFunctionsStatusInterface, GetDeviceDetailsInterface, + GetMacAddressInterface, GetPowerClampFunctionsStatusInterface, controlDeviceInterface, getDeviceLogsInterface, @@ -535,6 +536,9 @@ export class DeviceService { const response = await this.getDeviceDetailsByDeviceIdTuya( deviceDetails.deviceTuyaUuid, ); + const macAddress = await this.getMacAddressByDeviceIdTuya( + deviceDetails.deviceTuyaUuid, + ); return { ...response, @@ -542,6 +546,7 @@ export class DeviceService { productUuid: deviceDetails.productDevice.uuid, productType: deviceDetails.productDevice.prodType, permissionType: userDevicePermission, + macAddress: macAddress.mac, }; } catch (error) { throw new HttpException( @@ -582,7 +587,24 @@ export class DeviceService { ); } } + async getMacAddressByDeviceIdTuya( + deviceId: string, + ): Promise { + try { + const path = `/v1.0/devices/factory-infos?device_ids=${deviceId}`; + const response = await this.tuya.request({ + method: 'GET', + path, + }); + return response.result[0]; + } catch (error) { + throw new HttpException( + 'Error fetching mac address device from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } async getDeviceInstructionByDeviceId( deviceUuid: string, ): Promise { From 6d73968631143956854c82ff44397767181e883c Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 16 Nov 2024 23:36:10 -0600 Subject: [PATCH 059/109] finished update name device api --- src/device/controllers/device.controller.ts | 21 +++++++++++ src/device/dtos/add.device.dto.ts | 9 +++++ src/device/services/device.service.ts | 42 +++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 15bf622..cf977d2 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -15,6 +15,7 @@ import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { AddDeviceDto, AddSceneToFourSceneDeviceDto, + UpdateDeviceDto, UpdateDeviceInSpaceDto, } from '../dtos/add.device.dto'; import { GetDeviceLogsDto } from '../dtos/get.device.dto'; @@ -95,6 +96,26 @@ export class DeviceController { userUuid, ); } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Put(':deviceUuid') + async updateDevice( + @Param('deviceUuid') deviceUuid: string, + @Body() updateDeviceDto: UpdateDeviceDto, + ) { + const device = await this.deviceService.updateDevice( + deviceUuid, + updateDeviceDto, + ); + + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'device updated successfully', + data: device, + }; + } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':deviceUuid/functions') diff --git a/src/device/dtos/add.device.dto.ts b/src/device/dtos/add.device.dto.ts index b5884a8..bdc76f6 100644 --- a/src/device/dtos/add.device.dto.ts +++ b/src/device/dtos/add.device.dto.ts @@ -60,3 +60,12 @@ export class AddSceneToFourSceneDeviceDto { @IsNotEmpty() public spaceUuid: string; } +export class UpdateDeviceDto { + @ApiProperty({ + description: 'deviceName', + required: true, + }) + @IsString() + @IsNotEmpty() + public deviceName: string; +} diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 81ae501..0216cc7 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -13,6 +13,7 @@ import { ConfigService } from '@nestjs/config'; import { AddDeviceDto, AddSceneToFourSceneDeviceDto, + UpdateDeviceDto, UpdateDeviceInSpaceDto, } from '../dtos/add.device.dto'; import { @@ -291,6 +292,26 @@ export class DeviceService { ); } } + async updateDeviceNameTuya( + deviceId: string, + deviceName: string, + ): Promise { + try { + const path = `/v2.0/cloud/thing/${deviceId}/attribute`; + const response = await this.tuya.request({ + method: 'POST', + path, + body: { type: 1, data: deviceName }, + }); + + return response as controlDeviceInterface; + } catch (error) { + throw new HttpException( + 'Error updating device name from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } async controlDevice(controlDeviceDto: ControlDeviceDto, deviceUuid: string) { try { const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid, false); @@ -555,6 +576,27 @@ export class DeviceService { ); } } + async updateDevice(deviceUuid: string, updateDeviceDto: UpdateDeviceDto) { + try { + const device = await this.getDeviceByDeviceUuid(deviceUuid); + if (device.deviceTuyaUuid) { + await this.updateDeviceNameTuya( + device.deviceTuyaUuid, + updateDeviceDto.deviceName, + ); + } + + return { + uuid: device.uuid, + deviceName: updateDeviceDto.deviceName, + }; + } catch (error) { + throw new HttpException( + 'Error updating device', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } async getDeviceDetailsByDeviceIdTuya( deviceId: string, ): Promise { From 145e335bebee59eb93675c2dfccc7d07f45ab4c3 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 17 Nov 2024 03:04:33 -0600 Subject: [PATCH 060/109] finshed add subspace to devices details --- src/device/services/device.service.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 0216cc7..15276d9 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -82,11 +82,15 @@ export class DeviceService { deviceUuid: string, withProductDevice: boolean = true, ) { - return await this.deviceRepository.findOne({ - where: { - uuid: deviceUuid, - }, - ...(withProductDevice && { relations: ['productDevice'] }), + const relations = ['subspace']; + + if (withProductDevice) { + relations.push('productDevice'); + } + + return this.deviceRepository.findOne({ + where: { uuid: deviceUuid }, + relations, }); } @@ -568,6 +572,7 @@ export class DeviceService { productType: deviceDetails.productDevice.prodType, permissionType: userDevicePermission, macAddress: macAddress.mac, + subspace: deviceDetails.subspace ? deviceDetails.subspace : {}, }; } catch (error) { throw new HttpException( From ccf207d6529eeac1ac30c9676bcf35f0b7c11132 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 18 Nov 2024 00:23:54 -0600 Subject: [PATCH 061/109] finished get all switches --- src/device/controllers/device.controller.ts | 8 +++- src/device/services/device.service.ts | 47 +++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index cf977d2..564b9fc 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -231,7 +231,7 @@ export class DeviceController { } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get('four-scene/:deviceUuid') + @Get('four-scene/switch/:deviceUuid') async getSceneFourSceneDevice( @Param('deviceUuid') deviceUuid: string, @Query() getSceneFourSceneDeviceDto: GetSceneFourSceneDeviceDto, @@ -241,4 +241,10 @@ export class DeviceController { getSceneFourSceneDeviceDto, ); } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('four-scene/:deviceUuid') + async getScenesFourSceneDevice(@Param('deviceUuid') deviceUuid: string) { + return await this.deviceService.getScenesFourSceneDevice(deviceUuid); + } } diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 15276d9..950da44 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -1350,4 +1350,51 @@ export class DeviceService { throw new HttpException('Scene device not found', HttpStatus.NOT_FOUND); } } + async getScenesFourSceneDevice(deviceUuid: string): Promise { + try { + const sceneDevices = await this.sceneDeviceRepository.find({ + where: { device: { uuid: deviceUuid } }, + relations: ['device', 'scene'], + }); + + if (!sceneDevices.length) { + throw new HttpException( + 'No scenes found for the device', + HttpStatus.NOT_FOUND, + ); + } + + const results = await Promise.all( + sceneDevices.map(async (sceneDevice) => { + if (!sceneDevice.scene?.uuid) return null; + + try { + const sceneDetails = await this.sceneService.getSceneByUuid( + sceneDevice.scene.uuid, + ); + + return { + switchName: sceneDevice.switchName, + createdAt: sceneDevice.createdAt, + updatedAt: sceneDevice.updatedAt, + deviceUuid: sceneDevice.device.uuid, + scene: sceneDetails.data, + }; + } catch (error) { + throw new HttpException( + 'Failed to fetch scene details', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + }), + ); + + return results.filter(Boolean); + } catch (error) { + throw new HttpException( + error.message || 'Failed to fetch scenes for device', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } From 7ea69a10b971fad1b5b734232a4ade148b077493 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 19 Nov 2024 20:01:09 -0600 Subject: [PATCH 062/109] finished fix duplication --- .../src/modules/scene-device/entities/scene-device.entity.ts | 3 ++- src/device/services/device.service.ts | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/common/src/modules/scene-device/entities/scene-device.entity.ts b/libs/common/src/modules/scene-device/entities/scene-device.entity.ts index 537a627..27882d6 100644 --- a/libs/common/src/modules/scene-device/entities/scene-device.entity.ts +++ b/libs/common/src/modules/scene-device/entities/scene-device.entity.ts @@ -1,4 +1,4 @@ -import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import { Column, Entity, JoinColumn, ManyToOne, Unique } from 'typeorm'; import { SceneDeviceDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { FourSceneSwitchesEnum } from '@app/common/constants/four-scene.enum'; @@ -6,6 +6,7 @@ import { DeviceEntity } from '../../device/entities'; import { SceneEntity } from '../../scene/entities'; @Entity({ name: 'scene-device' }) +@Unique(['device', 'switchName']) export class SceneDeviceEntity extends AbstractEntity { @Column({ type: 'uuid', diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 950da44..1e1d5f0 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -1285,7 +1285,6 @@ export class DeviceService { const existingSceneDevice = await this.sceneDeviceRepository.findOne({ where: { device: { uuid: deviceUuid }, - scene: { uuid: sceneUuid }, switchName: switchName, }, relations: ['scene', 'device'], From 9510905fb47bdd1d2ac80831bd39da50839486b3 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 19 Nov 2024 20:44:45 -0600 Subject: [PATCH 063/109] finished config with 6 scene device --- libs/common/src/constants/four-scene.enum.ts | 6 --- .../common/src/constants/product-type.enum.ts | 2 + .../src/constants/scene-switch-type.enum.ts | 8 ++++ .../entities/scene-device.entity.ts | 6 +-- src/device/controllers/device.controller.ts | 25 +++++------ src/device/dtos/add.device.dto.ts | 6 +-- src/device/services/device.service.ts | 16 ++++--- src/guards/scene.device.type.guard.ts | 42 +++++++++++++++++++ 8 files changed, 81 insertions(+), 30 deletions(-) delete mode 100644 libs/common/src/constants/four-scene.enum.ts create mode 100644 libs/common/src/constants/scene-switch-type.enum.ts create mode 100644 src/guards/scene.device.type.guard.ts diff --git a/libs/common/src/constants/four-scene.enum.ts b/libs/common/src/constants/four-scene.enum.ts deleted file mode 100644 index 7f43d70..0000000 --- a/libs/common/src/constants/four-scene.enum.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum FourSceneSwitchesEnum { - Scene_1 = 'scene_1', - Scene_2 = 'scene_2', - Scene_3 = 'scene_3', - Scene_4 = 'scene_4', -} diff --git a/libs/common/src/constants/product-type.enum.ts b/libs/common/src/constants/product-type.enum.ts index 2f518fb..d368865 100644 --- a/libs/common/src/constants/product-type.enum.ts +++ b/libs/common/src/constants/product-type.enum.ts @@ -16,4 +16,6 @@ export enum ProductType { GD = 'GD', CUR = 'CUR', PC = 'PC', + FOUR_S = '4S', + SIX_S = '6S', } diff --git a/libs/common/src/constants/scene-switch-type.enum.ts b/libs/common/src/constants/scene-switch-type.enum.ts new file mode 100644 index 0000000..62175d5 --- /dev/null +++ b/libs/common/src/constants/scene-switch-type.enum.ts @@ -0,0 +1,8 @@ +export enum SceneSwitchesTypeEnum { + SCENE_1 = 'scene_1', + SCENE_2 = 'scene_2', + SCENE_3 = 'scene_3', + SCENE_4 = 'scene_4', + SCENE_5 = 'scene_5', + SCENE_6 = 'scene_6', +} diff --git a/libs/common/src/modules/scene-device/entities/scene-device.entity.ts b/libs/common/src/modules/scene-device/entities/scene-device.entity.ts index 537a627..768bf09 100644 --- a/libs/common/src/modules/scene-device/entities/scene-device.entity.ts +++ b/libs/common/src/modules/scene-device/entities/scene-device.entity.ts @@ -1,7 +1,7 @@ import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; import { SceneDeviceDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { FourSceneSwitchesEnum } from '@app/common/constants/four-scene.enum'; +import { SceneSwitchesTypeEnum } from '@app/common/constants/scene-switch-type.enum'; import { DeviceEntity } from '../../device/entities'; import { SceneEntity } from '../../scene/entities'; @@ -28,9 +28,9 @@ export class SceneDeviceEntity extends AbstractEntity { @Column({ nullable: false, type: 'enum', - enum: FourSceneSwitchesEnum, + enum: SceneSwitchesTypeEnum, }) - switchName: FourSceneSwitchesEnum; + switchName: SceneSwitchesTypeEnum; @Column({ nullable: false, diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 564b9fc..81da9f3 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -31,6 +31,7 @@ import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { CheckDeviceGuard } from 'src/guards/device.guard'; import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { CheckFourAndSixSceneDeviceTypeGuard } from 'src/guards/scene.device.type.guard'; @ApiTags('Device Module') @Controller({ @@ -211,13 +212,13 @@ export class DeviceController { ); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Post('four-scene/:deviceUuid') - async addSceneToFourSceneDevice( + @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) + @Post('scene-switch/:deviceUuid') + async addSceneToSceneDevice( @Param('deviceUuid') deviceUuid: string, @Body() addSceneToFourSceneDeviceDto: AddSceneToFourSceneDeviceDto, ) { - const device = await this.deviceService.addSceneToFourSceneDevice( + const device = await this.deviceService.addSceneToSceneDevice( deviceUuid, addSceneToFourSceneDeviceDto, ); @@ -230,21 +231,21 @@ export class DeviceController { }; } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get('four-scene/switch/:deviceUuid') - async getSceneFourSceneDevice( + @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) + @Get('scene-switch/switch/:deviceUuid') + async getSceneBySceneDevice( @Param('deviceUuid') deviceUuid: string, @Query() getSceneFourSceneDeviceDto: GetSceneFourSceneDeviceDto, ) { - return await this.deviceService.getSceneFourSceneDevice( + return await this.deviceService.getSceneBySceneDevice( deviceUuid, getSceneFourSceneDeviceDto, ); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Get('four-scene/:deviceUuid') - async getScenesFourSceneDevice(@Param('deviceUuid') deviceUuid: string) { - return await this.deviceService.getScenesFourSceneDevice(deviceUuid); + @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) + @Get('scene-switch/:deviceUuid') + async getScenesBySceneDevice(@Param('deviceUuid') deviceUuid: string) { + return await this.deviceService.getScenesBySceneDevice(deviceUuid); } } diff --git a/src/device/dtos/add.device.dto.ts b/src/device/dtos/add.device.dto.ts index bdc76f6..69f42c4 100644 --- a/src/device/dtos/add.device.dto.ts +++ b/src/device/dtos/add.device.dto.ts @@ -1,4 +1,4 @@ -import { FourSceneSwitchesEnum } from '@app/common/constants/four-scene.enum'; +import { SceneSwitchesTypeEnum } from '@app/common/constants/scene-switch-type.enum'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; @@ -41,9 +41,9 @@ export class AddSceneToFourSceneDeviceDto { description: 'switchName', required: true, }) - @IsEnum(FourSceneSwitchesEnum) + @IsEnum(SceneSwitchesTypeEnum) @IsNotEmpty() - switchName: FourSceneSwitchesEnum; + switchName: SceneSwitchesTypeEnum; @ApiProperty({ description: 'sceneUuid', diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 950da44..2ff0490 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -53,7 +53,7 @@ import { SceneService } from 'src/scene/services'; import { AddAutomationDto } from 'src/automation/dtos'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; -import { FourSceneSwitchesEnum } from '@app/common/constants/four-scene.enum'; +import { SceneSwitchesTypeEnum } from '@app/common/constants/scene-switch-type.enum'; @Injectable() export class DeviceService { @@ -1220,7 +1220,7 @@ export class DeviceService { return descendants; } - async addSceneToFourSceneDevice( + async addSceneToSceneDevice( deviceUuid: string, addSceneToFourSceneDeviceDto: AddSceneToFourSceneDeviceDto, ) { @@ -1319,7 +1319,8 @@ export class DeviceService { throw new HttpException(errorMessage, errorStatus); } } - async getSceneFourSceneDevice( + + async getSceneBySceneDevice( deviceUuid: string, getSceneFourSceneDeviceDto: GetSceneFourSceneDeviceDto, ) { @@ -1328,7 +1329,7 @@ export class DeviceService { where: { device: { uuid: deviceUuid }, switchName: - getSceneFourSceneDeviceDto.switchName as FourSceneSwitchesEnum, // Cast the string to the enum + getSceneFourSceneDeviceDto.switchName as SceneSwitchesTypeEnum, // Cast the string to the enum }, relations: ['device', 'scene'], }); @@ -1347,10 +1348,13 @@ export class DeviceService { }; } } catch (error) { - throw new HttpException('Scene device not found', HttpStatus.NOT_FOUND); + throw new HttpException( + error.message || 'Scene device not found', + error.status || HttpStatus.NOT_FOUND, + ); } } - async getScenesFourSceneDevice(deviceUuid: string): Promise { + async getScenesBySceneDevice(deviceUuid: string): Promise { try { const sceneDevices = await this.sceneDeviceRepository.find({ where: { device: { uuid: deviceUuid } }, diff --git a/src/guards/scene.device.type.guard.ts b/src/guards/scene.device.type.guard.ts new file mode 100644 index 0000000..777e60e --- /dev/null +++ b/src/guards/scene.device.type.guard.ts @@ -0,0 +1,42 @@ +import { ProductType } from '@app/common/constants/product-type.enum'; +import { + Injectable, + CanActivate, + ExecutionContext, + BadRequestException, + HttpException, +} from '@nestjs/common'; +import { DeviceService } from 'src/device/services'; + +@Injectable() +export class CheckFourAndSixSceneDeviceTypeGuard implements CanActivate { + constructor(private readonly deviceService: DeviceService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const deviceUuid = request.params.deviceUuid; + + if (!deviceUuid) { + throw new BadRequestException('Device UUID is required'); + } + + try { + const deviceDetails = + await this.deviceService.getDeviceByDeviceUuid(deviceUuid); + + if ( + deviceDetails.productDevice.prodType !== ProductType.FOUR_S && + deviceDetails.productDevice.prodType !== ProductType.SIX_S + ) { + throw new BadRequestException('The device type is not supported'); + } + + return true; + } catch (error) { + throw new HttpException( + error.message || 'An error occurred', + error.status || 500, + ); + } + } +} From 4a51c9170b5f120d4f3b5797b30145ac827e9152 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 19 Nov 2024 20:53:39 -0600 Subject: [PATCH 064/109] add constants values --- libs/common/src/constants/automation.enum.ts | 10 +++++++++ src/device/services/device.service.ts | 22 ++++++++++---------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/libs/common/src/constants/automation.enum.ts b/libs/common/src/constants/automation.enum.ts index 7e431eb..e9bb6a8 100644 --- a/libs/common/src/constants/automation.enum.ts +++ b/libs/common/src/constants/automation.enum.ts @@ -7,3 +7,13 @@ export enum ActionExecutorEnum { export enum EntityTypeEnum { DEVICE_REPORT = 'device_report', } +export const AUTOMATION_CONFIG = { + DEFAULT_START_TIME: '00:00', + DEFAULT_END_TIME: '23:59', + DEFAULT_LOOPS: '1111111', + DECISION_EXPR: 'and', + CONDITION_TYPE: 'device_report', + ACTION_EXECUTOR: 'rule_trigger', + COMPARATOR: '==', + SCENE_STATUS_VALUE: 'scene', +}; diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 1e44c6d..4d45264 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -54,6 +54,7 @@ import { AddAutomationDto } from 'src/automation/dtos'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; import { SceneSwitchesTypeEnum } from '@app/common/constants/scene-switch-type.enum'; +import { AUTOMATION_CONFIG } from '@app/common/constants/automation.enum'; @Injectable() export class DeviceService { @@ -1220,6 +1221,7 @@ export class DeviceService { return descendants; } + async addSceneToSceneDevice( deviceUuid: string, addSceneToFourSceneDeviceDto: AddSceneToFourSceneDeviceDto, @@ -1237,7 +1239,6 @@ export class DeviceService { this.getDeviceByDeviceUuid(deviceUuid), ]); - // Generate a shorter automation name (e.g., "Auto_ABC123_169") const shortUuid = deviceUuid.slice(0, 6); // First 6 characters of the UUID const timestamp = Math.floor(Date.now() / 1000); // Current timestamp in seconds const automationName = `Auto_${shortUuid}_${timestamp}`; @@ -1245,33 +1246,32 @@ export class DeviceService { const addAutomationData: AddAutomationDto = { spaceUuid: spaceData.spaceTuyaUuid, automationName, - decisionExpr: 'and', + decisionExpr: AUTOMATION_CONFIG.DECISION_EXPR, effectiveTime: { - start: '00:00', - end: '23:59', - loops: '1111111', + start: AUTOMATION_CONFIG.DEFAULT_START_TIME, + end: AUTOMATION_CONFIG.DEFAULT_END_TIME, + loops: AUTOMATION_CONFIG.DEFAULT_LOOPS, }, conditions: [ { code: 1, entityId: deviceData.deviceTuyaUuid, - entityType: 'device_report', + entityType: AUTOMATION_CONFIG.CONDITION_TYPE, expr: { - comparator: '==', + comparator: AUTOMATION_CONFIG.COMPARATOR, statusCode: switchName, - statusValue: 'scene', + statusValue: AUTOMATION_CONFIG.SCENE_STATUS_VALUE, }, }, ], actions: [ { - actionExecutor: 'rule_trigger', + actionExecutor: AUTOMATION_CONFIG.ACTION_EXECUTOR, entityId: sceneData.sceneTuyaUuid, }, ], }; - // Create automation const automation = await this.tuyaService.createAutomation( addAutomationData.spaceUuid, addAutomationData.automationName, @@ -1328,7 +1328,7 @@ export class DeviceService { where: { device: { uuid: deviceUuid }, switchName: - getSceneFourSceneDeviceDto.switchName as SceneSwitchesTypeEnum, // Cast the string to the enum + getSceneFourSceneDeviceDto.switchName as SceneSwitchesTypeEnum, }, relations: ['device', 'scene'], }); From 891129e7e3bdf86e15afc9fe8aa8be4ebcc273a5 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 20 Nov 2024 09:11:11 +0400 Subject: [PATCH 065/109] added product controller --- src/product/controllers/index.ts | 1 + src/product/controllers/product.controller.ts | 27 +++++++++++++++++++ src/product/index.ts | 1 + src/product/product.module.ts | 10 +++++++ src/product/services/index.ts | 1 + src/product/services/product.service.ts | 24 +++++++++++++++++ 6 files changed, 64 insertions(+) create mode 100644 src/product/controllers/index.ts create mode 100644 src/product/controllers/product.controller.ts create mode 100644 src/product/index.ts create mode 100644 src/product/product.module.ts create mode 100644 src/product/services/index.ts create mode 100644 src/product/services/product.service.ts diff --git a/src/product/controllers/index.ts b/src/product/controllers/index.ts new file mode 100644 index 0000000..eead553 --- /dev/null +++ b/src/product/controllers/index.ts @@ -0,0 +1 @@ +export * from './product.controller'; diff --git a/src/product/controllers/product.controller.ts b/src/product/controllers/product.controller.ts new file mode 100644 index 0000000..f5fdf53 --- /dev/null +++ b/src/product/controllers/product.controller.ts @@ -0,0 +1,27 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { ProductService } from '../services'; + +@ApiTags('Product Module') +@Controller({ + version: EnableDisableStatusEnum.ENABLED, + path: ControllerRoute.PRODUCT.ROUTE, +}) +export class ProductController { + constructor(private readonly productService: ProductService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get() + @ApiOperation({ + summary: ControllerRoute.PRODUCT.ACTIONS.LIST_PRODUCT_SUMMARY, + description: ControllerRoute.PRODUCT.ACTIONS.LIST_PRODUCT_DESCRIPTION, + }) + async getProducts(): Promise { + return await this.productService.list(); + } +} diff --git a/src/product/index.ts b/src/product/index.ts new file mode 100644 index 0000000..8b3162c --- /dev/null +++ b/src/product/index.ts @@ -0,0 +1 @@ +export * from './product.module'; diff --git a/src/product/product.module.ts b/src/product/product.module.ts new file mode 100644 index 0000000..e5ffd25 --- /dev/null +++ b/src/product/product.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ProductService } from './services'; +import { ProductRepository } from '@app/common/modules/product/repositories'; +import { ProductController } from './controllers'; + +@Module({ + controllers: [ProductController], + providers: [ProductService, ProductRepository], +}) +export class ProductModule {} diff --git a/src/product/services/index.ts b/src/product/services/index.ts new file mode 100644 index 0000000..59832ef --- /dev/null +++ b/src/product/services/index.ts @@ -0,0 +1 @@ +export * from './product.service'; diff --git a/src/product/services/product.service.ts b/src/product/services/product.service.ts new file mode 100644 index 0000000..cebf9ba --- /dev/null +++ b/src/product/services/product.service.ts @@ -0,0 +1,24 @@ +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { ProductRepository } from '@app/common/modules/product/repositories'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; + +@Injectable() +export class ProductService { + constructor(private readonly productRepository: ProductRepository) {} + + async list(): Promise { + const products = await this.productRepository.find(); + + if (!products) { + throw new HttpException( + `No products found in the system`, + HttpStatus.NOT_FOUND, + ); + } + return new SuccessResponseDto({ + data: products, + message: 'List of products retrieved successfully', + }); + } +} From 857b02b97b10bd6112c2a7e1250d3a1a8b484614 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 20 Nov 2024 09:11:22 +0400 Subject: [PATCH 066/109] added space link entity --- libs/common/src/constants/controller-route.ts | 9 +++++++ libs/common/src/database/database.module.ts | 3 ++- .../src/modules/space/entities/index.ts | 1 + .../space/entities/space-link.entity.ts | 26 +++++++++++++++++++ .../modules/space/entities/space.entity.ts | 17 ++++++++++++ .../space/repositories/space.repository.ts | 9 ++++++- src/app.module.ts | 2 ++ src/space/dtos/add.space.dto.ts | 18 +++++++++++++ src/space/services/space.service.ts | 20 +++++++++++--- src/space/space.module.ts | 2 ++ 10 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 libs/common/src/modules/space/entities/space-link.entity.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 7f84bcc..fdf0435 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -235,4 +235,13 @@ export class ControllerRoute { 'Removes the association of a device from a specific subspace, making it no longer managed within that subspace.'; }; }; + + static PRODUCT = class { + public static readonly ROUTE = 'products'; + static ACTIONS = class { + public static readonly LIST_PRODUCT_SUMMARY = 'Retrieve all products'; + public static readonly LIST_PRODUCT_DESCRIPTION = + 'Fetches a list of all products along with their associated device details'; + }; + }; } diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 1f30866..904cac7 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -8,7 +8,7 @@ import { UserOtpEntity } from '../modules/user/entities'; import { ProductEntity } from '../modules/product/entities'; import { DeviceEntity } from '../modules/device/entities'; import { PermissionTypeEntity } from '../modules/permission/entities'; -import { SpaceEntity, SubspaceEntity } from '../modules/space/entities'; +import { SpaceEntity, SpaceLinkEntity, SubspaceEntity } from '../modules/space/entities'; import { UserSpaceEntity } from '../modules/user/entities'; import { DeviceUserPermissionEntity } from '../modules/device/entities'; import { UserRoleEntity } from '../modules/user/entities'; @@ -45,6 +45,7 @@ import { SceneEntity, SceneIconEntity } from '../modules/scene/entities'; PermissionTypeEntity, CommunityEntity, SpaceEntity, + SpaceLinkEntity, SubspaceEntity, UserSpaceEntity, DeviceUserPermissionEntity, diff --git a/libs/common/src/modules/space/entities/index.ts b/libs/common/src/modules/space/entities/index.ts index e551c0a..f720cf4 100644 --- a/libs/common/src/modules/space/entities/index.ts +++ b/libs/common/src/modules/space/entities/index.ts @@ -1,2 +1,3 @@ export * from './space.entity'; export * from './subspace.entity'; +export * from './space-link.entity'; diff --git a/libs/common/src/modules/space/entities/space-link.entity.ts b/libs/common/src/modules/space/entities/space-link.entity.ts new file mode 100644 index 0000000..6e2524f --- /dev/null +++ b/libs/common/src/modules/space/entities/space-link.entity.ts @@ -0,0 +1,26 @@ +import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { SpaceEntity } from './space.entity'; + +@Entity({ name: 'space_link' }) +export class SpaceLinkEntity extends AbstractEntity { + @ManyToOne(() => SpaceEntity, { nullable: false, onDelete: 'CASCADE' }) + @JoinColumn({ name: 'start_space_id' }) + public startSpace: SpaceEntity; + + @ManyToOne(() => SpaceEntity, { nullable: false, onDelete: 'CASCADE' }) + @JoinColumn({ name: 'end_space_id' }) + public endSpace: SpaceEntity; + + @Column({ + type: 'varchar', + length: 10, + nullable: false, + }) + direction: string; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 04ebf83..f7034e3 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -12,6 +12,7 @@ import { UserSpaceEntity } from '../../user/entities'; import { DeviceEntity } from '../../device/entities'; import { CommunityEntity } from '../../community/entities'; import { SubspaceEntity } from './subspace.entity'; +import { SpaceLinkEntity } from './space-link.entity'; @Entity({ name: 'space' }) @Unique(['invitationCode']) @@ -70,6 +71,22 @@ export class SpaceEntity extends AbstractEntity { ) devices: DeviceEntity[]; + @OneToMany(() => SpaceLinkEntity, (connection) => connection.startSpace, { + nullable: true, + }) + public outgoingConnections: SpaceLinkEntity[]; + + @OneToMany(() => SpaceLinkEntity, (connection) => connection.endSpace, { + nullable: true, + }) + public incomingConnections: SpaceLinkEntity[]; + + @Column({ + nullable: true, + type: 'text', + }) + public icon: string; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/space/repositories/space.repository.ts b/libs/common/src/modules/space/repositories/space.repository.ts index 2806c0e..01e8c2d 100644 --- a/libs/common/src/modules/space/repositories/space.repository.ts +++ b/libs/common/src/modules/space/repositories/space.repository.ts @@ -1,6 +1,6 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; -import { SpaceEntity, SubspaceEntity } from '../entities'; +import { SpaceEntity, SpaceLinkEntity, SubspaceEntity } from '../entities'; @Injectable() export class SpaceRepository extends Repository { @@ -14,3 +14,10 @@ export class SubspaceRepository extends Repository { super(SubspaceEntity, dataSource.createEntityManager()); } } + +@Injectable() +export class SpaceLinkRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SpaceLinkEntity, dataSource.createEntityManager()); + } +} diff --git a/src/app.module.ts b/src/app.module.ts index 820602b..609147c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -21,6 +21,7 @@ import { TimeZoneModule } from './timezone/timezone.module'; import { VisitorPasswordModule } from './vistor-password/visitor-password.module'; import { ScheduleModule } from './schedule/schedule.module'; import { SpaceModule } from './space/space.module'; +import { ProductModule } from './product'; @Module({ imports: [ ConfigModule.forRoot({ @@ -46,6 +47,7 @@ import { SpaceModule } from './space/space.module'; TimeZoneModule, VisitorPasswordModule, ScheduleModule, + ProductModule, ], providers: [ { diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index a33bb34..e59d9d1 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -2,6 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsBoolean, IsNotEmpty, + IsNumber, IsOptional, IsString, IsUUID, @@ -25,6 +26,10 @@ export class AddSpaceDto { @IsOptional() parentUuid?: string; + @IsString() + @IsNotEmpty() + public icon: string; + @ApiProperty({ description: 'Indicates whether the space is private or public', example: false, @@ -32,6 +37,19 @@ export class AddSpaceDto { }) @IsBoolean() isPrivate: boolean; + + @ApiProperty({ description: 'X position on canvas', example: 120 }) + @IsNumber() + x: number; + + @ApiProperty({ description: 'Y position on canvas', example: 200 }) + @IsNumber() + y: number; + + @ApiProperty({ description: 'Y position on canvas', example: 200 }) + @IsString() + @IsOptional() + direction: string; } export class AddUserSpaceDto { diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 78d45d7..864ffbb 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -1,4 +1,7 @@ -import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { + SpaceLinkRepository, + SpaceRepository, +} from '@app/common/modules/space/repositories'; import { BadRequestException, HttpException, @@ -16,6 +19,7 @@ import { generateRandomString } from '@app/common/helper/randomString'; export class SpaceService { constructor( private readonly spaceRepository: SpaceRepository, + private readonly spaceLinkRepository: SpaceLinkRepository, private readonly communityRepository: CommunityRepository, ) {} @@ -25,7 +29,7 @@ export class SpaceService { ): Promise { let parent: SpaceEntity | null = null; - const { parentUuid } = addSpaceDto; + const { parentUuid, direction } = addSpaceDto; const community = await this.communityRepository.findOne({ where: { uuid: communityId }, }); @@ -60,6 +64,16 @@ export class SpaceService { await this.spaceRepository.save(newSpace); + if (direction && parent) { + const spaceLink = await this.spaceLinkRepository.create({ + direction, + endSpace: newSpace, + startSpace: parent, + }); + + await this.spaceLinkRepository.save(spaceLink); + } + return new SuccessResponseDto({ statusCode: HttpStatus.CREATED, data: newSpace, @@ -88,7 +102,7 @@ export class SpaceService { // Get all spaces related to the community, including the parent-child relations const spaces = await this.spaceRepository.find({ where: { community: { uuid: communityUuid } }, - relations: ['parent', 'children'], // Include parent and children relations + relations: ['parent', 'children', 'incomingConnections'], // Include parent and children relations }); // Organize spaces into a hierarchical structure diff --git a/src/space/space.module.ts b/src/space/space.module.ts index cf6b766..2d95d7a 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -20,6 +20,7 @@ import { import { SpaceRepository, SubspaceRepository, + SpaceLinkRepository, } from '@app/common/modules/space/repositories'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { @@ -59,6 +60,7 @@ import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log DeviceRepository, CommunityRepository, SubspaceRepository, + SpaceLinkRepository, UserSpaceRepository, UserRepository, SpaceUserService, From 1fe5ba70b9d20751eab689d6c81cc93e91e92551 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 19 Nov 2024 23:51:20 -0600 Subject: [PATCH 067/109] resolve review comments --- src/device/controllers/device.controller.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 81da9f3..8da3b03 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -213,7 +213,7 @@ export class DeviceController { } @ApiBearerAuth() @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) - @Post('scene-switch/:deviceUuid') + @Post(':deviceUuid/scenes') async addSceneToSceneDevice( @Param('deviceUuid') deviceUuid: string, @Body() addSceneToFourSceneDeviceDto: AddSceneToFourSceneDeviceDto, @@ -232,7 +232,7 @@ export class DeviceController { } @ApiBearerAuth() @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) - @Get('scene-switch/switch/:deviceUuid') + @Get(':deviceUuid/scenes/switch') async getSceneBySceneDevice( @Param('deviceUuid') deviceUuid: string, @Query() getSceneFourSceneDeviceDto: GetSceneFourSceneDeviceDto, @@ -244,7 +244,7 @@ export class DeviceController { } @ApiBearerAuth() @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) - @Get('scene-switch/:deviceUuid') + @Get(':deviceUuid/scenes') async getScenesBySceneDevice(@Param('deviceUuid') deviceUuid: string) { return await this.deviceService.getScenesBySceneDevice(deviceUuid); } From 16444c625a620d0df451064a7cf60f47d1ead716 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 20 Nov 2024 00:34:12 -0600 Subject: [PATCH 068/109] remove duplication code --- src/device/controllers/device.controller.ts | 12 +-- src/device/dtos/control.device.dto.ts | 8 +- src/device/services/device.service.ts | 82 ++++++++++----------- 3 files changed, 44 insertions(+), 58 deletions(-) diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 8da3b03..28392aa 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -232,20 +232,14 @@ export class DeviceController { } @ApiBearerAuth() @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) - @Get(':deviceUuid/scenes/switch') - async getSceneBySceneDevice( + @Get(':deviceUuid/scenes') + async getScenesBySceneDevice( @Param('deviceUuid') deviceUuid: string, @Query() getSceneFourSceneDeviceDto: GetSceneFourSceneDeviceDto, ) { - return await this.deviceService.getSceneBySceneDevice( + return await this.deviceService.getScenesBySceneDevice( deviceUuid, getSceneFourSceneDeviceDto, ); } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) - @Get(':deviceUuid/scenes') - async getScenesBySceneDevice(@Param('deviceUuid') deviceUuid: string) { - return await this.deviceService.getScenesBySceneDevice(deviceUuid); - } } diff --git a/src/device/dtos/control.device.dto.ts b/src/device/dtos/control.device.dto.ts index 7a1e900..1303640 100644 --- a/src/device/dtos/control.device.dto.ts +++ b/src/device/dtos/control.device.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsNotEmpty, IsString } from 'class-validator'; +import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class ControlDeviceDto { @ApiProperty({ @@ -57,9 +57,9 @@ export class BatchFactoryResetDevicesDto { export class GetSceneFourSceneDeviceDto { @ApiProperty({ description: 'switchName', - required: true, + required: false, }) @IsString() - @IsNotEmpty() - public switchName: string; + @IsOptional() + public switchName?: string; } diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 4d45264..eeca08c 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -1319,42 +1319,43 @@ export class DeviceService { } } - async getSceneBySceneDevice( + async getScenesBySceneDevice( deviceUuid: string, getSceneFourSceneDeviceDto: GetSceneFourSceneDeviceDto, - ) { + ): Promise { try { - const sceneDevice = await this.sceneDeviceRepository.findOne({ - where: { - device: { uuid: deviceUuid }, - switchName: - getSceneFourSceneDeviceDto.switchName as SceneSwitchesTypeEnum, - }, - relations: ['device', 'scene'], - }); - if (sceneDevice.uuid) { - const SceneDetails = await this.sceneService.getSceneByUuid( + if (getSceneFourSceneDeviceDto.switchName) { + // Query for a single record directly when switchName is provided + const sceneDevice = await this.sceneDeviceRepository.findOne({ + where: { + device: { uuid: deviceUuid }, + switchName: + getSceneFourSceneDeviceDto.switchName as SceneSwitchesTypeEnum, + }, + relations: ['device', 'scene'], + }); + + if (!sceneDevice) { + throw new HttpException( + `No scene found for device with UUID ${deviceUuid} and switch name ${getSceneFourSceneDeviceDto.switchName}`, + HttpStatus.NOT_FOUND, + ); + } + + const sceneDetails = await this.sceneService.getSceneByUuid( sceneDevice.scene.uuid, ); + return { switchName: sceneDevice.switchName, createdAt: sceneDevice.createdAt, updatedAt: sceneDevice.updatedAt, deviceUuid: sceneDevice.device.uuid, - scene: { - ...SceneDetails.data, - }, + scene: sceneDetails.data, }; } - } catch (error) { - throw new HttpException( - error.message || 'Scene device not found', - error.status || HttpStatus.NOT_FOUND, - ); - } - } - async getScenesBySceneDevice(deviceUuid: string): Promise { - try { + + // Query for multiple records if switchName is not provided const sceneDevices = await this.sceneDeviceRepository.find({ where: { device: { uuid: deviceUuid } }, relations: ['device', 'scene'], @@ -1362,37 +1363,28 @@ export class DeviceService { if (!sceneDevices.length) { throw new HttpException( - 'No scenes found for the device', + `No scenes found for device with UUID ${deviceUuid}`, HttpStatus.NOT_FOUND, ); } const results = await Promise.all( sceneDevices.map(async (sceneDevice) => { - if (!sceneDevice.scene?.uuid) return null; + const sceneDetails = await this.sceneService.getSceneByUuid( + sceneDevice.scene.uuid, + ); - try { - const sceneDetails = await this.sceneService.getSceneByUuid( - sceneDevice.scene.uuid, - ); - - return { - switchName: sceneDevice.switchName, - createdAt: sceneDevice.createdAt, - updatedAt: sceneDevice.updatedAt, - deviceUuid: sceneDevice.device.uuid, - scene: sceneDetails.data, - }; - } catch (error) { - throw new HttpException( - 'Failed to fetch scene details', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return { + switchName: sceneDevice.switchName, + createdAt: sceneDevice.createdAt, + updatedAt: sceneDevice.updatedAt, + deviceUuid: sceneDevice.device.uuid, + scene: sceneDetails.data, + }; }), ); - return results.filter(Boolean); + return results; } catch (error) { throw new HttpException( error.message || 'Failed to fetch scenes for device', From 4a98d94ddae95af73662320b27834b20ec91a415 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 20 Nov 2024 14:55:32 +0400 Subject: [PATCH 069/109] added name to product --- libs/common/src/modules/product/entities/product.entity.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/common/src/modules/product/entities/product.entity.ts b/libs/common/src/modules/product/entities/product.entity.ts index 6553dc1..6158aa9 100644 --- a/libs/common/src/modules/product/entities/product.entity.ts +++ b/libs/common/src/modules/product/entities/product.entity.ts @@ -16,6 +16,11 @@ export class ProductEntity extends AbstractEntity { }) public prodId: string; + @Column({ + nullable: true, + }) + public name: string; + @Column({ nullable: false, }) From c7404972a27c3928e4d53648643573f41ccdbe8c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 20 Nov 2024 16:01:51 +0400 Subject: [PATCH 070/109] added space link service --- libs/common/src/constants/direction.enum.ts | 5 + .../space/entities/space-link.entity.ts | 6 +- src/space/services/index.ts | 1 + src/space/services/space-link/index.ts | 1 + .../services/space-link/space-link.service.ts | 88 ++++++++ src/space/services/space.service.ts | 201 ++++-------------- src/space/space.module.ts | 2 + 7 files changed, 145 insertions(+), 159 deletions(-) create mode 100644 libs/common/src/constants/direction.enum.ts create mode 100644 src/space/services/space-link/index.ts create mode 100644 src/space/services/space-link/space-link.service.ts diff --git a/libs/common/src/constants/direction.enum.ts b/libs/common/src/constants/direction.enum.ts new file mode 100644 index 0000000..5791b57 --- /dev/null +++ b/libs/common/src/constants/direction.enum.ts @@ -0,0 +1,5 @@ +export enum Direction { + LEFT = 'left', + RIGHT = 'right', + DOWN = 'down', +} diff --git a/libs/common/src/modules/space/entities/space-link.entity.ts b/libs/common/src/modules/space/entities/space-link.entity.ts index 6e2524f..a62ce4f 100644 --- a/libs/common/src/modules/space/entities/space-link.entity.ts +++ b/libs/common/src/modules/space/entities/space-link.entity.ts @@ -1,8 +1,9 @@ import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceEntity } from './space.entity'; +import { Direction } from '@app/common/constants/direction.enum'; -@Entity({ name: 'space_link' }) +@Entity({ name: 'space-link' }) export class SpaceLinkEntity extends AbstractEntity { @ManyToOne(() => SpaceEntity, { nullable: false, onDelete: 'CASCADE' }) @JoinColumn({ name: 'start_space_id' }) @@ -13,9 +14,8 @@ export class SpaceLinkEntity extends AbstractEntity { public endSpace: SpaceEntity; @Column({ - type: 'varchar', - length: 10, nullable: false, + enum: Object.values(Direction), }) direction: string; diff --git a/src/space/services/index.ts b/src/space/services/index.ts index c4e9405..1dde0ae 100644 --- a/src/space/services/index.ts +++ b/src/space/services/index.ts @@ -2,4 +2,5 @@ export * from './space.service'; export * from './space-user.service'; export * from './space-device.service'; export * from './subspace'; +export * from './space-link'; export * from './space-scene.service'; diff --git a/src/space/services/space-link/index.ts b/src/space/services/space-link/index.ts new file mode 100644 index 0000000..ad41b9a --- /dev/null +++ b/src/space/services/space-link/index.ts @@ -0,0 +1 @@ +export * from './space-link.service'; diff --git a/src/space/services/space-link/space-link.service.ts b/src/space/services/space-link/space-link.service.ts new file mode 100644 index 0000000..0acece0 --- /dev/null +++ b/src/space/services/space-link/space-link.service.ts @@ -0,0 +1,88 @@ +import { + SpaceLinkRepository, + SpaceRepository, +} from '@app/common/modules/space/repositories'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; + +@Injectable() +export class SpaceLinkService { + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly spaceLinkRepository: SpaceLinkRepository, + ) {} + + async saveSpaceLink( + startSpaceId: string, + endSpaceId: string, + direction: string, + ): Promise { + try { + // Check if a link between the startSpace and endSpace already exists + const existingLink = await this.spaceLinkRepository.findOne({ + where: { + startSpace: { uuid: startSpaceId }, + endSpace: { uuid: endSpaceId }, + }, + }); + + if (existingLink) { + // Update the direction if the link exists + existingLink.direction = direction; + await this.spaceLinkRepository.save(existingLink); + return; + } + + const existingEndSpaceLink = await this.spaceLinkRepository.findOne({ + where: { endSpace: { uuid: endSpaceId } }, + }); + + if ( + existingEndSpaceLink && + existingEndSpaceLink.startSpace.uuid !== startSpaceId + ) { + throw new Error( + `Space with ID ${endSpaceId} is already an endSpace in another link and cannot be reused.`, + ); + } + + // Find start space + const startSpace = await this.spaceRepository.findOne({ + where: { uuid: startSpaceId }, + }); + + if (!startSpace) { + throw new HttpException( + `Start space with ID ${startSpaceId} not found.`, + HttpStatus.NOT_FOUND, + ); + } + + // Find end space + const endSpace = await this.spaceRepository.findOne({ + where: { uuid: endSpaceId }, + }); + + if (!endSpace) { + throw new HttpException( + `End space with ID ${endSpaceId} not found.`, + HttpStatus.NOT_FOUND, + ); + } + + // Create and save the space link + const spaceLink = this.spaceLinkRepository.create({ + startSpace, + endSpace, + direction, + }); + + await this.spaceLinkRepository.save(spaceLink); + } catch (error) { + throw new HttpException( + error.message || + `Failed to save space link. Internal Server Error: ${error.message}`, + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 864ffbb..e9dedb5 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -1,7 +1,4 @@ -import { - SpaceLinkRepository, - SpaceRepository, -} from '@app/common/modules/space/repositories'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException, HttpException, @@ -14,47 +11,25 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { SpaceEntity } from '@app/common/modules/space/entities'; import { generateRandomString } from '@app/common/helper/randomString'; +import { SpaceLinkService } from './space-link'; @Injectable() export class SpaceService { constructor( private readonly spaceRepository: SpaceRepository, - private readonly spaceLinkRepository: SpaceLinkRepository, private readonly communityRepository: CommunityRepository, + private readonly spaceLinkService: SpaceLinkService, ) {} async createSpace( addSpaceDto: AddSpaceDto, communityId: string, ): Promise { - let parent: SpaceEntity | null = null; - const { parentUuid, direction } = addSpaceDto; - const community = await this.communityRepository.findOne({ - where: { uuid: communityId }, - }); - // If the community doesn't exist, throw a 404 error - if (!community) { - throw new HttpException( - `Community with ID ${communityId} not found`, - HttpStatus.NOT_FOUND, - ); - } + const community = await this.validateCommunity(communityId); - if (parentUuid) { - parent = await this.spaceRepository.findOne({ - where: { uuid: parentUuid }, - }); - - // If the community doesn't exist, throw a 404 error - if (!parent) { - throw new HttpException( - `Parent with ID ${parentUuid} not found`, - HttpStatus.NOT_FOUND, - ); - } - } + const parent = parentUuid ? await this.validateSpace(parentUuid) : null; try { const newSpace = this.spaceRepository.create({ ...addSpaceDto, @@ -65,13 +40,11 @@ export class SpaceService { await this.spaceRepository.save(newSpace); if (direction && parent) { - const spaceLink = await this.spaceLinkRepository.create({ + await this.spaceLinkService.saveSpaceLink( + parent.uuid, + newSpace.uuid, direction, - endSpace: newSpace, - startSpace: parent, - }); - - await this.spaceLinkRepository.save(spaceLink); + ); } return new SuccessResponseDto({ @@ -87,17 +60,7 @@ export class SpaceService { async getSpacesHierarchyForCommunity( communityUuid: string, ): Promise { - 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, - ); - } + await this.validateCommunity(communityUuid); try { // Get all spaces related to the community, including the parent-child relations const spaces = await this.spaceRepository.find({ @@ -123,19 +86,8 @@ export class SpaceService { async findOne(spaceUuid: string): Promise { try { - const space = await this.spaceRepository.findOne({ - where: { - uuid: spaceUuid, - }, - }); + const space = await this.validateSpace(spaceUuid); - // If space is not found, throw a NotFoundException - if (!space) { - throw new HttpException( - `Space with UUID ${spaceUuid} not found`, - HttpStatus.NOT_FOUND, - ); - } return new SuccessResponseDto({ message: `Space with ID ${spaceUuid} successfully fetched`, data: space, @@ -158,28 +110,10 @@ export class SpaceService { ): Promise { try { // First, check if the community exists - const community = await this.communityRepository.findOne({ - where: { uuid: communityUuid }, - }); - - if (!community) { - throw new HttpException( - `Community with ID ${communityUuid} not found`, - HttpStatus.NOT_FOUND, - ); - } - - // Check if the space exists - const space = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid, community: { uuid: communityUuid } }, - }); - - if (!space) { - throw new HttpException( - `Space with ID ${spaceUuid} not found`, - HttpStatus.NOT_FOUND, - ); - } + const space = await this.validateCommunityAndSpace( + spaceUuid, + communityUuid, + ); // Delete the space await this.spaceRepository.remove(space); @@ -205,54 +139,17 @@ export class SpaceService { updateSpaceDto: AddSpaceDto, ): Promise { try { - // First, check if the community exists - const community = await this.communityRepository.findOne({ - where: { uuid: communityId }, - }); - - if (!community) { - throw new HttpException( - `Community with ID ${communityId} not found`, - HttpStatus.NOT_FOUND, - ); - } - - // Check if the space exists - const space = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid, community: { uuid: communityId } }, - }); - - if (!space) { - throw new HttpException( - `Space with ID ${spaceUuid} not found`, - HttpStatus.NOT_FOUND, - ); - } + const space = await this.validateCommunityAndSpace( + communityId, + spaceUuid, + ); // If a parentId is provided, check if the parent exists const { parentUuid } = updateSpaceDto; - let parent: SpaceEntity | null = null; - if (parentUuid) { - parent = await this.spaceRepository.findOne({ - where: { uuid: parentUuid, community: { uuid: communityId } }, - }); - - // If the parent doesn't exist, throw a 404 error - if (!parent) { - throw new HttpException( - `Parent space with ID ${parentUuid} not found`, - HttpStatus.NOT_FOUND, - ); - } - - // Set the parent of the current space - space.parent = parent; - } else { - space.parent = null; // If no parent is provided, clear the parent - } + const parent = parentUuid ? await this.validateSpace(parentUuid) : null; // Update other space properties from updateSpaceDto - Object.assign(space, updateSpaceDto); + Object.assign(space, updateSpaceDto, { parent }); // Save the updated space await this.spaceRepository.save(space); @@ -276,17 +173,7 @@ export class SpaceService { async getSpacesHierarchyForSpace( spaceUuid: string, ): Promise { - const space = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid }, - }); - - // If the space doesn't exist, throw a 404 error - if (!space) { - throw new HttpException( - `Space with ID ${spaceUuid} not found`, - HttpStatus.NOT_FOUND, - ); - } + await this.validateSpace(spaceUuid); try { // Get all spaces that are children of the provided space, including the parent-child relations @@ -315,18 +202,7 @@ export class SpaceService { try { const invitationCode = generateRandomString(6); - const space = await this.spaceRepository.findOne({ - where: { - uuid: spaceUuid, - }, - }); - - if (!space) { - throw new HttpException( - `Space with ID ${spaceUuid} not found`, - HttpStatus.NOT_FOUND, - ); - } + const space = await this.validateSpace(spaceUuid); space.invitationCode = invitationCode; await this.spaceRepository.save(space); @@ -369,24 +245,37 @@ export class SpaceService { } }); - return rootSpaces; // Return the root spaces with children nested within them + return rootSpaces; + } + + private async validateCommunity(communityId: string) { + const community = await this.communityRepository.findOne({ + where: { uuid: communityId }, + }); + if (!community) { + throw new HttpException( + `Community with ID ${communityId} not found`, + HttpStatus.NOT_FOUND, + ); + } + return community; } async validateCommunityAndSpace(communityUuid: string, spaceUuid: string) { - const community = await this.communityRepository.findOne({ - where: { uuid: communityUuid }, - }); + const community = await this.validateCommunity(communityUuid); if (!community) { this.throwNotFound('Community', communityUuid); } + const space = await this.validateSpace(spaceUuid); + return space; + } + + private async validateSpace(spaceUuid: string) { const space = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid, community: { uuid: communityUuid } }, - relations: ['devices', 'devices.productDevice'], + where: { uuid: spaceUuid }, }); - if (!space) { - this.throwNotFound('Space', spaceUuid); - } + if (!space) this.throwNotFound('Space', spaceUuid); return space; } diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 2d95d7a..8d3bc6e 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -11,6 +11,7 @@ import { } from './controllers'; import { SpaceDeviceService, + SpaceLinkService, SpaceSceneService, SpaceService, SpaceUserService, @@ -55,6 +56,7 @@ import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log ProductRepository, SubSpaceService, SpaceDeviceService, + SpaceLinkService, SubspaceDeviceService, SpaceRepository, DeviceRepository, From 2ed5d9dce7a05c1517021ef5d5d6b4329f9aee97 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 20 Nov 2024 16:16:17 +0400 Subject: [PATCH 071/109] Added space product relation --- libs/common/src/database/database.module.ts | 2 ++ .../product/entities/product.entity.ts | 4 +++ .../space/entities/space-product.entity.ts | 32 +++++++++++++++++++ .../modules/space/entities/space.entity.ts | 4 +++ .../space/repositories/space.repository.ts | 8 +++++ 5 files changed, 50 insertions(+) create mode 100644 libs/common/src/modules/space/entities/space-product.entity.ts diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 868ef5a..92f3db6 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -23,6 +23,7 @@ import { CommunityEntity } from '../modules/community/entities'; import { DeviceStatusLogEntity } from '../modules/device-status-log/entities'; import { SceneEntity, SceneIconEntity } from '../modules/scene/entities'; import { SceneDeviceEntity } from '../modules/scene-device/entities'; +import { SpaceProductEntity } from '../modules/space/entities/space-product.entity'; @Module({ imports: [ @@ -49,6 +50,7 @@ import { SceneDeviceEntity } from '../modules/scene-device/entities'; SpaceEntity, SubspaceEntity, SpaceTypeEntity, + SpaceProductEntity, UserSpaceEntity, DeviceUserPermissionEntity, UserRoleEntity, diff --git a/libs/common/src/modules/product/entities/product.entity.ts b/libs/common/src/modules/product/entities/product.entity.ts index 6553dc1..a7cf957 100644 --- a/libs/common/src/modules/product/entities/product.entity.ts +++ b/libs/common/src/modules/product/entities/product.entity.ts @@ -2,6 +2,7 @@ import { Column, Entity, OneToMany } from 'typeorm'; import { ProductDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceEntity } from '../../device/entities'; +import { SpaceProductEntity } from '../../space/entities/space-product.entity'; @Entity({ name: 'product' }) export class ProductEntity extends AbstractEntity { @@ -21,6 +22,9 @@ export class ProductEntity extends AbstractEntity { }) public prodType: string; + @OneToMany(() => SpaceProductEntity, (spaceProduct) => spaceProduct.product) + spaceProducts: SpaceProductEntity[]; + @OneToMany( () => DeviceEntity, (devicesProductEntity) => devicesProductEntity.productDevice, diff --git a/libs/common/src/modules/space/entities/space-product.entity.ts b/libs/common/src/modules/space/entities/space-product.entity.ts new file mode 100644 index 0000000..3dd6cbe --- /dev/null +++ b/libs/common/src/modules/space/entities/space-product.entity.ts @@ -0,0 +1,32 @@ +import { Column, Entity, ManyToOne, JoinColumn } from 'typeorm'; +import { SpaceEntity } from './space.entity'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { ProductEntity } from '../../product/entities'; + +@Entity({ name: 'space-product' }) +export class SpaceProductEntity extends AbstractEntity { + @ManyToOne(() => SpaceEntity, (space) => space.spaceProducts, { + nullable: false, + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'space_id' }) + space: SpaceEntity; + + @ManyToOne(() => ProductEntity, (product) => product.spaceProducts, { + nullable: false, + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'product_id' }) + product: ProductEntity; + + @Column({ + nullable: false, + type: 'int', + }) + productCount: number; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 196a522..837b875 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -12,6 +12,7 @@ import { UserSpaceEntity } from '../../user/entities'; import { DeviceEntity } from '../../device/entities'; import { CommunityEntity } from '../../community/entities'; import { SubspaceEntity } from './subspace.entity'; +import { SpaceProductEntity } from './space-product.entity'; @Entity({ name: 'space-type' }) export class SpaceTypeEntity extends AbstractEntity { @@ -83,6 +84,9 @@ export class SpaceEntity extends AbstractEntity { ) devices: DeviceEntity[]; + @OneToMany(() => SpaceProductEntity, (spaceProduct) => spaceProduct.space) + spaceProducts: SpaceProductEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/space/repositories/space.repository.ts b/libs/common/src/modules/space/repositories/space.repository.ts index 5e0962c..d34fc24 100644 --- a/libs/common/src/modules/space/repositories/space.repository.ts +++ b/libs/common/src/modules/space/repositories/space.repository.ts @@ -1,6 +1,7 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; import { SpaceEntity, SpaceTypeEntity, SubspaceEntity } from '../entities'; +import { SpaceProductEntity } from '../entities/space-product.entity'; @Injectable() export class SpaceRepository extends Repository { @@ -22,3 +23,10 @@ export class SubspaceRepository extends Repository { super(SubspaceEntity, dataSource.createEntityManager()); } } + +@Injectable() +export class SpaceProductRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SpaceProductEntity, dataSource.createEntityManager()); + } +} From bc1638aba488daf69cff691e477279968cecb042 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 20 Nov 2024 16:16:58 +0400 Subject: [PATCH 072/109] changed naming --- .../common/src/modules/space/entities/space-product.entity.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/common/src/modules/space/entities/space-product.entity.ts b/libs/common/src/modules/space/entities/space-product.entity.ts index 3dd6cbe..92ad411 100644 --- a/libs/common/src/modules/space/entities/space-product.entity.ts +++ b/libs/common/src/modules/space/entities/space-product.entity.ts @@ -9,14 +9,14 @@ export class SpaceProductEntity extends AbstractEntity { nullable: false, onDelete: 'CASCADE', }) - @JoinColumn({ name: 'space_id' }) + @JoinColumn({ name: 'space_uuid' }) space: SpaceEntity; @ManyToOne(() => ProductEntity, (product) => product.spaceProducts, { nullable: false, onDelete: 'CASCADE', }) - @JoinColumn({ name: 'product_id' }) + @JoinColumn({ name: 'product_uuid' }) product: ProductEntity; @Column({ From 11564eece780f565876f3eef7bb2bd7852762f26 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 20 Nov 2024 16:34:44 +0400 Subject: [PATCH 073/109] added space product service --- src/space/dtos/add.space.dto.ts | 17 ++++ src/space/services/index.ts | 1 + src/space/services/space-products/index.ts | 1 + .../space-products/space-products.service.ts | 95 +++++++++++++++++++ src/space/services/space.service.ts | 11 ++- src/space/space.module.ts | 4 + 6 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 src/space/services/space-products/index.ts create mode 100644 src/space/services/space-products/space-products.service.ts diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index a33bb34..832287f 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -1,10 +1,13 @@ import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; import { + IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString, IsUUID, + ValidateNested, } from 'class-validator'; export class AddSpaceDto { @@ -32,6 +35,11 @@ export class AddSpaceDto { }) @IsBoolean() isPrivate: boolean; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => ProductAssignmentDto) + products: ProductAssignmentDto[]; } export class AddUserSpaceDto { @@ -69,7 +77,16 @@ export class AddUserSpaceUsingCodeDto { @IsString() @IsNotEmpty() public inviteCode: string; + constructor(dto: Partial) { Object.assign(this, dto); } } + +class ProductAssignmentDto { + @IsNotEmpty() + productId: string; + + @IsNotEmpty() + count: number; +} diff --git a/src/space/services/index.ts b/src/space/services/index.ts index c4e9405..89f8d9e 100644 --- a/src/space/services/index.ts +++ b/src/space/services/index.ts @@ -3,3 +3,4 @@ export * from './space-user.service'; export * from './space-device.service'; export * from './subspace'; export * from './space-scene.service'; +export * from './space-products'; diff --git a/src/space/services/space-products/index.ts b/src/space/services/space-products/index.ts new file mode 100644 index 0000000..d0b92d2 --- /dev/null +++ b/src/space/services/space-products/index.ts @@ -0,0 +1 @@ +export * from './space-products.service'; diff --git a/src/space/services/space-products/space-products.service.ts b/src/space/services/space-products/space-products.service.ts new file mode 100644 index 0000000..7c4d634 --- /dev/null +++ b/src/space/services/space-products/space-products.service.ts @@ -0,0 +1,95 @@ +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { ProductRepository } from '@app/common/modules/product/repositories'; +import { SpaceEntity } from '@app/common/modules/space/entities'; +import { SpaceProductEntity } from '@app/common/modules/space/entities/space-product.entity'; +import { SpaceProductRepository } from '@app/common/modules/space/repositories'; +import { In } from 'typeorm'; + +@Injectable() +export class SpaceProductService { + constructor( + private readonly productRepository: ProductRepository, + private readonly spaceProductRepository: SpaceProductRepository, + ) {} + + async assignProductsToSpace( + space: SpaceEntity, + products: { productId: string; count: number }[], + ): Promise { + try { + const uniqueProducts = this.validateUniqueProducts(products); + const productEntities = await this.getProductEntities(uniqueProducts); + + const spaceProductEntities = uniqueProducts.map( + ({ productId, count }) => { + const product = productEntities.get(productId); + if (!product) { + throw new HttpException( + `Product with ID ${productId} not found`, + HttpStatus.NOT_FOUND, + ); + } + + return this.spaceProductRepository.create({ + space, + product, + productCount: count, + }); + }, + ); + + return await this.spaceProductRepository.save(spaceProductEntities); + } catch (error) { + console.error('Error assigning products to space:', error); + + if (!(error instanceof HttpException)) { + throw new HttpException( + 'An error occurred while assigning products to the space', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + throw error; + } + } + + private validateUniqueProducts( + products: { productId: string; count: number }[], + ): { productId: string; count: number }[] { + const productIds = new Set(); + const uniqueProducts = []; + + for (const product of products) { + if (productIds.has(product.productId)) { + throw new HttpException( + `Duplicate product ID found: ${product.productId}`, + HttpStatus.BAD_REQUEST, + ); + } + productIds.add(product.productId); + uniqueProducts.push(product); + } + + return uniqueProducts; + } + + private async getProductEntities( + products: { productId: string; count: number }[], + ): Promise> { + try { + const productIds = products.map((p) => p.productId); + + const productEntities = await this.productRepository.find({ + where: { prodId: In(productIds) }, + }); + + return new Map(productEntities.map((p) => [p.prodId, p])); + } catch (error) { + console.error('Error fetching product entities:', error); + throw new HttpException( + 'Failed to fetch product entities', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 78d45d7..7340c7c 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -11,12 +11,14 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { SpaceEntity } from '@app/common/modules/space/entities'; import { generateRandomString } from '@app/common/helper/randomString'; +import { SpaceProductService } from './space-products'; @Injectable() export class SpaceService { constructor( private readonly spaceRepository: SpaceRepository, private readonly communityRepository: CommunityRepository, + private readonly spaceProductService: SpaceProductService, ) {} async createSpace( @@ -25,7 +27,7 @@ export class SpaceService { ): Promise { let parent: SpaceEntity | null = null; - const { parentUuid } = addSpaceDto; + const { parentUuid, products } = addSpaceDto; const community = await this.communityRepository.findOne({ where: { uuid: communityId }, }); @@ -60,6 +62,13 @@ export class SpaceService { await this.spaceRepository.save(newSpace); + if (products && products.length > 0) { + await this.spaceProductService.assignProductsToSpace( + newSpace, + products, + ); + } + return new SuccessResponseDto({ statusCode: HttpStatus.CREATED, data: newSpace, diff --git a/src/space/space.module.ts b/src/space/space.module.ts index c840bd9..030cb95 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -11,6 +11,7 @@ import { } from './controllers'; import { SpaceDeviceService, + SpaceProductService, SpaceSceneService, SpaceService, SpaceUserService, @@ -18,6 +19,7 @@ import { SubSpaceService, } from './services'; import { + SpaceProductRepository, SpaceRepository, SubspaceRepository, } from '@app/common/modules/space/repositories'; @@ -71,6 +73,8 @@ import { SceneDeviceRepository } from '@app/common/modules/scene-device/reposito DeviceStatusFirebaseService, DeviceStatusLogRepository, SceneDeviceRepository, + SpaceProductService, + SpaceProductRepository, ], exports: [SpaceService], }) From c0b8e0d779edc5a37f4855892fd1051ed919b5b9 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 21 Nov 2024 08:20:33 +0400 Subject: [PATCH 074/109] merged with space-product --- libs/common/src/modules/space/entities/space.entity.ts | 1 + src/space/services/space.service.ts | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 16d5786..38d4c5e 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -87,6 +87,7 @@ export class SpaceEntity extends AbstractEntity { type: 'text', }) public icon: string; + @OneToMany(() => SpaceProductEntity, (spaceProduct) => spaceProduct.space) spaceProducts: SpaceProductEntity[]; diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index f8dcf71..7e22f40 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -74,7 +74,12 @@ export class SpaceService { // Get all spaces related to the community, including the parent-child relations const spaces = await this.spaceRepository.find({ where: { community: { uuid: communityUuid } }, - relations: ['parent', 'children', 'incomingConnections'], // Include parent and children relations + relations: [ + 'parent', + 'children', + 'incomingConnections', + 'spaceProducts', + ], // Include parent and children relations }); // Organize spaces into a hierarchical structure From 7d627029c470481769042a9dee8117e688afaa80 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 21 Nov 2024 09:14:29 +0400 Subject: [PATCH 075/109] fixed uuid --- src/space/services/space-products/space-products.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/space/services/space-products/space-products.service.ts b/src/space/services/space-products/space-products.service.ts index 7c4d634..8484426 100644 --- a/src/space/services/space-products/space-products.service.ts +++ b/src/space/services/space-products/space-products.service.ts @@ -80,10 +80,10 @@ export class SpaceProductService { const productIds = products.map((p) => p.productId); const productEntities = await this.productRepository.find({ - where: { prodId: In(productIds) }, + where: { uuid: In(productIds) }, }); - return new Map(productEntities.map((p) => [p.prodId, p])); + return new Map(productEntities.map((p) => [p.uuid, p])); } catch (error) { console.error('Error fetching product entities:', error); throw new HttpException( From 3f5ae396e3c7f4d5ea82079e49398ccff3b538a0 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 21 Nov 2024 10:44:55 +0400 Subject: [PATCH 076/109] added product details --- src/space/services/space.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 7e22f40..7ee2041 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -79,6 +79,7 @@ export class SpaceService { 'children', 'incomingConnections', 'spaceProducts', + 'spaceProducts.product', ], // Include parent and children relations }); From 67c5ebaa41e1e1d22b9547e158dffe1bcd5e4042 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 21 Nov 2024 10:59:58 +0400 Subject: [PATCH 077/109] product update --- .../space-products/space-products.service.ts | 70 ++++++++++++++----- src/space/services/space.service.ts | 11 ++- 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/space/services/space-products/space-products.service.ts b/src/space/services/space-products/space-products.service.ts index 8484426..5b218e4 100644 --- a/src/space/services/space-products/space-products.service.ts +++ b/src/space/services/space-products/space-products.service.ts @@ -20,25 +20,61 @@ export class SpaceProductService { const uniqueProducts = this.validateUniqueProducts(products); const productEntities = await this.getProductEntities(uniqueProducts); - const spaceProductEntities = uniqueProducts.map( - ({ productId, count }) => { - const product = productEntities.get(productId); - if (!product) { - throw new HttpException( - `Product with ID ${productId} not found`, - HttpStatus.NOT_FOUND, - ); - } - - return this.spaceProductRepository.create({ - space, - product, - productCount: count, - }); + // Fetch existing space products + const existingSpaceProducts = await this.spaceProductRepository.find({ + where: { + space: { + uuid: space.uuid, + }, }, - ); + relations: ['product'], + }); - return await this.spaceProductRepository.save(spaceProductEntities); + const updatedProducts = []; + const newProducts = []; + + for (const { productId, count } of uniqueProducts) { + const product = productEntities.get(productId); + if (!product) { + throw new HttpException( + `Product with ID ${productId} not found`, + HttpStatus.NOT_FOUND, + ); + } + + // Check if product already exists in the space + const existingProduct = existingSpaceProducts.find( + (spaceProduct) => spaceProduct.product.uuid === productId, + ); + + if (existingProduct) { + // If count is different, update the existing record + if (existingProduct.productCount !== count) { + existingProduct.productCount = count; + updatedProducts.push(existingProduct); + } + } else { + // Add new product if it doesn't exist + newProducts.push( + this.spaceProductRepository.create({ + space, + product, + productCount: count, + }), + ); + } + } + + // Save updates and new records + if (updatedProducts.length > 0) { + await this.spaceProductRepository.save(updatedProducts); + } + + if (newProducts.length > 0) { + await this.spaceProductRepository.save(newProducts); + } + + return [...updatedProducts, ...newProducts]; } catch (error) { console.error('Error assigning products to space:', error); diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 7ee2041..44d27ba 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -160,14 +160,21 @@ export class SpaceService { ); // If a parentId is provided, check if the parent exists - const { parentUuid } = updateSpaceDto; + const { parentUuid, products } = updateSpaceDto; const parent = parentUuid ? await this.validateSpace(parentUuid) : null; // Update other space properties from updateSpaceDto Object.assign(space, updateSpaceDto, { parent }); // Save the updated space - await this.spaceRepository.save(space); + const updatedSpace = await this.spaceRepository.save(space); + + if (products && products.length > 0) { + await this.spaceProductService.assignProductsToSpace( + updatedSpace, + products, + ); + } return new SuccessResponseDto({ message: `Space with ID ${spaceUuid} successfully updated`, From 221fd47e2e71d87ca3532b6479f8ecf5cc396f48 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 21 Nov 2024 16:11:53 +0400 Subject: [PATCH 078/109] fixed delete bug --- libs/common/src/modules/space/entities/space.entity.ts | 5 ++++- src/space/services/space.service.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 38d4c5e..8aa717a 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -48,7 +48,10 @@ export class SpaceEntity extends AbstractEntity { @ManyToOne(() => SpaceEntity, (space) => space.children, { nullable: true }) parent: SpaceEntity; - @OneToMany(() => SpaceEntity, (space) => space.parent) + @OneToMany(() => SpaceEntity, (space) => space.parent, { + nullable: false, + onDelete: 'CASCADE', + }) children: SpaceEntity[]; @OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.space) diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 44d27ba..6e3e600 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -126,8 +126,8 @@ export class SpaceService { try { // First, check if the community exists const space = await this.validateCommunityAndSpace( - spaceUuid, communityUuid, + spaceUuid, ); // Delete the space From 7b3c929aa603b99fe571af2aea147dd41cbbea90 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 20:50:34 -0600 Subject: [PATCH 079/109] finished delete switch scene device --- .../modules/scene/entities/scene.entity.ts | 14 +++-- .../modules/space/entities/space.entity.ts | 4 ++ src/device/controllers/device.controller.ts | 11 ++++ src/device/dtos/device.param.dto.ts | 11 ++++ src/device/services/device.service.ts | 60 +++++++++++++++++++ 5 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 src/device/dtos/device.param.dto.ts diff --git a/libs/common/src/modules/scene/entities/scene.entity.ts b/libs/common/src/modules/scene/entities/scene.entity.ts index 58a880a..5daa690 100644 --- a/libs/common/src/modules/scene/entities/scene.entity.ts +++ b/libs/common/src/modules/scene/entities/scene.entity.ts @@ -1,8 +1,9 @@ -import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; import { SceneDto, SceneIconDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SceneIconType } from '@app/common/constants/secne-icon-type.enum'; import { SceneDeviceEntity } from '../../scene-device/entities'; +import { SpaceEntity } from '../../space/entities'; // Define SceneIconEntity before SceneEntity @Entity({ name: 'scene-icon' }) @@ -45,16 +46,19 @@ export class SceneEntity extends AbstractEntity { nullable: false, }) sceneTuyaUuid: string; - @Column({ - nullable: false, - }) - spaceUuid: string; @Column({ nullable: false, }) showInHomePage: boolean; + @ManyToOne(() => SpaceEntity, (space) => space.scenes, { + nullable: false, + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'space_uuid' }) + space: SpaceEntity; + @ManyToOne(() => SceneIconEntity, (icon) => icon.scenesIconEntity, { nullable: false, }) diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 8aa717a..103f0a2 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -14,6 +14,7 @@ import { CommunityEntity } from '../../community/entities'; import { SubspaceEntity } from './subspace.entity'; import { SpaceLinkEntity } from './space-link.entity'; import { SpaceProductEntity } from './space-product.entity'; +import { SceneEntity } from '../../scene/entities'; @Entity({ name: 'space' }) @Unique(['invitationCode']) @@ -94,6 +95,9 @@ export class SpaceEntity extends AbstractEntity { @OneToMany(() => SpaceProductEntity, (spaceProduct) => spaceProduct.space) spaceProducts: SpaceProductEntity[]; + @OneToMany(() => SceneEntity, (scene) => scene.space) + scenes: SceneEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 28392aa..ffce4b9 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -10,6 +10,7 @@ import { UseGuards, Req, Put, + Delete, } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { @@ -32,6 +33,8 @@ import { CheckDeviceGuard } from 'src/guards/device.guard'; import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { CheckFourAndSixSceneDeviceTypeGuard } from 'src/guards/scene.device.type.guard'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { DeviceSceneParamDto } from '../dtos/device.param.dto'; @ApiTags('Device Module') @Controller({ @@ -242,4 +245,12 @@ export class DeviceController { getSceneFourSceneDeviceDto, ); } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Delete(':switchSceneUuid/scenes') + async deleteSceneToSceneDevice( + @Param() param: DeviceSceneParamDto, + ): Promise { + return await this.deviceService.deleteSceneToSceneDevice(param); + } } diff --git a/src/device/dtos/device.param.dto.ts b/src/device/dtos/device.param.dto.ts new file mode 100644 index 0000000..264b656 --- /dev/null +++ b/src/device/dtos/device.param.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; + +export class DeviceSceneParamDto { + @ApiProperty({ + description: 'UUID of the Switch Scene', + example: 'b3421478-3bec-4634-9805-a53950260ecb', + }) + @IsUUID() + switchSceneUuid: string; +} diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index eeca08c..8eea7ce 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -55,6 +55,9 @@ import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; import { SceneSwitchesTypeEnum } from '@app/common/constants/scene-switch-type.enum'; import { AUTOMATION_CONFIG } from '@app/common/constants/automation.enum'; +import { DeviceSceneParamDto } from '../dtos/device.param.dto'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; @Injectable() export class DeviceService { @@ -1347,6 +1350,7 @@ export class DeviceService { ); return { + switchSceneUuid: sceneDevice.uuid, switchName: sceneDevice.switchName, createdAt: sceneDevice.createdAt, updatedAt: sceneDevice.updatedAt, @@ -1375,6 +1379,7 @@ export class DeviceService { ); return { + switchSceneUuid: sceneDevice.uuid, switchName: sceneDevice.switchName, createdAt: sceneDevice.createdAt, updatedAt: sceneDevice.updatedAt, @@ -1392,4 +1397,59 @@ export class DeviceService { ); } } + async deleteSceneToSceneDevice( + params: DeviceSceneParamDto, + ): Promise { + const { switchSceneUuid } = params; + + try { + const existingSceneDevice = await this.sceneDeviceRepository.findOne({ + where: { uuid: switchSceneUuid }, + relations: ['scene.space.community'], + }); + + if (!existingSceneDevice) { + throw new HttpException( + `Switch Scene not found for ID ${switchSceneUuid}`, + HttpStatus.NOT_FOUND, + ); + } + + const deleteResult = await this.sceneDeviceRepository.delete({ + uuid: switchSceneUuid, + }); + + if (deleteResult.affected === 0) { + throw new HttpException( + `Failed to delete Switch Scene with ID ${switchSceneUuid}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + const tuyaAutomationResult = await this.tuyaService.deleteAutomation( + existingSceneDevice.scene.space.community.externalId, + existingSceneDevice.automationTuyaUuid, + ); + + if (!tuyaAutomationResult.success) { + throw new HttpException( + `Failed to delete Tuya automation for Switch Scene with ID ${switchSceneUuid}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + return new SuccessResponseDto({ + message: `Switch Scene with ID ${switchSceneUuid} deleted successfully`, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + error.message || `An unexpected error occurred`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } From 4e31b71a56b1a9755dca598b0929a25cec81b270 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:49:53 -0600 Subject: [PATCH 080/109] Update DeviceStatusFirebaseController with improved route and API operation documentation --- .../controllers/devices-status.controller.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/libs/common/src/firebase/devices-status/controllers/devices-status.controller.ts b/libs/common/src/firebase/devices-status/controllers/devices-status.controller.ts index 4a3458d..5a79b1c 100644 --- a/libs/common/src/firebase/devices-status/controllers/devices-status.controller.ts +++ b/libs/common/src/firebase/devices-status/controllers/devices-status.controller.ts @@ -1,13 +1,14 @@ import { Controller, Post, Param } from '@nestjs/common'; -import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { AddDeviceStatusDto } from '../dtos/add.devices-status.dto'; import { DeviceStatusFirebaseService } from '../services/devices-status.service'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; @ApiTags('Device Status Firebase Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'device-status-firebase', + path: ControllerRoute.DEVICE_STATUS_FIREBASE.ROUTE, }) export class DeviceStatusFirebaseController { constructor( @@ -16,6 +17,13 @@ export class DeviceStatusFirebaseController { @ApiBearerAuth() @Post(':deviceTuyaUuid') + @ApiOperation({ + summary: + ControllerRoute.DEVICE_STATUS_FIREBASE.ACTIONS.ADD_DEVICE_STATUS_SUMMARY, + description: + ControllerRoute.DEVICE_STATUS_FIREBASE.ACTIONS + .ADD_DEVICE_STATUS_DESCRIPTION, + }) async addDeviceStatus( @Param('deviceTuyaUuid') deviceTuyaUuid: string, ): Promise { From f1da846bb5e9311e1829419eb1e4642ce82c63be Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:50:02 -0600 Subject: [PATCH 081/109] Enhance Swagger Documentation in UserAuthController --- src/auth/controllers/user-auth.controller.ts | 41 +++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/src/auth/controllers/user-auth.controller.ts b/src/auth/controllers/user-auth.controller.ts index e7074da..616f4ad 100644 --- a/src/auth/controllers/user-auth.controller.ts +++ b/src/auth/controllers/user-auth.controller.ts @@ -10,7 +10,7 @@ import { } from '@nestjs/common'; import { UserAuthService } from '../services/user-auth.service'; import { UserSignUpDto } from '../dtos/user-auth.dto'; -import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { ResponseMessage } from '../../../libs/common/src/response/response.decorator'; import { UserLoginDto } from '../dtos/user-login.dto'; import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos'; @@ -18,17 +18,22 @@ import { RefreshTokenGuard } from '@app/common/guards/jwt-refresh.auth.guard'; import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { OtpType } from '@app/common/constants/otp-type.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'authentication', + path: ControllerRoute.AUTHENTICATION.ROUTE, }) -@ApiTags('Auth') +@ApiTags('Authentication Module') export class UserAuthController { constructor(private readonly userAuthService: UserAuthService) {} @ResponseMessage('User Registered Successfully') @Post('user/signup') + @ApiOperation({ + summary: ControllerRoute.AUTHENTICATION.ACTIONS.SIGN_UP_SUMMARY, + description: ControllerRoute.AUTHENTICATION.ACTIONS.SIGN_UP_DESCRIPTION, + }) async signUp(@Body() userSignUpDto: UserSignUpDto) { const signupUser = await this.userAuthService.signUp(userSignUpDto); return { @@ -41,8 +46,12 @@ export class UserAuthController { }; } - @ResponseMessage('user logged in successfully') + @ResponseMessage('User Logged in Successfully') @Post('user/login') + @ApiOperation({ + summary: ControllerRoute.AUTHENTICATION.ACTIONS.LOGIN_SUMMARY, + description: ControllerRoute.AUTHENTICATION.ACTIONS.LOGIN_DESCRIPTION, + }) async userLogin(@Body() data: UserLoginDto) { const accessToken = await this.userAuthService.userLogin(data); return { @@ -53,6 +62,10 @@ export class UserAuthController { } @Post('user/send-otp') + @ApiOperation({ + summary: ControllerRoute.AUTHENTICATION.ACTIONS.SEND_OTP_SUMMARY, + description: ControllerRoute.AUTHENTICATION.ACTIONS.SEND_OTP_DESCRIPTION, + }) async sendOtp(@Body() otpDto: UserOtpDto) { const otpCode = await this.userAuthService.generateOTP(otpDto); return { @@ -60,11 +73,15 @@ export class UserAuthController { data: { ...otpCode, }, - message: 'Otp Send Successfully', + message: 'Otp Sent Successfully', }; } @Post('user/verify-otp') + @ApiOperation({ + summary: ControllerRoute.AUTHENTICATION.ACTIONS.VERIFY_OTP_SUMMARY, + description: ControllerRoute.AUTHENTICATION.ACTIONS.VERIFY_OTP_DESCRIPTION, + }) async verifyOtp(@Body() verifyOtpDto: VerifyOtpDto) { await this.userAuthService.verifyOTP(verifyOtpDto); return { @@ -75,6 +92,11 @@ export class UserAuthController { } @Post('user/forget-password') + @ApiOperation({ + summary: ControllerRoute.AUTHENTICATION.ACTIONS.FORGET_PASSWORD_SUMMARY, + description: + ControllerRoute.AUTHENTICATION.ACTIONS.FORGET_PASSWORD_DESCRIPTION, + }) async forgetPassword(@Body() forgetPasswordDto: ForgetPasswordDto) { const otpResult = await this.userAuthService.verifyOTP( { @@ -102,6 +124,10 @@ export class UserAuthController { @ApiBearerAuth() @UseGuards(SuperAdminRoleGuard) @Get('user') + @ApiOperation({ + summary: ControllerRoute.AUTHENTICATION.ACTIONS.USER_LIST_SUMMARY, + description: ControllerRoute.AUTHENTICATION.ACTIONS.USER_LIST_DESCRIPTION, + }) async userList() { const userList = await this.userAuthService.userList(); return { @@ -114,6 +140,11 @@ export class UserAuthController { @ApiBearerAuth() @UseGuards(RefreshTokenGuard) @Get('refresh-token') + @ApiOperation({ + summary: ControllerRoute.AUTHENTICATION.ACTIONS.REFRESH_TOKEN_SUMMARY, + description: + ControllerRoute.AUTHENTICATION.ACTIONS.REFRESH_TOKEN_DESCRIPTION, + }) async refreshToken(@Req() req) { const refreshToken = await this.userAuthService.refreshToken( req.user.uuid, From c1b8e63c5e74fd8f95235a2937704745fb9007bd Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:50:13 -0600 Subject: [PATCH 082/109] Add `ApiOperation` decorators to AutomationController endpoints --- .../controllers/automation.controller.ts | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/automation/controllers/automation.controller.ts b/src/automation/controllers/automation.controller.ts index 49a5012..8d9d1ab 100644 --- a/src/automation/controllers/automation.controller.ts +++ b/src/automation/controllers/automation.controller.ts @@ -10,7 +10,7 @@ import { Put, UseGuards, } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { AddAutomationDto, UpdateAutomationDto, @@ -19,11 +19,12 @@ import { import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { AutomationParamDto, SpaceParamDto } from '../dtos'; +import { ControllerRoute } from '@app/common/constants/controller-route'; @ApiTags('Automation Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'automation', + path: ControllerRoute.AUTOMATION.ROUTE, }) export class AutomationController { constructor(private readonly automationService: AutomationService) {} @@ -31,6 +32,10 @@ export class AutomationController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post() + @ApiOperation({ + summary: ControllerRoute.AUTOMATION.ACTIONS.ADD_AUTOMATION_SUMMARY, + description: ControllerRoute.AUTOMATION.ACTIONS.ADD_AUTOMATION_DESCRIPTION, + }) async addAutomation(@Body() addAutomationDto: AddAutomationDto) { const automation = await this.automationService.addAutomation(addAutomationDto); @@ -45,6 +50,11 @@ export class AutomationController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':spaceUuid') + @ApiOperation({ + summary: ControllerRoute.AUTOMATION.ACTIONS.GET_AUTOMATION_BY_SPACE_SUMMARY, + description: + ControllerRoute.AUTOMATION.ACTIONS.GET_AUTOMATION_BY_SPACE_DESCRIPTION, + }) async getAutomationBySpace(@Param() param: SpaceParamDto) { const automation = await this.automationService.getAutomationBySpace( param.spaceUuid, @@ -55,6 +65,11 @@ export class AutomationController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('details/:automationUuid') + @ApiOperation({ + summary: ControllerRoute.AUTOMATION.ACTIONS.GET_AUTOMATION_DETAILS_SUMMARY, + description: + ControllerRoute.AUTOMATION.ACTIONS.GET_AUTOMATION_DETAILS_DESCRIPTION, + }) async getAutomationDetails(@Param() param: AutomationParamDto) { const automation = await this.automationService.getAutomationDetails( param.automationUuid, @@ -65,6 +80,11 @@ export class AutomationController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Delete(':automationUuid') + @ApiOperation({ + summary: ControllerRoute.AUTOMATION.ACTIONS.DELETE_AUTOMATION_SUMMARY, + description: + ControllerRoute.AUTOMATION.ACTIONS.DELETE_AUTOMATION_DESCRIPTION, + }) async deleteAutomation(@Param() param: AutomationParamDto) { await this.automationService.deleteAutomation(param); return { @@ -76,6 +96,11 @@ export class AutomationController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put(':automationUuid') + @ApiOperation({ + summary: ControllerRoute.AUTOMATION.ACTIONS.UPDATE_AUTOMATION_SUMMARY, + description: + ControllerRoute.AUTOMATION.ACTIONS.UPDATE_AUTOMATION_DESCRIPTION, + }) async updateAutomation( @Body() updateAutomationDto: UpdateAutomationDto, @Param() param: AutomationParamDto, @@ -95,6 +120,12 @@ export class AutomationController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('status/:automationUuid') + @ApiOperation({ + summary: + ControllerRoute.AUTOMATION.ACTIONS.UPDATE_AUTOMATION_STATUS_SUMMARY, + description: + ControllerRoute.AUTOMATION.ACTIONS.UPDATE_AUTOMATION_STATUS_DESCRIPTION, + }) async updateAutomationStatus( @Body() updateAutomationStatusDto: UpdateAutomationStatusDto, @Param() param: AutomationParamDto, From c2374e7155d8370c64fb31c02c6edf85af41405d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:50:22 -0600 Subject: [PATCH 083/109] Add `@ApiOperation` decorators to DeviceController endpoints --- src/device/controllers/device.controller.ts | 91 ++++++++++++++++++++- 1 file changed, 89 insertions(+), 2 deletions(-) diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 28392aa..905212f 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -11,7 +11,7 @@ import { Req, Put, } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { AddDeviceDto, AddSceneToFourSceneDeviceDto, @@ -32,17 +32,22 @@ import { CheckDeviceGuard } from 'src/guards/device.guard'; import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { CheckFourAndSixSceneDeviceTypeGuard } from 'src/guards/scene.device.type.guard'; +import { ControllerRoute } from '@app/common/constants/controller-route'; @ApiTags('Device Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'device', + path: ControllerRoute.DEVICE.ROUTE, }) export class DeviceController { constructor(private readonly deviceService: DeviceService) {} @ApiBearerAuth() @UseGuards(SuperAdminRoleGuard, CheckDeviceGuard) @Post() + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.ADD_DEVICE_TO_USER_SUMMARY, + description: ControllerRoute.DEVICE.ACTIONS.ADD_DEVICE_TO_USER_DESCRIPTION, + }) async addDeviceUser(@Body() addDeviceDto: AddDeviceDto) { const device = await this.deviceService.addDeviceUser(addDeviceDto); @@ -56,6 +61,10 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('user/:userUuid') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_BY_USER_SUMMARY, + description: ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_BY_USER_DESCRIPTION, + }) async getDevicesByUser(@Param('userUuid') userUuid: string) { return await this.deviceService.getDevicesByUser(userUuid); } @@ -63,12 +72,22 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('space/:spaceUuid') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_BY_SPACE_UUID_SUMMARY, + description: + ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_BY_SPACE_UUID_DESCRIPTION, + }) async getDevicesByUnitId(@Param('spaceUuid') spaceUuid: string) { return await this.deviceService.getDevicesBySpaceUuid(spaceUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard, CheckRoomGuard) @Put('space') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_IN_ROOM_SUMMARY, + description: + ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_IN_ROOM_DESCRIPTION, + }) async updateDeviceInRoom( @Body() updateDeviceInSpaceDto: UpdateDeviceInSpaceDto, ) { @@ -87,6 +106,10 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':deviceUuid') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_DETAILS_SUMMARY, + description: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_DETAILS_DESCRIPTION, + }) async getDeviceDetailsByDeviceId( @Param('deviceUuid') deviceUuid: string, @Req() req: any, @@ -100,6 +123,10 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put(':deviceUuid') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_SUMMARY, + description: ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_DESCRIPTION, + }) async updateDevice( @Param('deviceUuid') deviceUuid: string, @Body() updateDeviceDto: UpdateDeviceDto, @@ -120,6 +147,11 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':deviceUuid/functions') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_INSTRUCTION_SUMMARY, + description: + ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_INSTRUCTION_DESCRIPTION, + }) async getDeviceInstructionByDeviceId( @Param('deviceUuid') deviceUuid: string, ) { @@ -128,6 +160,10 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':deviceUuid/functions/status') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_STATUS_SUMMARY, + description: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_STATUS_DESCRIPTION, + }) async getDevicesInstructionStatus(@Param('deviceUuid') deviceUuid: string) { return await this.deviceService.getDevicesInstructionStatus(deviceUuid); } @@ -135,6 +171,10 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post(':deviceUuid/control') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.CONTROL_DEVICE_SUMMARY, + description: ControllerRoute.DEVICE.ACTIONS.CONTROL_DEVICE_DESCRIPTION, + }) async controlDevice( @Body() controlDeviceDto: ControlDeviceDto, @Param('deviceUuid') deviceUuid: string, @@ -144,6 +184,11 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post(':deviceUuid/firmware/:firmwareVersion') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_FIRMWARE_SUMMARY, + description: + ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_FIRMWARE_DESCRIPTION, + }) async updateDeviceFirmware( @Param('deviceUuid') deviceUuid: string, @Param('firmwareVersion') firmwareVersion: number, @@ -156,12 +201,21 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('gateway/:gatewayUuid/devices') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_IN_GATEWAY_SUMMARY, + description: + ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_IN_GATEWAY_DESCRIPTION, + }) async getDevicesInGateway(@Param('gatewayUuid') gatewayUuid: string) { return await this.deviceService.getDevicesInGateway(gatewayUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get() + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.GET_ALL_DEVICES_SUMMARY, + description: ControllerRoute.DEVICE.ACTIONS.GET_ALL_DEVICES_DESCRIPTION, + }) async getAllDevices() { return await this.deviceService.getAllDevices(); } @@ -169,6 +223,10 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('report-logs/:deviceUuid') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_LOGS_SUMMARY, + description: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_LOGS_DESCRIPTION, + }) async getBuildingChildByUuid( @Param('deviceUuid') deviceUuid: string, @Query() query: GetDeviceLogsDto, @@ -178,6 +236,11 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('control/batch') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.BATCH_CONTROL_DEVICES_SUMMARY, + description: + ControllerRoute.DEVICE.ACTIONS.BATCH_CONTROL_DEVICES_DESCRIPTION, + }) async batchControlDevices( @Body() batchControlDevicesDto: BatchControlDevicesDto, ) { @@ -186,6 +249,11 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('status/batch') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.BATCH_STATUS_DEVICES_SUMMARY, + description: + ControllerRoute.DEVICE.ACTIONS.BATCH_STATUS_DEVICES_DESCRIPTION, + }) async batchStatusDevices( @Query() batchStatusDevicesDto: BatchStatusDevicesDto, ) { @@ -194,6 +262,11 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('factory/reset/:deviceUuid') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.BATCH_FACTORY_RESET_DEVICES_SUMMARY, + description: + ControllerRoute.DEVICE.ACTIONS.BATCH_FACTORY_RESET_DEVICES_DESCRIPTION, + }) async batchFactoryResetDevices( @Body() batchFactoryResetDevicesDto: BatchFactoryResetDevicesDto, ) { @@ -204,6 +277,11 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':powerClampUuid/power-clamp/status') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.GET_POWER_CLAMP_STATUS_SUMMARY, + description: + ControllerRoute.DEVICE.ACTIONS.GET_POWER_CLAMP_STATUS_DESCRIPTION, + }) async getPowerClampInstructionStatus( @Param('powerClampUuid') powerClampUuid: string, ) { @@ -213,6 +291,10 @@ export class DeviceController { } @ApiBearerAuth() @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.ADD_SCENE_TO_DEVICE_SUMMARY, + description: ControllerRoute.DEVICE.ACTIONS.ADD_SCENE_TO_DEVICE_DESCRIPTION, + }) @Post(':deviceUuid/scenes') async addSceneToSceneDevice( @Param('deviceUuid') deviceUuid: string, @@ -233,6 +315,11 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) @Get(':deviceUuid/scenes') + @ApiOperation({ + summary: ControllerRoute.DEVICE.ACTIONS.GET_SCENES_BY_DEVICE_SUMMARY, + description: + ControllerRoute.DEVICE.ACTIONS.GET_SCENES_BY_DEVICE_DESCRIPTION, + }) async getScenesBySceneDevice( @Param('deviceUuid') deviceUuid: string, @Query() getSceneFourSceneDeviceDto: GetSceneFourSceneDeviceDto, From 0414e106d13199a754fce6924dfaacfa491db975 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:50:31 -0600 Subject: [PATCH 084/109] Add API operation descriptions to device messages controller --- .../controllers/device-messages.controller.ts | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/device-messages/controllers/device-messages.controller.ts b/src/device-messages/controllers/device-messages.controller.ts index 6f942b4..d41d477 100644 --- a/src/device-messages/controllers/device-messages.controller.ts +++ b/src/device-messages/controllers/device-messages.controller.ts @@ -8,17 +8,18 @@ import { Post, UseGuards, } from '@nestjs/common'; -import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { DeviceMessagesSubscriptionService } from '../services/device-messages.service'; import { DeviceMessagesAddDto } from '../dtos/device-messages.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; @ApiTags('Device Messages Status Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'device-messages/subscription', + path: ControllerRoute.DEVICE_MESSAGES_SUBSCRIPTION.ROUTE, }) export class DeviceMessagesSubscriptionController { constructor( @@ -28,6 +29,14 @@ export class DeviceMessagesSubscriptionController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post() + @ApiOperation({ + summary: + ControllerRoute.DEVICE_MESSAGES_SUBSCRIPTION.ACTIONS + .ADD_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY, + description: + ControllerRoute.DEVICE_MESSAGES_SUBSCRIPTION.ACTIONS + .ADD_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION, + }) async addDeviceMessagesSubscription( @Body() deviceMessagesAddDto: DeviceMessagesAddDto, ) { @@ -45,6 +54,14 @@ export class DeviceMessagesSubscriptionController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':deviceUuid/user/:userUuid') + @ApiOperation({ + summary: + ControllerRoute.DEVICE_MESSAGES_SUBSCRIPTION.ACTIONS + .GET_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY, + description: + ControllerRoute.DEVICE_MESSAGES_SUBSCRIPTION.ACTIONS + .GET_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION, + }) async getDeviceMessagesSubscription( @Param('deviceUuid') deviceUuid: string, @Param('userUuid') userUuid: string, @@ -60,9 +77,18 @@ export class DeviceMessagesSubscriptionController { data: deviceDetails, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Delete() + @ApiOperation({ + summary: + ControllerRoute.DEVICE_MESSAGES_SUBSCRIPTION.ACTIONS + .DELETE_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY, + description: + ControllerRoute.DEVICE_MESSAGES_SUBSCRIPTION.ACTIONS + .DELETE_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION, + }) async deleteDeviceMessagesSubscription( @Body() deviceMessagesAddDto: DeviceMessagesAddDto, ) { From 059d248d44e3c780bfcbf7a04769f69cf9b0cd06 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:50:42 -0600 Subject: [PATCH 085/109] Add API operation descriptions to DoorLockController --- .../controllers/door.lock.controller.ts | 93 +++++++++++++++++-- 1 file changed, 83 insertions(+), 10 deletions(-) diff --git a/src/door-lock/controllers/door.lock.controller.ts b/src/door-lock/controllers/door.lock.controller.ts index 3e6f459..a965ecb 100644 --- a/src/door-lock/controllers/door.lock.controller.ts +++ b/src/door-lock/controllers/door.lock.controller.ts @@ -10,23 +10,31 @@ import { UseGuards, Put, } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { AddDoorLockOnlineDto } from '../dtos/add.online-temp.dto'; import { AddDoorLockOfflineTempMultipleTimeDto } from '../dtos/add.offline-temp.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { UpdateDoorLockOfflineTempDto } from '../dtos/update.offline-temp.dto'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; @ApiTags('Door Lock Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'door-lock', + path: ControllerRoute.DOOR_LOCK.ROUTE, }) export class DoorLockController { constructor(private readonly doorLockService: DoorLockService) {} @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('temporary-password/online/:doorLockUuid') + @ApiOperation({ + summary: + ControllerRoute.DOOR_LOCK.ACTIONS.ADD_ONLINE_TEMPORARY_PASSWORD_SUMMARY, + description: + ControllerRoute.DOOR_LOCK.ACTIONS + .ADD_ONLINE_TEMPORARY_PASSWORD_DESCRIPTION, + }) async addOnlineTemporaryPassword( @Body() addDoorLockDto: AddDoorLockOnlineDto, @Param('doorLockUuid') doorLockUuid: string, @@ -40,15 +48,24 @@ export class DoorLockController { return { statusCode: HttpStatus.CREATED, success: true, - message: 'online temporary password added successfully', + message: 'Online temporary password added successfully', data: { id: temporaryPassword.id, }, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('temporary-password/offline/one-time/:doorLockUuid') + @ApiOperation({ + summary: + ControllerRoute.DOOR_LOCK.ACTIONS + .ADD_OFFLINE_ONE_TIME_TEMPORARY_PASSWORD_SUMMARY, + description: + ControllerRoute.DOOR_LOCK.ACTIONS + .ADD_OFFLINE_ONE_TIME_TEMPORARY_PASSWORD_DESCRIPTION, + }) async addOfflineOneTimeTemporaryPassword( @Param('doorLockUuid') doorLockUuid: string, ) { @@ -60,13 +77,22 @@ export class DoorLockController { return { statusCode: HttpStatus.CREATED, success: true, - message: 'offline temporary password added successfully', + message: 'Offline one-time temporary password added successfully', data: temporaryPassword, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('temporary-password/offline/multiple-time/:doorLockUuid') + @ApiOperation({ + summary: + ControllerRoute.DOOR_LOCK.ACTIONS + .ADD_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORD_SUMMARY, + description: + ControllerRoute.DOOR_LOCK.ACTIONS + .ADD_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORD_DESCRIPTION, + }) async addOfflineMultipleTimeTemporaryPassword( @Body() addDoorLockOfflineTempMultipleTimeDto: AddDoorLockOfflineTempMultipleTimeDto, @@ -81,13 +107,21 @@ export class DoorLockController { return { statusCode: HttpStatus.CREATED, success: true, - message: 'offline temporary password added successfully', + message: 'Offline multiple-time temporary password added successfully', data: temporaryPassword, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('temporary-password/online/:doorLockUuid') + @ApiOperation({ + summary: + ControllerRoute.DOOR_LOCK.ACTIONS.GET_ONLINE_TEMPORARY_PASSWORDS_SUMMARY, + description: + ControllerRoute.DOOR_LOCK.ACTIONS + .GET_ONLINE_TEMPORARY_PASSWORDS_DESCRIPTION, + }) async getOnlineTemporaryPasswords( @Param('doorLockUuid') doorLockUuid: string, ) { @@ -95,9 +129,18 @@ export class DoorLockController { doorLockUuid, ); } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Delete('temporary-password/online/:doorLockUuid/:passwordId') + @ApiOperation({ + summary: + ControllerRoute.DOOR_LOCK.ACTIONS + .DELETE_ONLINE_TEMPORARY_PASSWORD_SUMMARY, + description: + ControllerRoute.DOOR_LOCK.ACTIONS + .DELETE_ONLINE_TEMPORARY_PASSWORD_DESCRIPTION, + }) async deleteDoorLockPassword( @Param('doorLockUuid') doorLockUuid: string, @Param('passwordId') passwordId: string, @@ -105,12 +148,21 @@ export class DoorLockController { await this.doorLockService.deleteDoorLockPassword(doorLockUuid, passwordId); return { statusCode: HttpStatus.OK, - message: 'Temporary Password deleted Successfully', + message: 'Temporary password deleted successfully', }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('temporary-password/offline/one-time/:doorLockUuid') + @ApiOperation({ + summary: + ControllerRoute.DOOR_LOCK.ACTIONS + .GET_OFFLINE_ONE_TIME_TEMPORARY_PASSWORDS_SUMMARY, + description: + ControllerRoute.DOOR_LOCK.ACTIONS + .GET_OFFLINE_ONE_TIME_TEMPORARY_PASSWORDS_DESCRIPTION, + }) async getOfflineOneTimeTemporaryPasswords( @Param('doorLockUuid') doorLockUuid: string, ) { @@ -118,9 +170,18 @@ export class DoorLockController { doorLockUuid, ); } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('temporary-password/offline/multiple-time/:doorLockUuid') + @ApiOperation({ + summary: + ControllerRoute.DOOR_LOCK.ACTIONS + .GET_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORDS_SUMMARY, + description: + ControllerRoute.DOOR_LOCK.ACTIONS + .GET_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORDS_DESCRIPTION, + }) async getOfflineMultipleTimeTemporaryPasswords( @Param('doorLockUuid') doorLockUuid: string, ) { @@ -132,9 +193,16 @@ export class DoorLockController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('temporary-password/:doorLockUuid/offline/:passwordId') + @ApiOperation({ + summary: + ControllerRoute.DOOR_LOCK.ACTIONS + .UPDATE_OFFLINE_TEMPORARY_PASSWORD_SUMMARY, + description: + ControllerRoute.DOOR_LOCK.ACTIONS + .UPDATE_OFFLINE_TEMPORARY_PASSWORD_DESCRIPTION, + }) async updateOfflineTemporaryPassword( - @Body() - updateDoorLockOfflineTempDto: UpdateDoorLockOfflineTempDto, + @Body() updateDoorLockOfflineTempDto: UpdateDoorLockOfflineTempDto, @Param('doorLockUuid') doorLockUuid: string, @Param('passwordId') passwordId: string, ) { @@ -148,20 +216,25 @@ export class DoorLockController { return { statusCode: HttpStatus.CREATED, success: true, - message: 'offline temporary password updated successfully', + message: 'Offline temporary password updated successfully', data: temporaryPassword, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('open/:doorLockUuid') + @ApiOperation({ + summary: ControllerRoute.DOOR_LOCK.ACTIONS.OPEN_DOOR_LOCK_SUMMARY, + description: ControllerRoute.DOOR_LOCK.ACTIONS.OPEN_DOOR_LOCK_DESCRIPTION, + }) async openDoorLock(@Param('doorLockUuid') doorLockUuid: string) { await this.doorLockService.openDoorLock(doorLockUuid); return { statusCode: HttpStatus.CREATED, success: true, - message: 'door lock opened successfully', + message: 'Door lock opened successfully', }; } } From 19c7233836fae131a5274437c6a530e9d4167203 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:50:52 -0600 Subject: [PATCH 086/109] Refactor GroupController to use static route constants and add API operation descriptions --- src/group/controllers/group.controller.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/group/controllers/group.controller.ts b/src/group/controllers/group.controller.ts index 3098da0..a5d7cd7 100644 --- a/src/group/controllers/group.controller.ts +++ b/src/group/controllers/group.controller.ts @@ -1,13 +1,14 @@ import { GroupService } from '../services/group.service'; import { Controller, Get, UseGuards, Param, Req } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; // Assuming this is where the routes are defined @ApiTags('Group Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'group', + path: ControllerRoute.GROUP.ROUTE, // use the static route constant }) export class GroupController { constructor(private readonly groupService: GroupService) {} @@ -15,6 +16,11 @@ export class GroupController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':spaceUuid') + @ApiOperation({ + summary: ControllerRoute.GROUP.ACTIONS.GET_GROUPS_BY_SPACE_UUID_SUMMARY, + description: + ControllerRoute.GROUP.ACTIONS.GET_GROUPS_BY_SPACE_UUID_DESCRIPTION, + }) async getGroupsBySpaceUuid(@Param('spaceUuid') spaceUuid: string) { return await this.groupService.getGroupsBySpaceUuid(spaceUuid); } @@ -22,6 +28,12 @@ export class GroupController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':spaceUuid/devices/:groupName') + @ApiOperation({ + summary: + ControllerRoute.GROUP.ACTIONS.GET_UNIT_DEVICES_BY_GROUP_NAME_SUMMARY, + description: + ControllerRoute.GROUP.ACTIONS.GET_UNIT_DEVICES_BY_GROUP_NAME_DESCRIPTION, + }) async getUnitDevicesByGroupName( @Param('spaceUuid') spaceUuid: string, @Param('groupName') groupName: string, From 6bb0feec6147ec0cb1c269420aa9c479e11b6c39 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:51:00 -0600 Subject: [PATCH 087/109] Update RoleController to use static route constants and add API operation descriptions --- src/role/controllers/role.controller.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/role/controllers/role.controller.ts b/src/role/controllers/role.controller.ts index f08adb4..a53cd1c 100644 --- a/src/role/controllers/role.controller.ts +++ b/src/role/controllers/role.controller.ts @@ -6,22 +6,28 @@ import { Post, UseGuards, } from '@nestjs/common'; -import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { RoleService } from '../services/role.service'; import { AddUserRoleDto } from '../dtos'; import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; // Assuming this is where the routes are defined @ApiTags('Role Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'role', + path: ControllerRoute.ROLE.ROUTE, // use the static route constant }) export class RoleController { constructor(private readonly roleService: RoleService) {} + @ApiBearerAuth() @UseGuards(SuperAdminRoleGuard) @Get('types') + @ApiOperation({ + summary: ControllerRoute.ROLE.ACTIONS.FETCH_ROLE_TYPES_SUMMARY, + description: ControllerRoute.ROLE.ACTIONS.FETCH_ROLE_TYPES_DESCRIPTION, + }) async fetchRoleTypes() { const roleTypes = await this.roleService.fetchRoleTypes(); return { @@ -30,9 +36,14 @@ export class RoleController { data: roleTypes, }; } + @ApiBearerAuth() @UseGuards(SuperAdminRoleGuard) @Post() + @ApiOperation({ + summary: ControllerRoute.ROLE.ACTIONS.ADD_USER_ROLE_SUMMARY, + description: ControllerRoute.ROLE.ACTIONS.ADD_USER_ROLE_DESCRIPTION, + }) async addUserRoleType(@Body() addUserRoleDto: AddUserRoleDto) { await this.roleService.addUserRoleType(addUserRoleDto); return { From a5ebecb952ad98ed57d941b36cce9becd8e2946a Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:51:10 -0600 Subject: [PATCH 088/109] Add `ApiOperation` decorators and update route paths in `ScheduleController` --- .../controllers/schedule.controller.ts | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/src/schedule/controllers/schedule.controller.ts b/src/schedule/controllers/schedule.controller.ts index 82676bf..d4c7a91 100644 --- a/src/schedule/controllers/schedule.controller.ts +++ b/src/schedule/controllers/schedule.controller.ts @@ -11,7 +11,7 @@ import { Delete, Query, } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { AddScheduleDto, EnableScheduleDto, @@ -21,17 +21,24 @@ import { import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; @ApiTags('Schedule Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'schedule', + path: ControllerRoute.SCHEDULE.ROUTE, }) export class ScheduleController { constructor(private readonly scheduleService: ScheduleService) {} + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post(':deviceUuid') + @ApiOperation({ + summary: ControllerRoute.SCHEDULE.ACTIONS.ADD_DEVICE_SCHEDULE_SUMMARY, + description: + ControllerRoute.SCHEDULE.ACTIONS.ADD_DEVICE_SCHEDULE_DESCRIPTION, + }) async addDeviceSchedule( @Param('deviceUuid') deviceUuid: string, @Body() addScheduleDto: AddScheduleDto, @@ -44,13 +51,21 @@ export class ScheduleController { return { statusCode: HttpStatus.CREATED, success: true, - message: 'schedule added successfully', + message: 'Schedule added successfully', data: schedule, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':deviceUuid') + @ApiOperation({ + summary: + ControllerRoute.SCHEDULE.ACTIONS.GET_DEVICE_SCHEDULE_BY_CATEGORY_SUMMARY, + description: + ControllerRoute.SCHEDULE.ACTIONS + .GET_DEVICE_SCHEDULE_BY_CATEGORY_DESCRIPTION, + }) async getDeviceScheduleByCategory( @Param('deviceUuid') deviceUuid: string, @Query() query: GetScheduleDeviceDto, @@ -60,9 +75,15 @@ export class ScheduleController { query.category, ); } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Delete(':deviceUuid/:scheduleId') + @ApiOperation({ + summary: ControllerRoute.SCHEDULE.ACTIONS.DELETE_DEVICE_SCHEDULE_SUMMARY, + description: + ControllerRoute.SCHEDULE.ACTIONS.DELETE_DEVICE_SCHEDULE_DESCRIPTION, + }) async deleteDeviceSchedule( @Param('deviceUuid') deviceUuid: string, @Param('scheduleId') scheduleId: string, @@ -71,12 +92,18 @@ export class ScheduleController { return { statusCode: HttpStatus.CREATED, success: true, - message: 'schedule deleted successfully', + message: 'Schedule deleted successfully', }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('enable/:deviceUuid') + @ApiOperation({ + summary: ControllerRoute.SCHEDULE.ACTIONS.ENABLE_DEVICE_SCHEDULE_SUMMARY, + description: + ControllerRoute.SCHEDULE.ACTIONS.ENABLE_DEVICE_SCHEDULE_DESCRIPTION, + }) async enableDeviceSchedule( @Param('deviceUuid') deviceUuid: string, @Body() enableScheduleDto: EnableScheduleDto, @@ -88,12 +115,18 @@ export class ScheduleController { return { statusCode: HttpStatus.CREATED, success: true, - message: 'schedule updated successfully', + message: 'Schedule updated successfully', }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put(':deviceUuid') + @ApiOperation({ + summary: ControllerRoute.SCHEDULE.ACTIONS.UPDATE_DEVICE_SCHEDULE_SUMMARY, + description: + ControllerRoute.SCHEDULE.ACTIONS.UPDATE_DEVICE_SCHEDULE_DESCRIPTION, + }) async updateDeviceSchedule( @Param('deviceUuid') deviceUuid: string, @Body() updateScheduleDto: UpdateScheduleDto, @@ -106,7 +139,7 @@ export class ScheduleController { return { statusCode: HttpStatus.CREATED, success: true, - message: 'schedule updated successfully', + message: 'Schedule updated successfully', data: schedule, }; } From e47cb82e337dc5caeb4e04894d4215a80581956f Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:51:17 -0600 Subject: [PATCH 089/109] Update TimeZoneController with ApiOperation and dynamic route --- src/timezone/controllers/timezone.controller.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/timezone/controllers/timezone.controller.ts b/src/timezone/controllers/timezone.controller.ts index bc46381..756bdbb 100644 --- a/src/timezone/controllers/timezone.controller.ts +++ b/src/timezone/controllers/timezone.controller.ts @@ -1,13 +1,14 @@ import { Controller, Get, UseGuards } from '@nestjs/common'; import { TimeZoneService } from '../services/timezone.service'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; @ApiTags('TimeZone Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'timezone', + path: ControllerRoute.TIMEZONE.ROUTE, }) export class TimeZoneController { constructor(private readonly timeZoneService: TimeZoneService) {} @@ -15,6 +16,11 @@ export class TimeZoneController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get() + @ApiOperation({ + summary: ControllerRoute.TIMEZONE.ACTIONS.GET_ALL_TIME_ZONES_SUMMARY, + description: + ControllerRoute.TIMEZONE.ACTIONS.GET_ALL_TIME_ZONES_DESCRIPTION, + }) async getAllTimeZones() { return await this.timeZoneService.getAllTimeZones(); } From 977cc6fb070027e121ee5a8313d6caf61bc30f98 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:51:30 -0600 Subject: [PATCH 090/109] Add API operation descriptions to user device permission controller --- .../user-device-permission.controller.ts | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/user-device-permission/controllers/user-device-permission.controller.ts b/src/user-device-permission/controllers/user-device-permission.controller.ts index 5ff5284..7d33b7f 100644 --- a/src/user-device-permission/controllers/user-device-permission.controller.ts +++ b/src/user-device-permission/controllers/user-device-permission.controller.ts @@ -9,17 +9,18 @@ import { Put, UseGuards, } from '@nestjs/common'; -import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiTags, ApiOperation } from '@nestjs/swagger'; import { UserDevicePermissionService } from '../services/user-device-permission.service'; import { UserDevicePermissionAddDto } from '../dtos/user-device-permission.add.dto'; import { UserDevicePermissionEditDto } from '../dtos/user-device-permission.edit.dto'; import { AdminRoleGuard } from 'src/guards/admin.role.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; @ApiTags('Device Permission Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'device-permission', + path: ControllerRoute.DEVICE_PERMISSION.ROUTE, }) export class UserDevicePermissionController { constructor( @@ -29,6 +30,11 @@ export class UserDevicePermissionController { @ApiBearerAuth() @UseGuards(AdminRoleGuard) @Post() + @ApiOperation({ + summary: ControllerRoute.DEVICE_PERMISSION.ACTIONS.ADD_PERMISSION_SUMMARY, + description: + ControllerRoute.DEVICE_PERMISSION.ACTIONS.ADD_PERMISSION_DESCRIPTION, + }) async addDevicePermission( @Body() userDevicePermissionDto: UserDevicePermissionAddDto, ) { @@ -45,6 +51,11 @@ export class UserDevicePermissionController { @ApiBearerAuth() @UseGuards(AdminRoleGuard) @Put(':devicePermissionUuid') + @ApiOperation({ + summary: ControllerRoute.DEVICE_PERMISSION.ACTIONS.EDIT_PERMISSION_SUMMARY, + description: + ControllerRoute.DEVICE_PERMISSION.ACTIONS.EDIT_PERMISSION_DESCRIPTION, + }) async editDevicePermission( @Param('devicePermissionUuid') devicePermissionUuid: string, @Body() userDevicePermissionEditDto: UserDevicePermissionEditDto, @@ -62,6 +73,11 @@ export class UserDevicePermissionController { @ApiBearerAuth() @UseGuards(AdminRoleGuard) @Get(':deviceUuid') + @ApiOperation({ + summary: ControllerRoute.DEVICE_PERMISSION.ACTIONS.FETCH_PERMISSION_SUMMARY, + description: + ControllerRoute.DEVICE_PERMISSION.ACTIONS.FETCH_PERMISSION_DESCRIPTION, + }) async fetchDevicePermission(@Param('deviceUuid') deviceUuid: string) { const deviceDetails = await this.userDevicePermissionService.fetchUserPermission(deviceUuid); @@ -71,9 +87,16 @@ export class UserDevicePermissionController { data: deviceDetails, }; } + @ApiBearerAuth() @UseGuards(AdminRoleGuard) @Delete(':devicePermissionUuid') + @ApiOperation({ + summary: + ControllerRoute.DEVICE_PERMISSION.ACTIONS.DELETE_PERMISSION_SUMMARY, + description: + ControllerRoute.DEVICE_PERMISSION.ACTIONS.DELETE_PERMISSION_DESCRIPTION, + }) async deleteDevicePermission( @Param('devicePermissionUuid') devicePermissionUuid: string, ) { From b904c1a338bb9e4b4b7bc306148c69884315787d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:51:37 -0600 Subject: [PATCH 091/109] Add `ApiOperation` decorators and update controller path --- .../user-notification.controller.ts | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/user-notification/controllers/user-notification.controller.ts b/src/user-notification/controllers/user-notification.controller.ts index 8fe8586..8e57122 100644 --- a/src/user-notification/controllers/user-notification.controller.ts +++ b/src/user-notification/controllers/user-notification.controller.ts @@ -8,7 +8,7 @@ import { Put, UseGuards, } from '@nestjs/common'; -import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiTags, ApiOperation } from '@nestjs/swagger'; import { UserNotificationService } from '../services/user-notification.service'; import { UserNotificationAddDto, @@ -17,11 +17,12 @@ import { import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; @ApiTags('User Notification Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'user-notification/subscription', + path: ControllerRoute.USER_NOTIFICATION.ROUTE, }) export class UserNotificationController { constructor( @@ -31,6 +32,11 @@ export class UserNotificationController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post() + @ApiOperation({ + summary: ControllerRoute.USER_NOTIFICATION.ACTIONS.ADD_SUBSCRIPTION_SUMMARY, + description: + ControllerRoute.USER_NOTIFICATION.ACTIONS.ADD_SUBSCRIPTION_DESCRIPTION, + }) async addUserSubscription( @Body() userNotificationAddDto: UserNotificationAddDto, ) { @@ -47,6 +53,12 @@ export class UserNotificationController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':userUuid') + @ApiOperation({ + summary: + ControllerRoute.USER_NOTIFICATION.ACTIONS.FETCH_SUBSCRIPTIONS_SUMMARY, + description: + ControllerRoute.USER_NOTIFICATION.ACTIONS.FETCH_SUBSCRIPTIONS_DESCRIPTION, + }) async fetchUserSubscriptions(@Param('userUuid') userUuid: string) { const userDetails = await this.userNotificationService.fetchUserSubscriptions(userUuid); @@ -56,9 +68,16 @@ export class UserNotificationController { data: { ...userDetails }, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put() + @ApiOperation({ + summary: + ControllerRoute.USER_NOTIFICATION.ACTIONS.UPDATE_SUBSCRIPTION_SUMMARY, + description: + ControllerRoute.USER_NOTIFICATION.ACTIONS.UPDATE_SUBSCRIPTION_DESCRIPTION, + }) async updateUserSubscription( @Body() userNotificationUpdateDto: UserNotificationUpdateDto, ) { From fbbabc0644da379b895e08b9ad5e541c800d7eee Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:51:49 -0600 Subject: [PATCH 092/109] Add API operation metadata to verifyCodeAndAddUserSpace endpoint --- src/users/controllers/user-space.controller.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/users/controllers/user-space.controller.ts b/src/users/controllers/user-space.controller.ts index df36a5a..aed945a 100644 --- a/src/users/controllers/user-space.controller.ts +++ b/src/users/controllers/user-space.controller.ts @@ -40,6 +40,13 @@ export class UserSpaceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('/verify-code') + @ApiOperation({ + summary: + ControllerRoute.USER_SPACE.ACTIONS.VERIFY_CODE_AND_ADD_USER_SPACE_SUMMARY, + description: + ControllerRoute.USER_SPACE.ACTIONS + .VERIFY_CODE_AND_ADD_USER_SPACE_DESCRIPTION, + }) async verifyCodeAndAddUserSpace( @Body() dto: AddUserSpaceUsingCodeDto, @Param() params: UserParamDto, From 25e331f59e699d29312a143b467fd1e74714e179 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:52:00 -0600 Subject: [PATCH 093/109] Add API operation descriptions and update route paths --- src/users/controllers/user.controller.ts | 49 +++++++++++++++++++----- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/src/users/controllers/user.controller.ts b/src/users/controllers/user.controller.ts index 503f47c..6f0eded 100644 --- a/src/users/controllers/user.controller.ts +++ b/src/users/controllers/user.controller.ts @@ -9,7 +9,7 @@ import { UseGuards, } from '@nestjs/common'; import { UserService } from '../services/user.service'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { UpdateNameDto, @@ -20,11 +20,12 @@ import { import { CheckProfilePictureGuard } from 'src/guards/profile.picture.guard'; import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; @ApiTags('User Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'user', + path: ControllerRoute.USER.ROUTE, }) export class UserController { constructor(private readonly userService: UserService) {} @@ -32,12 +33,22 @@ export class UserController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':userUuid') + @ApiOperation({ + summary: ControllerRoute.USER.ACTIONS.GET_USER_DETAILS_SUMMARY, + description: ControllerRoute.USER.ACTIONS.GET_USER_DETAILS_DESCRIPTION, + }) async getUserDetailsByUserUuid(@Param('userUuid') userUuid: string) { return await this.userService.getUserDetailsByUserUuid(userUuid); } + @ApiBearerAuth() @UseGuards(JwtAuthGuard, CheckProfilePictureGuard) @Put('/profile-picture/:userUuid') + @ApiOperation({ + summary: ControllerRoute.USER.ACTIONS.UPDATE_PROFILE_PICTURE_SUMMARY, + description: + ControllerRoute.USER.ACTIONS.UPDATE_PROFILE_PICTURE_DESCRIPTION, + }) async updateProfilePictureByUserUuid( @Param('userUuid') userUuid: string, @Body() updateProfilePictureDataDto: UpdateProfilePictureDataDto, @@ -49,13 +60,18 @@ export class UserController { return { statusCode: HttpStatus.CREATED, success: true, - message: 'profile picture updated successfully', + message: 'Profile picture updated successfully', data: userData, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('/region/:userUuid') + @ApiOperation({ + summary: ControllerRoute.USER.ACTIONS.UPDATE_REGION_SUMMARY, + description: ControllerRoute.USER.ACTIONS.UPDATE_REGION_DESCRIPTION, + }) async updateRegionByUserUuid( @Param('userUuid') userUuid: string, @Body() updateRegionDataDto: UpdateRegionDataDto, @@ -67,14 +83,19 @@ export class UserController { return { statusCode: HttpStatus.CREATED, success: true, - message: 'region updated successfully', + message: 'Region updated successfully', data: userData, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('/timezone/:userUuid') - async updateNameByUserUuid( + @ApiOperation({ + summary: ControllerRoute.USER.ACTIONS.UPDATE_TIMEZONE_SUMMARY, + description: ControllerRoute.USER.ACTIONS.UPDATE_TIMEZONE_DESCRIPTION, + }) + async updateTimezoneByUserUuid( @Param('userUuid') userUuid: string, @Body() updateTimezoneDataDto: UpdateTimezoneDataDto, ) { @@ -85,14 +106,19 @@ export class UserController { return { statusCode: HttpStatus.CREATED, success: true, - message: 'timezone updated successfully', + message: 'Timezone updated successfully', data: userData, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Put('/name/:userUuid') - async updateTimezoneByUserUuid( + @ApiOperation({ + summary: ControllerRoute.USER.ACTIONS.UPDATE_NAME_SUMMARY, + description: ControllerRoute.USER.ACTIONS.UPDATE_NAME_DESCRIPTION, + }) + async updateNameByUserUuid( @Param('userUuid') userUuid: string, @Body() updateNameDto: UpdateNameDto, ) { @@ -103,13 +129,18 @@ export class UserController { return { statusCode: HttpStatus.CREATED, success: true, - message: 'name updated successfully', + message: 'Name updated successfully', data: userData, }; } + @ApiBearerAuth() @UseGuards(SuperAdminRoleGuard) @Delete('/:userUuid') + @ApiOperation({ + summary: ControllerRoute.USER.ACTIONS.DELETE_USER_SUMMARY, + description: ControllerRoute.USER.ACTIONS.DELETE_USER_DESCRIPTION, + }) async userDelete(@Param('userUuid') userUuid: string) { await this.userService.deleteUser(userUuid); return { @@ -117,7 +148,7 @@ export class UserController { data: { userUuid, }, - message: 'User Deleted Successfully', + message: 'User deleted successfully', }; } } From 2069b92e6d7332fb7d78201362de9811f1d352ff Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:52:11 -0600 Subject: [PATCH 094/109] Add `ApiOperation` decorators to visitor password controller endpoints --- .../visitor-password.controller.ts | 55 ++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/vistor-password/controllers/visitor-password.controller.ts b/src/vistor-password/controllers/visitor-password.controller.ts index 894b759..55276be 100644 --- a/src/vistor-password/controllers/visitor-password.controller.ts +++ b/src/vistor-password/controllers/visitor-password.controller.ts @@ -8,7 +8,7 @@ import { Get, Req, } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { AddDoorLockOfflineMultipleDto, AddDoorLockOfflineOneTimeDto, @@ -17,19 +17,29 @@ import { } from '../dtos/temp-pass.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; @ApiTags('Visitor Password Module') @Controller({ version: EnableDisableStatusEnum.ENABLED, - path: 'visitor-password', + path: ControllerRoute.VISITOR_PASSWORD.ROUTE, }) export class VisitorPasswordController { constructor( private readonly visitorPasswordService: VisitorPasswordService, ) {} + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('temporary-password/online/multiple-time') + @ApiOperation({ + summary: + ControllerRoute.VISITOR_PASSWORD.ACTIONS + .ADD_ONLINE_TEMP_PASSWORD_MULTIPLE_TIME_SUMMARY, + description: + ControllerRoute.VISITOR_PASSWORD.ACTIONS + .ADD_ONLINE_TEMP_PASSWORD_MULTIPLE_TIME_DESCRIPTION, + }) async addOnlineTemporaryPasswordMultipleTime( @Body() addDoorLockOnlineMultipleDto: AddDoorLockOnlineMultipleDto, @Req() req: any, @@ -46,9 +56,18 @@ export class VisitorPasswordController { data: temporaryPasswords, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('temporary-password/online/one-time') + @ApiOperation({ + summary: + ControllerRoute.VISITOR_PASSWORD.ACTIONS + .ADD_ONLINE_TEMP_PASSWORD_ONE_TIME_SUMMARY, + description: + ControllerRoute.VISITOR_PASSWORD.ACTIONS + .ADD_ONLINE_TEMP_PASSWORD_ONE_TIME_DESCRIPTION, + }) async addOnlineTemporaryPassword( @Body() addDoorLockOnlineOneTimeDto: AddDoorLockOnlineOneTimeDto, @Req() req: any, @@ -65,9 +84,18 @@ export class VisitorPasswordController { data: temporaryPasswords, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('temporary-password/offline/one-time') + @ApiOperation({ + summary: + ControllerRoute.VISITOR_PASSWORD.ACTIONS + .ADD_OFFLINE_TEMP_PASSWORD_ONE_TIME_SUMMARY, + description: + ControllerRoute.VISITOR_PASSWORD.ACTIONS + .ADD_OFFLINE_TEMP_PASSWORD_ONE_TIME_DESCRIPTION, + }) async addOfflineOneTimeTemporaryPassword( @Body() addDoorLockOfflineOneTimeDto: AddDoorLockOfflineOneTimeDto, @Req() req: any, @@ -84,9 +112,18 @@ export class VisitorPasswordController { data: temporaryPassword, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('temporary-password/offline/multiple-time') + @ApiOperation({ + summary: + ControllerRoute.VISITOR_PASSWORD.ACTIONS + .ADD_OFFLINE_TEMP_PASSWORD_MULTIPLE_TIME_SUMMARY, + description: + ControllerRoute.VISITOR_PASSWORD.ACTIONS + .ADD_OFFLINE_TEMP_PASSWORD_MULTIPLE_TIME_DESCRIPTION, + }) async addOfflineMultipleTimeTemporaryPassword( @Body() addDoorLockOfflineMultipleDto: AddDoorLockOfflineMultipleDto, @@ -104,15 +141,29 @@ export class VisitorPasswordController { data: temporaryPassword, }; } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get() + @ApiOperation({ + summary: + ControllerRoute.VISITOR_PASSWORD.ACTIONS.GET_VISITOR_PASSWORD_SUMMARY, + description: + ControllerRoute.VISITOR_PASSWORD.ACTIONS.GET_VISITOR_PASSWORD_DESCRIPTION, + }) async GetVisitorPassword() { return await this.visitorPasswordService.getPasswords(); } + @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('/devices') + @ApiOperation({ + summary: + ControllerRoute.VISITOR_PASSWORD.ACTIONS.GET_VISITOR_DEVICES_SUMMARY, + description: + ControllerRoute.VISITOR_PASSWORD.ACTIONS.GET_VISITOR_DEVICES_DESCRIPTION, + }) async GetVisitorDevices() { return await this.visitorPasswordService.getAllPassDevices(); } From 360371e910b5738b94a3dbe87f409865d7deb766 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:53:20 -0600 Subject: [PATCH 095/109] Add comprehensive endpoint descriptions for various controller routes --- libs/common/src/constants/controller-route.ts | 437 ++++++++++++++++++ 1 file changed, 437 insertions(+) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index fdf0435..0f13f5c 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -77,6 +77,10 @@ export class ControllerRoute { 'Retrieve list of spaces a user belongs to'; public static readonly GET_USER_SPACES_DESCRIPTION = 'This endpoint retrieves all the spaces that a user is associated with, based on the user ID. It fetches the user spaces by querying the UserSpaceEntity to find the spaces where the user has an association.'; + public static readonly VERIFY_CODE_AND_ADD_USER_SPACE_SUMMARY = + 'Verify code and add user space'; + public static readonly VERIFY_CODE_AND_ADD_USER_SPACE_DESCRIPTION = + 'This endpoint verifies a provided code and associates the user with a space. It checks the validity of the code and, if valid, links the user to the corresponding space in the system.'; }; }; @@ -244,4 +248,437 @@ export class ControllerRoute { 'Fetches a list of all products along with their associated device details'; }; }; + static USER = class { + public static readonly ROUTE = '/user'; + + static ACTIONS = class { + public static readonly GET_USER_DETAILS_SUMMARY = + 'Retrieve user details by user UUID'; + public static readonly GET_USER_DETAILS_DESCRIPTION = + 'This endpoint retrieves detailed information for a specific user based on their UUID.'; + + public static readonly UPDATE_PROFILE_PICTURE_SUMMARY = + 'Update profile picture by user UUID'; + public static readonly UPDATE_PROFILE_PICTURE_DESCRIPTION = + 'This endpoint updates the profile picture for a user identified by their UUID.'; + + public static readonly UPDATE_REGION_SUMMARY = + 'Update region by user UUID'; + public static readonly UPDATE_REGION_DESCRIPTION = + 'This endpoint updates the region information for a user identified by their UUID.'; + + public static readonly UPDATE_TIMEZONE_SUMMARY = + 'Update timezone by user UUID'; + public static readonly UPDATE_TIMEZONE_DESCRIPTION = + 'This endpoint updates the timezone information for a user identified by their UUID.'; + + public static readonly UPDATE_NAME_SUMMARY = 'Update name by user UUID'; + public static readonly UPDATE_NAME_DESCRIPTION = + 'This endpoint updates the name for a user identified by their UUID.'; + + public static readonly DELETE_USER_SUMMARY = 'Delete user by UUID'; + public static readonly DELETE_USER_DESCRIPTION = + 'This endpoint deletes a user identified by their UUID. Accessible only by users with the Super Admin role.'; + }; + }; + static AUTHENTICATION = class { + public static readonly ROUTE = 'authentication'; + + static ACTIONS = class { + public static readonly SIGN_UP_SUMMARY = + 'Sign up a new user and return a JWT token'; + public static readonly SIGN_UP_DESCRIPTION = + 'This endpoint is used to register a new user by providing the user details. A JWT token will be generated and returned upon successful registration.'; + + public static readonly LOGIN_SUMMARY = + 'Login a user and return an access token'; + public static readonly LOGIN_DESCRIPTION = + 'This endpoint allows an existing user to log in using their credentials. Upon successful login, an access token will be returned.'; + + public static readonly SEND_OTP_SUMMARY = + 'Generate and send OTP to the user'; + public static readonly SEND_OTP_DESCRIPTION = + 'This endpoint generates and sends an OTP to the user for verification, such as for password reset or account verification.'; + + public static readonly VERIFY_OTP_SUMMARY = + 'Verify the OTP entered by the user'; + public static readonly VERIFY_OTP_DESCRIPTION = + 'This endpoint verifies the OTP entered by the user. If the OTP is valid, the process continues (e.g., password reset).'; + + public static readonly FORGET_PASSWORD_SUMMARY = + 'Reset the user password after OTP verification'; + public static readonly FORGET_PASSWORD_DESCRIPTION = + 'This endpoint allows users who have forgotten their password to reset it. After verifying the OTP, the user can set a new password.'; + + public static readonly USER_LIST_SUMMARY = 'Fetch the list of all users'; + public static readonly USER_LIST_DESCRIPTION = + 'This endpoint retrieves a list of all users in the system. Access is restricted to super admins only.'; + + public static readonly REFRESH_TOKEN_SUMMARY = + 'Refresh the user session token'; + public static readonly REFRESH_TOKEN_DESCRIPTION = + 'This endpoint allows a user to refresh their session token. The user must provide a valid refresh token to get a new session token.'; + }; + }; + static ROLE = class { + public static readonly ROUTE = 'role'; + + static ACTIONS = class { + public static readonly FETCH_ROLE_TYPES_SUMMARY = + 'Fetch available role types'; + public static readonly FETCH_ROLE_TYPES_DESCRIPTION = + 'This endpoint retrieves all available role types in the system.'; + + public static readonly ADD_USER_ROLE_SUMMARY = 'Add a new user role'; + public static readonly ADD_USER_ROLE_DESCRIPTION = + 'This endpoint adds a new user role to the system based on the provided role data.'; + }; + }; + static GROUP = class { + public static readonly ROUTE = 'group'; + + static ACTIONS = class { + public static readonly GET_GROUPS_BY_SPACE_UUID_SUMMARY = + 'Get groups by space UUID'; + public static readonly GET_GROUPS_BY_SPACE_UUID_DESCRIPTION = + 'This endpoint retrieves all groups for a specific space, identified by the space UUID.'; + + public static readonly GET_UNIT_DEVICES_BY_GROUP_NAME_SUMMARY = + 'Get devices by group name in a space'; + public static readonly GET_UNIT_DEVICES_BY_GROUP_NAME_DESCRIPTION = + 'This endpoint retrieves all devices in a specified group within a space, based on the group name and space UUID.'; + }; + }; + static DEVICE = class { + public static readonly ROUTE = 'device'; + + static ACTIONS = class { + public static readonly ADD_DEVICE_TO_USER_SUMMARY = 'Add device to user'; + public static readonly ADD_DEVICE_TO_USER_DESCRIPTION = + 'This endpoint adds a device to a user in the system.'; + + public static readonly GET_DEVICES_BY_USER_SUMMARY = + 'Get devices by user UUID'; + public static readonly GET_DEVICES_BY_USER_DESCRIPTION = + 'This endpoint retrieves all devices associated with a specific user.'; + + public static readonly GET_DEVICES_BY_SPACE_UUID_SUMMARY = + 'Get devices by space UUID'; + public static readonly GET_DEVICES_BY_SPACE_UUID_DESCRIPTION = + 'This endpoint retrieves all devices associated with a specific space UUID.'; + + public static readonly UPDATE_DEVICE_IN_ROOM_SUMMARY = + 'Update device in Space'; + public static readonly UPDATE_DEVICE_IN_ROOM_DESCRIPTION = + 'This endpoint updates the device in a specific space with new details.'; + + public static readonly GET_DEVICE_DETAILS_SUMMARY = 'Get device details'; + public static readonly GET_DEVICE_DETAILS_DESCRIPTION = + 'This endpoint retrieves details of a specific device by its UUID.'; + + public static readonly UPDATE_DEVICE_SUMMARY = 'Update device'; + public static readonly UPDATE_DEVICE_DESCRIPTION = + 'This endpoint updates the details of a device by its UUID.'; + + public static readonly GET_DEVICE_INSTRUCTION_SUMMARY = + 'Get device instruction'; + public static readonly GET_DEVICE_INSTRUCTION_DESCRIPTION = + 'This endpoint retrieves the instruction details for a specific device.'; + + public static readonly GET_DEVICE_STATUS_SUMMARY = 'Get device status'; + public static readonly GET_DEVICE_STATUS_DESCRIPTION = + 'This endpoint retrieves the current status of a specific device.'; + + public static readonly CONTROL_DEVICE_SUMMARY = 'Control device'; + public static readonly CONTROL_DEVICE_DESCRIPTION = + 'This endpoint allows control operations (like power on/off) on a specific device.'; + + public static readonly UPDATE_DEVICE_FIRMWARE_SUMMARY = + 'Update device firmware'; + public static readonly UPDATE_DEVICE_FIRMWARE_DESCRIPTION = + 'This endpoint updates the firmware of a specific device to the provided version.'; + + public static readonly GET_DEVICES_IN_GATEWAY_SUMMARY = + 'Get devices in gateway'; + public static readonly GET_DEVICES_IN_GATEWAY_DESCRIPTION = + 'This endpoint retrieves all devices associated with a specific gateway.'; + + public static readonly GET_ALL_DEVICES_SUMMARY = 'Get all devices'; + public static readonly GET_ALL_DEVICES_DESCRIPTION = + 'This endpoint retrieves all devices in the system.'; + + public static readonly GET_DEVICE_LOGS_SUMMARY = 'Get device logs'; + public static readonly GET_DEVICE_LOGS_DESCRIPTION = + 'This endpoint retrieves the logs for a specific device based on device UUID.'; + + public static readonly BATCH_CONTROL_DEVICES_SUMMARY = + 'Batch control devices'; + public static readonly BATCH_CONTROL_DEVICES_DESCRIPTION = + 'This endpoint controls a batch of devices with the specified actions.'; + + public static readonly BATCH_STATUS_DEVICES_SUMMARY = + 'Batch status devices'; + public static readonly BATCH_STATUS_DEVICES_DESCRIPTION = + 'This endpoint retrieves the status of a batch of devices.'; + + public static readonly BATCH_FACTORY_RESET_DEVICES_SUMMARY = + 'Batch factory reset devices'; + public static readonly BATCH_FACTORY_RESET_DEVICES_DESCRIPTION = + 'This endpoint performs a factory reset on a batch of devices.'; + + public static readonly GET_POWER_CLAMP_STATUS_SUMMARY = + 'Get power clamp status'; + public static readonly GET_POWER_CLAMP_STATUS_DESCRIPTION = + 'This endpoint retrieves the status of a specific power clamp device.'; + + public static readonly ADD_SCENE_TO_DEVICE_SUMMARY = + 'Add scene to device (4 Scene and 6 Scene devices only)'; + public static readonly ADD_SCENE_TO_DEVICE_DESCRIPTION = + 'This endpoint adds a scene to a specific switch device.'; + + public static readonly GET_SCENES_BY_DEVICE_SUMMARY = + 'Get scenes by device (4 Scene and 6 Scene devices only)'; + public static readonly GET_SCENES_BY_DEVICE_DESCRIPTION = + 'This endpoint retrieves all scenes associated with a specific switch device.'; + }; + }; + + static DEVICE_PERMISSION = class { + public static readonly ROUTE = 'device-permission'; + + static ACTIONS = class { + public static readonly ADD_PERMISSION_SUMMARY = + 'Add user permission for device'; + public static readonly ADD_PERMISSION_DESCRIPTION = + 'This endpoint adds a user permission for a specific device. Accessible only by users with the Super Admin role.'; + + public static readonly EDIT_PERMISSION_SUMMARY = + 'Edit user permission for device'; + public static readonly EDIT_PERMISSION_DESCRIPTION = + 'This endpoint updates a user permission for a specific device. Accessible only by users with the Super Admin role.'; + + public static readonly FETCH_PERMISSION_SUMMARY = + 'Fetch user permission for device'; + public static readonly FETCH_PERMISSION_DESCRIPTION = + 'This endpoint retrieves the user permission for a specific device. Accessible only by users with the Super Admin role.'; + + public static readonly DELETE_PERMISSION_SUMMARY = + 'Delete user permission for device'; + public static readonly DELETE_PERMISSION_DESCRIPTION = + 'This endpoint deletes the user permission for a specific device. Accessible only by users with the Super Admin role.'; + }; + }; + + static USER_NOTIFICATION = class { + public static readonly ROUTE = 'user-notification/subscription'; + + static ACTIONS = class { + public static readonly ADD_SUBSCRIPTION_SUMMARY = + 'Add user notification subscription'; + public static readonly ADD_SUBSCRIPTION_DESCRIPTION = + 'This endpoint adds a subscription for user notifications.'; + + public static readonly FETCH_SUBSCRIPTIONS_SUMMARY = + 'Fetch user notification subscriptions'; + public static readonly FETCH_SUBSCRIPTIONS_DESCRIPTION = + 'This endpoint retrieves the subscriptions of a specific user based on their UUID.'; + + public static readonly UPDATE_SUBSCRIPTION_SUMMARY = + 'Update user notification subscription'; + public static readonly UPDATE_SUBSCRIPTION_DESCRIPTION = + 'This endpoint updates the notification subscription details for a user.'; + }; + }; + + static AUTOMATION = class { + public static readonly ROUTE = 'automation'; + + static ACTIONS = class { + public static readonly ADD_AUTOMATION_SUMMARY = 'Add automation'; + public static readonly ADD_AUTOMATION_DESCRIPTION = + 'This endpoint creates a new automation based on the provided details.'; + + public static readonly GET_AUTOMATION_BY_SPACE_SUMMARY = + 'Get automation by space'; + public static readonly GET_AUTOMATION_BY_SPACE_DESCRIPTION = + 'This endpoint retrieves the automations associated with a particular space.'; + + public static readonly GET_AUTOMATION_DETAILS_SUMMARY = + 'Get automation details'; + public static readonly GET_AUTOMATION_DETAILS_DESCRIPTION = + 'This endpoint retrieves detailed information about a specific automation.'; + + public static readonly DELETE_AUTOMATION_SUMMARY = 'Delete automation'; + public static readonly DELETE_AUTOMATION_DESCRIPTION = + 'This endpoint deletes an automation identified by its UUID.'; + + public static readonly UPDATE_AUTOMATION_SUMMARY = 'Update automation'; + public static readonly UPDATE_AUTOMATION_DESCRIPTION = + 'This endpoint updates the details of an existing automation.'; + + public static readonly UPDATE_AUTOMATION_STATUS_SUMMARY = + 'Update automation status'; + public static readonly UPDATE_AUTOMATION_STATUS_DESCRIPTION = + 'This endpoint updates the status of an automation identified by its UUID (enabled/disabled).'; + }; + }; + + static DOOR_LOCK = class { + public static readonly ROUTE = 'door-lock'; + + static ACTIONS = class { + public static readonly ADD_ONLINE_TEMPORARY_PASSWORD_SUMMARY = + 'Add online temporary password'; + public static readonly ADD_ONLINE_TEMPORARY_PASSWORD_DESCRIPTION = + 'This endpoint allows you to add an online temporary password to a door lock.'; + + public static readonly ADD_OFFLINE_ONE_TIME_TEMPORARY_PASSWORD_SUMMARY = + 'Add offline one-time temporary password'; + public static readonly ADD_OFFLINE_ONE_TIME_TEMPORARY_PASSWORD_DESCRIPTION = + 'This endpoint allows you to add an offline one-time temporary password to a door lock.'; + + public static readonly ADD_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORD_SUMMARY = + 'Add offline multiple-time temporary password'; + public static readonly ADD_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORD_DESCRIPTION = + 'This endpoint allows you to add an offline multiple-time temporary password to a door lock.'; + + public static readonly GET_ONLINE_TEMPORARY_PASSWORDS_SUMMARY = + 'Get online temporary passwords'; + public static readonly GET_ONLINE_TEMPORARY_PASSWORDS_DESCRIPTION = + 'This endpoint retrieves the list of online temporary passwords for a door lock.'; + + public static readonly DELETE_ONLINE_TEMPORARY_PASSWORD_SUMMARY = + 'Delete online temporary password'; + public static readonly DELETE_ONLINE_TEMPORARY_PASSWORD_DESCRIPTION = + 'This endpoint deletes an online temporary password for a door lock.'; + + public static readonly GET_OFFLINE_ONE_TIME_TEMPORARY_PASSWORDS_SUMMARY = + 'Get offline one-time temporary passwords'; + public static readonly GET_OFFLINE_ONE_TIME_TEMPORARY_PASSWORDS_DESCRIPTION = + 'This endpoint retrieves the list of offline one-time temporary passwords for a door lock.'; + + public static readonly GET_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORDS_SUMMARY = + 'Get offline multiple-time temporary passwords'; + public static readonly GET_OFFLINE_MULTIPLE_TIME_TEMPORARY_PASSWORDS_DESCRIPTION = + 'This endpoint retrieves the list of offline multiple-time temporary passwords for a door lock.'; + + public static readonly UPDATE_OFFLINE_TEMPORARY_PASSWORD_SUMMARY = + 'Update offline temporary password'; + public static readonly UPDATE_OFFLINE_TEMPORARY_PASSWORD_DESCRIPTION = + 'This endpoint updates an offline temporary password for a door lock.'; + + public static readonly OPEN_DOOR_LOCK_SUMMARY = 'Open door lock'; + public static readonly OPEN_DOOR_LOCK_DESCRIPTION = + 'This endpoint allows you to open a door lock.'; + }; + }; + static TIMEZONE = class { + public static readonly ROUTE = 'timezone'; + + static ACTIONS = class { + public static readonly GET_ALL_TIME_ZONES_SUMMARY = 'Get all time zones'; + public static readonly GET_ALL_TIME_ZONES_DESCRIPTION = + 'This endpoint retrieves all available time zones.'; + }; + }; + static VISITOR_PASSWORD = class { + public static readonly ROUTE = 'visitor-password'; + + static ACTIONS = class { + public static readonly ADD_ONLINE_TEMP_PASSWORD_MULTIPLE_TIME_SUMMARY = + 'Add online temporary passwords (multiple-time)'; + public static readonly ADD_ONLINE_TEMP_PASSWORD_MULTIPLE_TIME_DESCRIPTION = + 'This endpoint adds multiple online temporary passwords for door locks.'; + + public static readonly ADD_ONLINE_TEMP_PASSWORD_ONE_TIME_SUMMARY = + 'Add online temporary password (one-time)'; + public static readonly ADD_ONLINE_TEMP_PASSWORD_ONE_TIME_DESCRIPTION = + 'This endpoint adds a one-time online temporary password for a door lock.'; + + public static readonly ADD_OFFLINE_TEMP_PASSWORD_ONE_TIME_SUMMARY = + 'Add offline temporary password (one-time)'; + public static readonly ADD_OFFLINE_TEMP_PASSWORD_ONE_TIME_DESCRIPTION = + 'This endpoint adds a one-time offline temporary password for a door lock.'; + + public static readonly ADD_OFFLINE_TEMP_PASSWORD_MULTIPLE_TIME_SUMMARY = + 'Add offline temporary passwords (multiple-time)'; + public static readonly ADD_OFFLINE_TEMP_PASSWORD_MULTIPLE_TIME_DESCRIPTION = + 'This endpoint adds multiple offline temporary passwords for door locks.'; + + public static readonly GET_VISITOR_PASSWORD_SUMMARY = + 'Get visitor passwords'; + public static readonly GET_VISITOR_PASSWORD_DESCRIPTION = + 'This endpoint retrieves all visitor passwords.'; + + public static readonly GET_VISITOR_DEVICES_SUMMARY = + 'Get visitor devices'; + public static readonly GET_VISITOR_DEVICES_DESCRIPTION = + 'This endpoint retrieves all devices associated with visitor passwords.'; + }; + }; + static SCHEDULE = class { + public static readonly ROUTE = 'schedule'; + + static ACTIONS = class { + public static readonly ADD_DEVICE_SCHEDULE_SUMMARY = + 'Add device schedule'; + public static readonly ADD_DEVICE_SCHEDULE_DESCRIPTION = + 'This endpoint allows you to add a schedule for a specific device.'; + + public static readonly GET_DEVICE_SCHEDULE_BY_CATEGORY_SUMMARY = + 'Get device schedule by category'; + public static readonly GET_DEVICE_SCHEDULE_BY_CATEGORY_DESCRIPTION = + 'This endpoint retrieves the schedule for a specific device based on the given category.'; + + public static readonly DELETE_DEVICE_SCHEDULE_SUMMARY = + 'Delete device schedule'; + public static readonly DELETE_DEVICE_SCHEDULE_DESCRIPTION = + 'This endpoint deletes a specific schedule for a device.'; + + public static readonly ENABLE_DEVICE_SCHEDULE_SUMMARY = + 'Enable device schedule'; + public static readonly ENABLE_DEVICE_SCHEDULE_DESCRIPTION = + 'This endpoint enables a device schedule for a specific device.'; + + public static readonly UPDATE_DEVICE_SCHEDULE_SUMMARY = + 'Update device schedule'; + public static readonly UPDATE_DEVICE_SCHEDULE_DESCRIPTION = + 'This endpoint updates the schedule for a specific device.'; + }; + }; + static DEVICE_STATUS_FIREBASE = class { + public static readonly ROUTE = 'device-status-firebase'; + + static ACTIONS = class { + public static readonly ADD_DEVICE_STATUS_SUMMARY = + 'Add device status to Firebase'; + public static readonly ADD_DEVICE_STATUS_DESCRIPTION = + 'This endpoint adds a device status in Firebase based on the provided device UUID.'; + + public static readonly GET_DEVICE_STATUS_SUMMARY = + 'Get device status from Firebase'; + public static readonly GET_DEVICE_STATUS_DESCRIPTION = + 'This endpoint retrieves a device status from Firebase using the device UUID.'; + }; + }; + static DEVICE_MESSAGES_SUBSCRIPTION = class { + public static readonly ROUTE = 'device-messages/subscription'; + + static ACTIONS = class { + public static readonly ADD_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY = + 'Add device messages subscription'; + public static readonly ADD_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION = + 'This endpoint adds a subscription for device messages.'; + + public static readonly GET_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY = + 'Get device messages subscription'; + public static readonly GET_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION = + 'This endpoint fetches a user’s subscription for a specific device.'; + + public static readonly DELETE_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY = + 'Delete device messages subscription'; + public static readonly DELETE_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION = + 'This endpoint deletes a user’s subscription for device messages.'; + }; + }; } From 4d0c91db19225be4d0f66ca92a5d896411d1c69d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 24 Nov 2024 15:58:44 -0600 Subject: [PATCH 096/109] fix delete scene issue --- src/scene/services/scene.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 9c5791b..33bc44e 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -459,6 +459,7 @@ export class SceneService { const space = await this.getSpaceByUuid(scene.spaceUuid); await this.delete(scene.sceneTuyaUuid, space.spaceTuyaUuid); + await this.sceneRepository.delete({ uuid: sceneUuid }); return new SuccessResponseDto({ message: `Scene with ID ${sceneUuid} deleted successfully`, }); From 6b66bf2b20d232bfc749d5bf20fa592f32532a79 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 24 Nov 2024 16:09:21 -0600 Subject: [PATCH 097/109] fix automation get endpoint remove return Auto_ --- libs/common/src/constants/automation.enum.ts | 3 ++- src/automation/services/automation.service.ts | 20 +++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/libs/common/src/constants/automation.enum.ts b/libs/common/src/constants/automation.enum.ts index e9bb6a8..8370cf9 100644 --- a/libs/common/src/constants/automation.enum.ts +++ b/libs/common/src/constants/automation.enum.ts @@ -1,4 +1,3 @@ -// automation.enum.ts export enum ActionExecutorEnum { DEVICE_ISSUE = 'device_issue', DELAY = 'delay', @@ -17,3 +16,5 @@ export const AUTOMATION_CONFIG = { COMPARATOR: '==', SCENE_STATUS_VALUE: 'scene', }; +export const AUTOMATION_TYPE = 'automation'; +export const AUTO_PREFIX = 'Auto_'; diff --git a/src/automation/services/automation.service.ts b/src/automation/services/automation.service.ts index 27d1b6a..9c805f3 100644 --- a/src/automation/services/automation.service.ts +++ b/src/automation/services/automation.service.ts @@ -26,6 +26,8 @@ import { import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { ActionExecutorEnum, + AUTO_PREFIX, + AUTOMATION_TYPE, EntityTypeEnum, } from '@app/common/constants/automation.enum'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; @@ -154,14 +156,16 @@ export class AutomationService { throw new HttpException(response.msg, HttpStatus.BAD_REQUEST); } - return response.result.list.map((item) => { - return { - id: item.id, - name: item.name, - status: item.status, - type: 'automation', - }; - }); + return response.result.list + .filter((item) => item.name && !item.name.startsWith(AUTO_PREFIX)) + .map((item) => { + return { + id: item.id, + name: item.name, + status: item.status, + type: AUTOMATION_TYPE, + }; + }); } catch (err) { if (err instanceof BadRequestException) { throw err; // Re-throw BadRequestException From 99bacbe2020dee8681709e67f743501e3d792104 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 24 Nov 2024 17:47:47 -0600 Subject: [PATCH 098/109] finished skip any error from tuya device --- src/device/services/device.service.ts | 29 ++++++++++++------- src/space/services/space-device.service.ts | 17 +++++++---- .../subspace/subspace-device.service.ts | 16 ++++++---- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index eeca08c..0253430 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -172,31 +172,40 @@ export class DeviceService { 'permission.permissionType', ], }); - const devicesData = await Promise.all( - devices.map(async (device) => { + const safeFetchDeviceDetails = async (device: any) => { + try { + const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya( + device.deviceTuyaUuid, + ); + return { - haveRoom: device.spaceDevice ? true : false, + haveRoom: !!device.spaceDevice, productUuid: device.productDevice.uuid, productType: device.productDevice.prodType, permissionType: device.permission[0].permissionType.type, - ...(await this.getDeviceDetailsByDeviceIdTuya( - device.deviceTuyaUuid, - )), + ...tuyaDetails, uuid: device.uuid, } as GetDeviceDetailsInterface; - }), + } catch (error) { + console.warn( + `Skipping device with deviceTuyaUuid: ${device.deviceTuyaUuid} due to error.`, + ); + return null; + } + }; + const devicesData = await Promise.all( + devices.map(safeFetchDeviceDetails), ); - return devicesData; + return devicesData.filter(Boolean); // Remove null or undefined entries } catch (error) { - // Handle the error here + console.error('Error fetching devices by user:', error); throw new HttpException( 'User does not have any devices', HttpStatus.NOT_FOUND, ); } } - async getDevicesBySpaceId( getDeviceBySpaceUuidDto: GetDeviceBySpaceUuidDto, userUuid: string, diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index df79a5e..4dc24ba 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -26,8 +26,8 @@ export class SpaceDeviceService { spaceUuid, ); - const detailedDevices = await Promise.all( - space.devices.map(async (device) => { + const safeFetch = async (device: any) => { + try { const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya( device.deviceTuyaUuid, ); @@ -41,11 +41,18 @@ export class SpaceDeviceService { updatedAt: device.updatedAt, ...tuyaDetails, }; - }), - ); + } catch (error) { + console.warn( + `Skipping device with deviceTuyaUuid: ${device.deviceTuyaUuid} due to error.`, + ); + return null; + } + }; + + const detailedDevices = await Promise.all(space.devices.map(safeFetch)); return new SuccessResponseDto({ - data: detailedDevices, + data: detailedDevices.filter(Boolean), // Remove null or undefined values message: 'Successfully retrieved list of devices', }); } catch (error) { diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index 1dac1ee..74f6610 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -33,8 +33,8 @@ export class SubspaceDeviceService { const subspace = await this.findSubspaceWithDevices(subSpaceUuid); - const detailedDevices = await Promise.all( - subspace.devices.map(async (device) => { + const safeFetch = async (device: any) => { + try { const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya( device.deviceTuyaUuid, ); @@ -49,11 +49,17 @@ export class SubspaceDeviceService { updatedAt: device.updatedAt, ...tuyaDetails, }; - }), - ); + } catch (error) { + console.warn( + `Skipping device with deviceTuyaUuid: ${device.deviceTuyaUuid} due to error.`, + ); + return null; + } + }; + const detailedDevices = await Promise.all(subspace.devices.map(safeFetch)); return new SuccessResponseDto({ - data: detailedDevices, + data: detailedDevices.filter(Boolean), // Remove nulls message: 'Successfully retrieved list of devices', }); } From 9721cb765f58f70177de237a9f43863df7271bfc Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 24 Nov 2024 23:43:27 -0600 Subject: [PATCH 099/109] Update scene service to use space object instead of spaceUuid --- src/scene/services/scene.service.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 33bc44e..c3dff2b 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -106,7 +106,7 @@ export class SceneService { sceneTuyaUuid: response.result.id, sceneIcon: { uuid: iconUuid || defaultSceneIcon.uuid }, showInHomePage, - spaceUuid, + space: { uuid: spaceUuid }, }); return scene; @@ -174,7 +174,7 @@ export class SceneService { const scenesData = await this.sceneRepository.find({ where: { - spaceUuid, + space: { uuid: spaceUuid }, ...(showInHomePage ? { showInHomePage } : {}), }, relations: ['sceneIcon'], @@ -278,11 +278,11 @@ export class SceneService { ) { try { const scene = await this.findScene(sceneUuid); - const space = await this.getSpaceByUuid(scene.spaceUuid); + const space = await this.getSpaceByUuid(scene.space.uuid); const addSceneTapToRunDto: AddSceneTapToRunDto = { ...updateSceneTapToRunDto, - spaceUuid: scene.spaceUuid, + spaceUuid: scene.space.uuid, iconUuid: updateSceneTapToRunDto.iconUuid, showInHomePage: updateSceneTapToRunDto.showInHomePage, }; @@ -310,7 +310,7 @@ export class SceneService { sceneIcon: { uuid: addSceneTapToRunDto.iconUuid, }, - spaceUuid: scene.spaceUuid, + space: { uuid: scene.space.uuid }, }, ); return new SuccessResponseDto({ @@ -438,7 +438,7 @@ export class SceneService { showInHome: scene.showInHomePage, type: 'tap_to_run', actions, - spaceId: scene.spaceUuid, + spaceId: scene.space.uuid, }; } catch (err) { if (err instanceof BadRequestException) { @@ -456,7 +456,7 @@ export class SceneService { try { const { sceneUuid } = params; const scene = await this.findScene(sceneUuid); - const space = await this.getSpaceByUuid(scene.spaceUuid); + const space = await this.getSpaceByUuid(scene.space.uuid); await this.delete(scene.sceneTuyaUuid, space.spaceTuyaUuid); await this.sceneRepository.delete({ uuid: sceneUuid }); From c90b07fab438372064f71cc2f5dce823330ecc17 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 25 Nov 2024 01:01:31 -0600 Subject: [PATCH 100/109] Update `getScene` method to accept `sceneUuid` and use it for spaceId --- src/scene/services/scene.service.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index c3dff2b..895c712 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -190,7 +190,10 @@ export class SceneService { const scenes = await Promise.all( scenesData.map(async (scene) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { actions, ...sceneDetails } = await this.getScene(scene); + const { actions, ...sceneDetails } = await this.getScene( + scene, + spaceUuid, + ); return sceneDetails; }), @@ -376,7 +379,7 @@ export class SceneService { async getSceneByUuid(sceneUuid: string): Promise { try { const scene = await this.findScene(sceneUuid); - const sceneDetails = await this.getScene(scene); + const sceneDetails = await this.getScene(scene, sceneUuid); return new SuccessResponseDto({ data: sceneDetails, @@ -399,7 +402,7 @@ export class SceneService { } } - async getScene(scene: SceneEntity): Promise { + async getScene(scene: SceneEntity, sceneUuid: string): Promise { try { const { actions, name, status } = await this.fetchSceneDetailsFromTuya( scene.sceneTuyaUuid, @@ -438,7 +441,7 @@ export class SceneService { showInHome: scene.showInHomePage, type: 'tap_to_run', actions, - spaceId: scene.space.uuid, + spaceId: sceneUuid, }; } catch (err) { if (err instanceof BadRequestException) { From a37af93696eaff08ab8ed6c2191e3d5aa6db164a Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 25 Nov 2024 01:10:32 -0600 Subject: [PATCH 101/109] Include space relation in findScene method --- src/scene/services/scene.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 895c712..b33f84a 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -481,7 +481,7 @@ export class SceneService { async findScene(sceneUuid: string): Promise { const scene = await this.sceneRepository.findOne({ where: { uuid: sceneUuid }, - relations: ['sceneIcon'], + relations: ['sceneIcon', 'space'], }); if (!scene) { From 06c10c39eebfb888d02a57114377c19f41835e81 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 25 Nov 2024 01:11:51 -0600 Subject: [PATCH 102/109] Include space relation in scene query --- src/scene/services/scene.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index b33f84a..8a9b38f 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -177,7 +177,7 @@ export class SceneService { space: { uuid: spaceUuid }, ...(showInHomePage ? { showInHomePage } : {}), }, - relations: ['sceneIcon'], + relations: ['sceneIcon', 'space'], }); if (!scenesData.length) { From 13983d61aa5e5be2eca03c5128e3a4fd586aab65 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 25 Nov 2024 19:29:07 -0600 Subject: [PATCH 103/109] Remove unnecessary check for scenesData length --- src/scene/services/scene.service.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 8a9b38f..0aa8c81 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -180,13 +180,6 @@ export class SceneService { relations: ['sceneIcon', 'space'], }); - if (!scenesData.length) { - throw new HttpException( - `No scenes found for space UUID ${spaceUuid} with showInHomePage ${showInHomePage} `, - HttpStatus.NOT_FOUND, - ); - } - const scenes = await Promise.all( scenesData.map(async (scene) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars From bae48c02fedbddb42d659d25c50ae0c932b6a7e6 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 26 Nov 2024 22:26:46 -0600 Subject: [PATCH 104/109] Add SceneDeviceRepository and update deleteScene method --- src/scene/services/scene.service.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 0aa8c81..20866b2 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -39,6 +39,7 @@ import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { HttpStatusCode } from 'axios'; import { ConvertedAction } from '@app/common/integrations/tuya/interfaces'; import { DeviceService } from 'src/device/services'; +import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; @Injectable() export class SceneService { @@ -46,6 +47,7 @@ export class SceneService { private readonly spaceRepository: SpaceRepository, private readonly sceneIconRepository: SceneIconRepository, private readonly sceneRepository: SceneRepository, + private readonly sceneDeviceRepository: SceneDeviceRepository, private readonly tuyaService: TuyaService, @Inject(forwardRef(() => DeviceService)) private readonly deviceService: DeviceService, @@ -449,13 +451,18 @@ export class SceneService { } async deleteScene(params: SceneParamDto): Promise { + const { sceneUuid } = params; try { - const { sceneUuid } = params; const scene = await this.findScene(sceneUuid); const space = await this.getSpaceByUuid(scene.space.uuid); await this.delete(scene.sceneTuyaUuid, space.spaceTuyaUuid); - await this.sceneRepository.delete({ uuid: sceneUuid }); + await this.sceneDeviceRepository.delete({ + scene: { uuid: sceneUuid }, + }); + await this.sceneRepository.delete({ + uuid: sceneUuid, + }); return new SuccessResponseDto({ message: `Scene with ID ${sceneUuid} deleted successfully`, }); From 4d2257a3e30ab2aae796f79a723ef1918d91e21a Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 27 Nov 2024 03:17:56 -0600 Subject: [PATCH 105/109] Add endpoint to delete scenes by device UUID and switch name --- libs/common/src/constants/controller-route.ts | 4 ++++ src/device/controllers/device.controller.ts | 14 ++++++++--- src/device/dtos/delete.device.dto.ts | 12 ++++++++++ src/device/dtos/device.param.dto.ts | 6 ++--- src/device/services/device.service.ts | 23 ++++++++++++------- 5 files changed, 45 insertions(+), 14 deletions(-) create mode 100644 src/device/dtos/delete.device.dto.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 0f13f5c..eeacd0f 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -440,6 +440,10 @@ export class ControllerRoute { 'Get scenes by device (4 Scene and 6 Scene devices only)'; public static readonly GET_SCENES_BY_DEVICE_DESCRIPTION = 'This endpoint retrieves all scenes associated with a specific switch device.'; + public static readonly DELETE_SCENES_BY_SWITCH_NAME_SUMMARY = + 'Delete scenes by device uuid and switch name (4 Scene and 6 Scene devices only)'; + public static readonly DELETE_SCENES_BY_SWITCH_NAME_DESCRIPTION = + 'This endpoint deletes all scenes associated with a specific switch device.'; }; }; diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 5db48cb..a49b1f7 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -36,6 +36,7 @@ import { CheckFourAndSixSceneDeviceTypeGuard } from 'src/guards/scene.device.typ import { ControllerRoute } from '@app/common/constants/controller-route'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { DeviceSceneParamDto } from '../dtos/device.param.dto'; +import { DeleteSceneFromSceneDeviceDto } from '../dtos/delete.device.dto'; @ApiTags('Device Module') @Controller({ @@ -334,10 +335,17 @@ export class DeviceController { } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Delete(':switchSceneUuid/scenes') - async deleteSceneToSceneDevice( + @Delete(':deviceUuid/scenes') + @ApiOperation({ + summary: + ControllerRoute.DEVICE.ACTIONS.DELETE_SCENES_BY_SWITCH_NAME_SUMMARY, + description: + ControllerRoute.DEVICE.ACTIONS.DELETE_SCENES_BY_SWITCH_NAME_DESCRIPTION, + }) + async deleteSceneFromSceneDevice( @Param() param: DeviceSceneParamDto, + @Query() query: DeleteSceneFromSceneDeviceDto, ): Promise { - return await this.deviceService.deleteSceneToSceneDevice(param); + return await this.deviceService.deleteSceneFromSceneDevice(param, query); } } diff --git a/src/device/dtos/delete.device.dto.ts b/src/device/dtos/delete.device.dto.ts new file mode 100644 index 0000000..2e60435 --- /dev/null +++ b/src/device/dtos/delete.device.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class DeleteSceneFromSceneDeviceDto { + @ApiProperty({ + description: 'switchName', + required: true, + }) + @IsString() + @IsNotEmpty() + public switchName: string; +} diff --git a/src/device/dtos/device.param.dto.ts b/src/device/dtos/device.param.dto.ts index 264b656..0a0eb6c 100644 --- a/src/device/dtos/device.param.dto.ts +++ b/src/device/dtos/device.param.dto.ts @@ -3,9 +3,9 @@ import { IsUUID } from 'class-validator'; export class DeviceSceneParamDto { @ApiProperty({ - description: 'UUID of the Switch Scene', - example: 'b3421478-3bec-4634-9805-a53950260ecb', + description: 'UUID of the device', + example: 'b3a37332-9c03-4ce2-ac94-bea75382b365', }) @IsUUID() - switchSceneUuid: string; + deviceUuid: string; } diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 521693b..3ed3327 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -58,6 +58,7 @@ import { AUTOMATION_CONFIG } from '@app/common/constants/automation.enum'; import { DeviceSceneParamDto } from '../dtos/device.param.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { DeleteSceneFromSceneDeviceDto } from '../dtos/delete.device.dto'; @Injectable() export class DeviceService { @@ -1406,31 +1407,37 @@ export class DeviceService { ); } } - async deleteSceneToSceneDevice( + async deleteSceneFromSceneDevice( params: DeviceSceneParamDto, + query: DeleteSceneFromSceneDeviceDto, ): Promise { - const { switchSceneUuid } = params; + const { deviceUuid } = params; + const { switchName } = query; try { const existingSceneDevice = await this.sceneDeviceRepository.findOne({ - where: { uuid: switchSceneUuid }, + where: { + device: { uuid: deviceUuid }, + switchName: switchName as SceneSwitchesTypeEnum, + }, relations: ['scene.space.community'], }); if (!existingSceneDevice) { throw new HttpException( - `Switch Scene not found for ID ${switchSceneUuid}`, + `No scene found for device with UUID ${deviceUuid} and switch name ${switchName}`, HttpStatus.NOT_FOUND, ); } const deleteResult = await this.sceneDeviceRepository.delete({ - uuid: switchSceneUuid, + device: { uuid: deviceUuid }, + switchName: switchName as SceneSwitchesTypeEnum, }); if (deleteResult.affected === 0) { throw new HttpException( - `Failed to delete Switch Scene with ID ${switchSceneUuid}`, + `Failed to delete Switch Scene with device ID ${deviceUuid} and switch name ${switchName}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -1442,13 +1449,13 @@ export class DeviceService { if (!tuyaAutomationResult.success) { throw new HttpException( - `Failed to delete Tuya automation for Switch Scene with ID ${switchSceneUuid}`, + `Failed to delete Tuya automation for Switch Scene with ID ${existingSceneDevice.automationTuyaUuid}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } return new SuccessResponseDto({ - message: `Switch Scene with ID ${switchSceneUuid} deleted successfully`, + message: `Switch Scene with device ID ${deviceUuid} and switch name ${switchName} deleted successfully`, }); } catch (error) { if (error instanceof HttpException) { From b3f4507a70dedfd72c4d9dd3e62d5b1a2edddba1 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Fri, 29 Nov 2024 01:40:52 -0600 Subject: [PATCH 106/109] Add productUuid and productType to action and condition --- src/automation/services/automation.service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/automation/services/automation.service.ts b/src/automation/services/automation.service.ts index 9c805f3..91cca1a 100644 --- a/src/automation/services/automation.service.ts +++ b/src/automation/services/automation.service.ts @@ -238,6 +238,8 @@ export class AutomationService { if (device) { action.entityId = device.uuid; + action.productUuid = device.productDevice.uuid; + action.productType = device.productDevice.prodType; } } else if ( action.actionExecutor !== ActionExecutorEnum.DEVICE_ISSUE && @@ -266,6 +268,8 @@ export class AutomationService { if (device) { condition.entityId = device.uuid; + condition.productUuid = device.productDevice.uuid; + condition.productType = device.productDevice.prodType; } } } From 17b300183c0fa4401e24e8d8866b73f1486d2634 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Fri, 29 Nov 2024 01:40:59 -0600 Subject: [PATCH 107/109] Add productUuid and productType to action when device is present --- src/scene/services/scene.service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 20866b2..cd37b18 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -411,6 +411,8 @@ export class SceneService { if (device) { action.entityId = device.uuid; + action.productUuid = device.productDevice.uuid; + action.productType = device.productDevice.prodType; } } else if ( action.actionExecutor !== ActionExecutorEnum.DEVICE_ISSUE && From e0ec9f20ffc1d1544a43fbb0c7ea35b45720abee Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 30 Nov 2024 20:25:59 -0600 Subject: [PATCH 108/109] Add method to update TapToRun scene in Tuya and integrate it in SceneService --- .../tuya/services/tuya.service.ts | 30 +++++++++++ src/scene/services/scene.service.ts | 52 ++++++++++++++++--- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/libs/common/src/integrations/tuya/services/tuya.service.ts b/libs/common/src/integrations/tuya/services/tuya.service.ts index ff69aa9..76c6582 100644 --- a/libs/common/src/integrations/tuya/services/tuya.service.ts +++ b/libs/common/src/integrations/tuya/services/tuya.service.ts @@ -120,6 +120,36 @@ export class TuyaService { ); } } + async updateTapToRunScene( + sceneTuyaUuid: string, + spaceId: string, + sceneName: string, + actions: ConvertedAction[], + decisionExpr: string, + ) { + const path = `/v2.0/cloud/scene/rule/${sceneTuyaUuid}`; + const response = await this.tuya.request({ + method: 'PUT', + path, + body: { + space_id: spaceId, + name: sceneName, + type: 'scene', + decision_expr: decisionExpr, + conditions: [], + actions: actions, + }, + }); + + if (response.success) { + return response; + } else { + throw new HttpException( + `Error fetching scene rule: ${response.msg}`, + HttpStatus.BAD_REQUEST, + ); + } + } async triggerScene(sceneId: string): Promise { const path = `/v2.0/cloud/scene/rule/${sceneId}/actions/trigger`; diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index cd37b18..15f1b23 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -128,6 +128,47 @@ export class SceneService { } } } + async updateSceneExternalService( + spaceTuyaUuid: string, + sceneTuyaUuid: string, + updateSceneTapToRunDto: UpdateSceneTapToRunDto, + ) { + const { sceneName, decisionExpr, actions } = updateSceneTapToRunDto; + try { + const formattedActions = await this.prepareActions(actions); + + const response = (await this.tuyaService.updateTapToRunScene( + sceneTuyaUuid, + spaceTuyaUuid, + sceneName, + formattedActions, + decisionExpr, + )) as AddTapToRunSceneInterface; + + if (!response.success) { + throw new HttpException( + 'Failed to update scene in Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + return response; + } catch (err) { + if (err instanceof HttpException) { + throw err; + } else if (err.message?.includes('tuya')) { + throw new HttpException( + 'API error: Failed to update scene', + HttpStatus.BAD_GATEWAY, + ); + } else { + throw new HttpException( + `An Internal error has been occured ${err}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } async createSceneExternalService( spaceTuyaUuid: string, @@ -285,25 +326,22 @@ export class SceneService { showInHomePage: updateSceneTapToRunDto.showInHomePage, }; - const createdTuyaSceneResponse = await this.createSceneExternalService( + const updateTuyaSceneResponse = await this.updateSceneExternalService( space.spaceTuyaUuid, + scene.sceneTuyaUuid, addSceneTapToRunDto, ); - const newSceneTuyaUuid = createdTuyaSceneResponse.result?.id; - if (!newSceneTuyaUuid) { + if (!updateTuyaSceneResponse.success) { throw new HttpException( - `Failed to create a external new scene`, + `Failed to update a external scene`, HttpStatus.BAD_GATEWAY, ); } - await this.delete(scene.sceneTuyaUuid, space.spaceTuyaUuid); - const updatedScene = await this.sceneRepository.update( { uuid: sceneUuid }, { - sceneTuyaUuid: newSceneTuyaUuid, showInHomePage: addSceneTapToRunDto.showInHomePage, sceneIcon: { uuid: addSceneTapToRunDto.iconUuid, From b143e50fba1dac99e54b96eaef7c7a01bec1af64 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 30 Nov 2024 20:26:34 -0600 Subject: [PATCH 109/109] handle empty return when get scene switch --- src/device/services/device.service.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 3ed3327..add62c9 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -1349,10 +1349,7 @@ export class DeviceService { }); if (!sceneDevice) { - throw new HttpException( - `No scene found for device with UUID ${deviceUuid} and switch name ${getSceneFourSceneDeviceDto.switchName}`, - HttpStatus.NOT_FOUND, - ); + return {}; } const sceneDetails = await this.sceneService.getSceneByUuid( @@ -1376,10 +1373,7 @@ export class DeviceService { }); if (!sceneDevices.length) { - throw new HttpException( - `No scenes found for device with UUID ${deviceUuid}`, - HttpStatus.NOT_FOUND, - ); + return []; } const results = await Promise.all(