Merge pull request #18 from SyncrowIOT/device-config-product-type

Device config product type
This commit is contained in:
Ammar Qaffaf
2024-03-31 11:56:41 +03:00
committed by GitHub
15 changed files with 255 additions and 58 deletions

View File

@ -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,

View File

@ -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;
}

View File

@ -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' })

View File

@ -0,0 +1 @@
export * from './product.dto';

View File

@ -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;
}

View File

@ -0,0 +1 @@
export * from './product.entity';

View File

@ -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<ProductDto> {
@Column({
nullable: false,
})
catName: string;
@Column({
nullable: false,
unique: true,
})
public prodId: string;
@Column({
nullable: false,
})
public prodType: string;
constructor(partial: Partial<ProductEntity>) {
super();
Object.assign(this, partial);
}
}

View File

@ -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 {}

View File

@ -0,0 +1 @@
export * from './product.repository';

View File

@ -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<ProductEntity> {
constructor(private dataSource: DataSource) {
super(ProductEntity, dataSource.createEntityManager());
}
}

View File

@ -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');
}

View File

@ -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);
}

View File

@ -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 {}

View File

@ -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;
}

View File

@ -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<string>('auth-config.ACCESS_KEY');
const secretKey = this.configService.get<string>('auth-config.SECRET_KEY');
// const clientId = this.configService.get<string>('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<GetDeviceDetailsInterface> {
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<GetProductInterface> {
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<DeviceInstructionResponse> {
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<GetDeviceDetailsFunctionsInterface> {
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,
);
}