diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index e263571..dd29899 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -6,6 +6,7 @@ import { UserEntity } from '../modules/user/entities/user.entity'; import { UserSessionEntity } from '../modules/session/entities/session.entity'; import { UserOtpEntity } from '../modules/user-otp/entities'; import { HomeEntity } from '../modules/home/entities'; +import { ProductEntity } from '../modules/product/entities'; @Module({ imports: [ @@ -20,7 +21,13 @@ import { HomeEntity } from '../modules/home/entities'; username: configService.get('DB_USER'), password: configService.get('DB_PASSWORD'), database: configService.get('DB_NAME'), - entities: [UserEntity, UserSessionEntity, UserOtpEntity, HomeEntity], + entities: [ + UserEntity, + UserSessionEntity, + UserOtpEntity, + HomeEntity, + ProductEntity, + ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), logging: true, diff --git a/libs/common/src/helper/camelCaseConverter.ts b/libs/common/src/helper/camelCaseConverter.ts new file mode 100644 index 0000000..5cfc2ba --- /dev/null +++ b/libs/common/src/helper/camelCaseConverter.ts @@ -0,0 +1,20 @@ +export function convertKeysToCamelCase(obj: any): any { + if (!obj || typeof obj !== 'object') { + return obj; + } + + if (Array.isArray(obj)) { + return obj.map(convertKeysToCamelCase); + } + + const camelCaseObj: { [key: string]: any } = {}; + + for (const key of Object.keys(obj)) { + const camelCaseKey = key.replace(/_([a-z])/g, (_, letter) => + letter.toUpperCase(), + ); + camelCaseObj[camelCaseKey] = convertKeysToCamelCase(obj[key]); + } + + return camelCaseObj; +} diff --git a/libs/common/src/modules/abstract/entities/abstract.entity.ts b/libs/common/src/modules/abstract/entities/abstract.entity.ts index bc27899..7c54a31 100644 --- a/libs/common/src/modules/abstract/entities/abstract.entity.ts +++ b/libs/common/src/modules/abstract/entities/abstract.entity.ts @@ -1,11 +1,5 @@ import { Exclude } from 'class-transformer'; -import { - Column, - CreateDateColumn, - Generated, - PrimaryGeneratedColumn, - UpdateDateColumn, -} from 'typeorm'; +import { CreateDateColumn, PrimaryColumn, UpdateDateColumn } from 'typeorm'; import { AbstractDto } from '../dtos'; import { Constructor } from '../../../../../common/src/util/types'; @@ -14,12 +8,11 @@ export abstract class AbstractEntity< T extends AbstractDto = AbstractDto, O = never, > { - @PrimaryGeneratedColumn('increment') + @PrimaryColumn({ + type: 'uuid', + generated: 'uuid', + }) @Exclude() - public id: number; - - @Column() - @Generated('uuid') public uuid: string; @CreateDateColumn({ type: 'timestamp' }) diff --git a/libs/common/src/modules/product/dtos/index.ts b/libs/common/src/modules/product/dtos/index.ts new file mode 100644 index 0000000..810efbd --- /dev/null +++ b/libs/common/src/modules/product/dtos/index.ts @@ -0,0 +1 @@ +export * from './product.dto'; diff --git a/libs/common/src/modules/product/dtos/product.dto.ts b/libs/common/src/modules/product/dtos/product.dto.ts new file mode 100644 index 0000000..2614d78 --- /dev/null +++ b/libs/common/src/modules/product/dtos/product.dto.ts @@ -0,0 +1,19 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +export class ProductDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public catName: string; + + @IsString() + @IsNotEmpty() + public prodId: string; + + @IsString() + @IsNotEmpty() + public prodType: string; +} diff --git a/libs/common/src/modules/product/entities/index.ts b/libs/common/src/modules/product/entities/index.ts new file mode 100644 index 0000000..9120a7a --- /dev/null +++ b/libs/common/src/modules/product/entities/index.ts @@ -0,0 +1 @@ +export * from './product.entity'; diff --git a/libs/common/src/modules/product/entities/product.entity.ts b/libs/common/src/modules/product/entities/product.entity.ts new file mode 100644 index 0000000..5f04d66 --- /dev/null +++ b/libs/common/src/modules/product/entities/product.entity.ts @@ -0,0 +1,27 @@ +import { Column, Entity } from 'typeorm'; +import { ProductDto } from '../dtos'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; + +@Entity({ name: 'product' }) +export class ProductEntity extends AbstractEntity { + @Column({ + nullable: false, + }) + catName: string; + + @Column({ + nullable: false, + unique: true, + }) + public prodId: string; + + @Column({ + nullable: false, + }) + public prodType: string; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/product/product.repository.module.ts b/libs/common/src/modules/product/product.repository.module.ts new file mode 100644 index 0000000..6d92960 --- /dev/null +++ b/libs/common/src/modules/product/product.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ProductEntity } from './entities/product.entity'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [TypeOrmModule.forFeature([ProductEntity])], +}) +export class ProductRepositoryModule {} diff --git a/libs/common/src/modules/product/repositories/index.ts b/libs/common/src/modules/product/repositories/index.ts new file mode 100644 index 0000000..4d7899c --- /dev/null +++ b/libs/common/src/modules/product/repositories/index.ts @@ -0,0 +1 @@ +export * from './product.repository'; diff --git a/libs/common/src/modules/product/repositories/product.repository.ts b/libs/common/src/modules/product/repositories/product.repository.ts new file mode 100644 index 0000000..3244f52 --- /dev/null +++ b/libs/common/src/modules/product/repositories/product.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { ProductEntity } from '../entities/product.entity'; + +@Injectable() +export class ProductRepository extends Repository { + constructor(private dataSource: DataSource) { + super(ProductEntity, dataSource.createEntityManager()); + } +} diff --git a/src/auth/services/user-auth.service.ts b/src/auth/services/user-auth.service.ts index ae8c721..6c1e172 100644 --- a/src/auth/services/user-auth.service.ts +++ b/src/auth/services/user-auth.service.ts @@ -135,7 +135,7 @@ export class UserAuthService { } if (otp.expiryTime < new Date()) { - await this.otpRepository.delete(otp.id); + await this.otpRepository.delete(otp.uuid); throw new BadRequestException('OTP expired'); } diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 936b8e1..136e94f 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -57,9 +57,9 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':deviceId') - async getDevicesByDeviceId(@Param('deviceId') deviceId: string) { + async getDeviceDetailsByDeviceId(@Param('deviceId') deviceId: string) { try { - return await this.deviceService.getDevicesByDeviceId(deviceId); + return await this.deviceService.getDeviceDetailsByDeviceId(deviceId); } catch (err) { throw new Error(err); } @@ -67,9 +67,9 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':deviceId/functions') - async getDevicesInstructionByDeviceId(@Param('deviceId') deviceId: string) { + async getDeviceInstructionByDeviceId(@Param('deviceId') deviceId: string) { try { - return await this.deviceService.getDevicesInstructionByDeviceId(deviceId); + return await this.deviceService.getDeviceInstructionByDeviceId(deviceId); } catch (err) { throw new Error(err); } diff --git a/src/device/device.module.ts b/src/device/device.module.ts index 8bac0cf..d72db05 100644 --- a/src/device/device.module.ts +++ b/src/device/device.module.ts @@ -2,10 +2,12 @@ import { Module } from '@nestjs/common'; import { DeviceService } from './services/device.service'; import { DeviceController } from './controllers/device.controller'; import { ConfigModule } from '@nestjs/config'; +import { ProductRepositoryModule } from '@app/common/modules/product/product.repository.module'; +import { ProductRepository } from '@app/common/modules/product/repositories'; @Module({ - imports: [ConfigModule], + imports: [ConfigModule, ProductRepositoryModule], controllers: [DeviceController], - providers: [DeviceService], + providers: [DeviceService, ProductRepository], exports: [DeviceService], }) export class DeviceModule {} diff --git a/src/device/interfaces/get.device.interface.ts b/src/device/interfaces/get.device.interface.ts index e6f1361..30f57f8 100644 --- a/src/device/interfaces/get.device.interface.ts +++ b/src/device/interfaces/get.device.interface.ts @@ -1,15 +1,17 @@ -export class GetDeviceDetailsInterface { - result: object; +export interface GetDeviceDetailsInterface { + result: { + productId: string; + }; success: boolean; msg: string; } -export class GetDevicesByRoomIdInterface { +export interface GetDevicesByRoomIdInterface { success: boolean; msg: string; result: []; } -export class GetDevicesByGroupIdInterface { +export interface GetDevicesByGroupIdInterface { success: boolean; msg: string; result: { @@ -18,18 +20,18 @@ export class GetDevicesByGroupIdInterface { }; } -export class addDeviceInRoomInterface { +export interface addDeviceInRoomInterface { success: boolean; msg: string; result: boolean; } -export class controlDeviceInterface { +export interface controlDeviceInterface { success: boolean; result: boolean; msg: string; } -export class GetDeviceDetailsFunctionsInterface { +export interface GetDeviceDetailsFunctionsInterface { result: { category: string; functions: []; @@ -37,8 +39,26 @@ export class GetDeviceDetailsFunctionsInterface { success: boolean; msg: string; } -export class GetDeviceDetailsFunctionsStatusInterface { +export interface GetDeviceDetailsFunctionsStatusInterface { result: [{ id: string; status: [] }]; success: boolean; msg: string; } +export interface GetProductInterface { + productType: string; + productId: string; +} + +export interface DeviceInstructionResponse { + success: boolean; + result: { + productId: string; + productType: string; + functions: { + code: string; + values: any[]; + dataType: string; + }[]; + }; + msg: string; +} diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index e062076..cd00e7b 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -6,11 +6,13 @@ import { AddDeviceInRoomDto, } from '../dtos/add.device.dto'; import { + DeviceInstructionResponse, GetDeviceDetailsFunctionsInterface, GetDeviceDetailsFunctionsStatusInterface, GetDeviceDetailsInterface, GetDevicesByGroupIdInterface, GetDevicesByRoomIdInterface, + GetProductInterface, addDeviceInRoomInterface, controlDeviceInterface, } from '../interfaces/get.device.interface'; @@ -19,14 +21,18 @@ import { GetDeviceByRoomIdDto, } from '../dtos/get.device.dto'; import { ControlDeviceDto } from '../dtos/control.device.dto'; +import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; +import { ProductRepository } from '@app/common/modules/product/repositories'; @Injectable() export class DeviceService { private tuya: TuyaContext; - constructor(private readonly configService: ConfigService) { + constructor( + private readonly configService: ConfigService, + private readonly productRepository: ProductRepository, + ) { const accessKey = this.configService.get('auth-config.ACCESS_KEY'); const secretKey = this.configService.get('auth-config.SECRET_KEY'); - // const clientId = this.configService.get('auth-config.CLIENT_ID'); this.tuya = new TuyaContext({ baseUrl: 'https://openapi.tuyaeu.com', accessKey, @@ -45,7 +51,7 @@ export class DeviceService { }; } catch (error) { throw new HttpException( - 'Error fetching devices', + 'Error fetching devices by room', HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -67,7 +73,7 @@ export class DeviceService { return response as GetDevicesByRoomIdInterface; } catch (error) { throw new HttpException( - 'Error fetching devices ', + 'Error fetching devices by room from Tuya', HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -89,7 +95,7 @@ export class DeviceService { }; } catch (error) { throw new HttpException( - 'Error fetching devices', + 'Error fetching devices by group', HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -111,7 +117,7 @@ export class DeviceService { return response as GetDevicesByGroupIdInterface; } catch (error) { throw new HttpException( - 'Error fetching devices ', + 'Error fetching devices by group from Tuya', HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -149,7 +155,7 @@ export class DeviceService { return response as addDeviceInRoomInterface; } catch (error) { throw new HttpException( - 'Error adding device', + 'Error adding device in room from Tuya', HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -187,7 +193,7 @@ export class DeviceService { return response as addDeviceInRoomInterface; } catch (error) { throw new HttpException( - 'Error adding device', + 'Error adding device in group from Tuya', HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -223,15 +229,15 @@ export class DeviceService { return response as controlDeviceInterface; } catch (error) { throw new HttpException( - 'Error control device', + 'Error control device from Tuya', HttpStatus.INTERNAL_SERVER_ERROR, ); } } - async getDevicesByDeviceId(deviceId: string) { + async getDeviceDetailsByDeviceId(deviceId: string) { try { - const response = await this.getDevicesByDeviceIdTuya(deviceId); + const response = await this.getDeviceDetailsByDeviceIdTuya(deviceId); return { success: response.success, @@ -240,13 +246,12 @@ export class DeviceService { }; } catch (error) { throw new HttpException( - 'Error fetching device', + 'Error fetching device details', HttpStatus.INTERNAL_SERVER_ERROR, ); } } - - async getDevicesByDeviceIdTuya( + async getDeviceDetailsByDeviceIdTuya( deviceId: string, ): Promise { try { @@ -255,35 +260,109 @@ export class DeviceService { method: 'GET', path, }); - return response as GetDeviceDetailsInterface; + // Convert keys to camel case + const camelCaseResponse = convertKeysToCamelCase(response); + const productType: string = await this.getProductTypeByProductId( + camelCaseResponse.result.productId, + ); + + return { + result: { + ...camelCaseResponse.result, + productType: productType, + }, + success: camelCaseResponse.success, + msg: camelCaseResponse.msg, + } as GetDeviceDetailsInterface; } catch (error) { throw new HttpException( - 'Error fetching device ', + 'Error fetching device details from Tuya', HttpStatus.INTERNAL_SERVER_ERROR, ); } } - async getDevicesInstructionByDeviceId(deviceId: string) { + async getProductIdByDeviceId(deviceId: string) { try { - const response = await this.getDevicesInstructionByDeviceIdTuya(deviceId); + const deviceDetails: GetDeviceDetailsInterface = + await this.getDeviceDetailsByDeviceId(deviceId); + + return deviceDetails.result.productId; + } catch (error) { + throw new HttpException( + 'Error fetching product id by device id', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getProductByProductId(productId: string): Promise { + try { + const product = await this.productRepository + .createQueryBuilder('product') + .where('product.prodId = :productId', { productId }) + .select(['product.prodId', 'product.prodType']) + .getOne(); + + if (product) { + return { + productType: product.prodType, + productId: product.prodId, + }; + } else { + throw new HttpException('Product not found', HttpStatus.NOT_FOUND); + } + } catch (error) { + throw new HttpException( + 'Error fetching product by product id from db', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getProductTypeByProductId(productId: string) { + try { + const product = await this.getProductByProductId(productId); + return product.productType; + } catch (error) { + throw new HttpException( + 'Error getting product type by product id', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getDeviceInstructionByDeviceId( + deviceId: string, + ): Promise { + try { + const response = await this.getDeviceInstructionByDeviceIdTuya(deviceId); + + const productId: string = await this.getProductIdByDeviceId(deviceId); + const productType: string = + await this.getProductTypeByProductId(productId); return { success: response.success, result: { - category: response.result.category, - function: response.result.functions, + productId: productId, + productType: productType, + functions: response.result.functions.map((fun: any) => { + return { + code: fun.code, + values: fun.values, + dataType: fun.type, + }; + }), }, msg: response.msg, }; } catch (error) { throw new HttpException( - 'Error fetching device functions', + 'Error fetching device functions by device id', HttpStatus.INTERNAL_SERVER_ERROR, ); } } - async getDevicesInstructionByDeviceIdTuya( + async getDeviceInstructionByDeviceIdTuya( deviceId: string, ): Promise { try { @@ -295,23 +374,29 @@ export class DeviceService { return response as GetDeviceDetailsFunctionsInterface; } catch (error) { throw new HttpException( - 'Error fetching device functions', + 'Error fetching device functions from Tuya', HttpStatus.INTERNAL_SERVER_ERROR, ); } } async getDevicesInstructionStatus(deviceId: string) { try { - const response = await this.getDevicesInstructionStatusTuya(deviceId); - + const deviceStatus = await this.getDevicesInstructionStatusTuya(deviceId); + const productId: string = await this.getProductIdByDeviceId(deviceId); + const productType: string = + await this.getProductTypeByProductId(productId); return { - result: response.result, - success: response.success, - msg: response.msg, + result: { + productId: productId, + productType: productType, + status: deviceStatus.result[0].status, + }, + success: deviceStatus.success, + msg: deviceStatus.msg, }; } catch (error) { throw new HttpException( - 'Error fetching device functions', + 'Error fetching device functions status', HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -332,7 +417,7 @@ export class DeviceService { return response as unknown as GetDeviceDetailsFunctionsStatusInterface; } catch (error) { throw new HttpException( - 'Error fetching device functions', + 'Error fetching device functions status from Tuya', HttpStatus.INTERNAL_SERVER_ERROR, ); }