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 { UserSessionEntity } from '../modules/session/entities/session.entity';
import { UserOtpEntity } from '../modules/user-otp/entities'; import { UserOtpEntity } from '../modules/user-otp/entities';
import { HomeEntity } from '../modules/home/entities'; import { HomeEntity } from '../modules/home/entities';
import { ProductEntity } from '../modules/product/entities';
@Module({ @Module({
imports: [ imports: [
@ -20,7 +21,13 @@ import { HomeEntity } from '../modules/home/entities';
username: configService.get('DB_USER'), username: configService.get('DB_USER'),
password: configService.get('DB_PASSWORD'), password: configService.get('DB_PASSWORD'),
database: configService.get('DB_NAME'), database: configService.get('DB_NAME'),
entities: [UserEntity, UserSessionEntity, UserOtpEntity, HomeEntity], entities: [
UserEntity,
UserSessionEntity,
UserOtpEntity,
HomeEntity,
ProductEntity,
],
namingStrategy: new SnakeNamingStrategy(), namingStrategy: new SnakeNamingStrategy(),
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
logging: true, 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 { Exclude } from 'class-transformer';
import { import { CreateDateColumn, PrimaryColumn, UpdateDateColumn } from 'typeorm';
Column,
CreateDateColumn,
Generated,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { AbstractDto } from '../dtos'; import { AbstractDto } from '../dtos';
import { Constructor } from '../../../../../common/src/util/types'; import { Constructor } from '../../../../../common/src/util/types';
@ -14,12 +8,11 @@ export abstract class AbstractEntity<
T extends AbstractDto = AbstractDto, T extends AbstractDto = AbstractDto,
O = never, O = never,
> { > {
@PrimaryGeneratedColumn('increment') @PrimaryColumn({
type: 'uuid',
generated: 'uuid',
})
@Exclude() @Exclude()
public id: number;
@Column()
@Generated('uuid')
public uuid: string; public uuid: string;
@CreateDateColumn({ type: 'timestamp' }) @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()) { if (otp.expiryTime < new Date()) {
await this.otpRepository.delete(otp.id); await this.otpRepository.delete(otp.uuid);
throw new BadRequestException('OTP expired'); throw new BadRequestException('OTP expired');
} }

View File

@ -57,9 +57,9 @@ export class DeviceController {
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get(':deviceId') @Get(':deviceId')
async getDevicesByDeviceId(@Param('deviceId') deviceId: string) { async getDeviceDetailsByDeviceId(@Param('deviceId') deviceId: string) {
try { try {
return await this.deviceService.getDevicesByDeviceId(deviceId); return await this.deviceService.getDeviceDetailsByDeviceId(deviceId);
} catch (err) { } catch (err) {
throw new Error(err); throw new Error(err);
} }
@ -67,9 +67,9 @@ export class DeviceController {
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get(':deviceId/functions') @Get(':deviceId/functions')
async getDevicesInstructionByDeviceId(@Param('deviceId') deviceId: string) { async getDeviceInstructionByDeviceId(@Param('deviceId') deviceId: string) {
try { try {
return await this.deviceService.getDevicesInstructionByDeviceId(deviceId); return await this.deviceService.getDeviceInstructionByDeviceId(deviceId);
} catch (err) { } catch (err) {
throw new Error(err); throw new Error(err);
} }

View File

@ -2,10 +2,12 @@ import { Module } from '@nestjs/common';
import { DeviceService } from './services/device.service'; import { DeviceService } from './services/device.service';
import { DeviceController } from './controllers/device.controller'; import { DeviceController } from './controllers/device.controller';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { ProductRepositoryModule } from '@app/common/modules/product/product.repository.module';
import { ProductRepository } from '@app/common/modules/product/repositories';
@Module({ @Module({
imports: [ConfigModule], imports: [ConfigModule, ProductRepositoryModule],
controllers: [DeviceController], controllers: [DeviceController],
providers: [DeviceService], providers: [DeviceService, ProductRepository],
exports: [DeviceService], exports: [DeviceService],
}) })
export class DeviceModule {} export class DeviceModule {}

View File

@ -1,15 +1,17 @@
export class GetDeviceDetailsInterface { export interface GetDeviceDetailsInterface {
result: object; result: {
productId: string;
};
success: boolean; success: boolean;
msg: string; msg: string;
} }
export class GetDevicesByRoomIdInterface { export interface GetDevicesByRoomIdInterface {
success: boolean; success: boolean;
msg: string; msg: string;
result: []; result: [];
} }
export class GetDevicesByGroupIdInterface { export interface GetDevicesByGroupIdInterface {
success: boolean; success: boolean;
msg: string; msg: string;
result: { result: {
@ -18,18 +20,18 @@ export class GetDevicesByGroupIdInterface {
}; };
} }
export class addDeviceInRoomInterface { export interface addDeviceInRoomInterface {
success: boolean; success: boolean;
msg: string; msg: string;
result: boolean; result: boolean;
} }
export class controlDeviceInterface { export interface controlDeviceInterface {
success: boolean; success: boolean;
result: boolean; result: boolean;
msg: string; msg: string;
} }
export class GetDeviceDetailsFunctionsInterface { export interface GetDeviceDetailsFunctionsInterface {
result: { result: {
category: string; category: string;
functions: []; functions: [];
@ -37,8 +39,26 @@ export class GetDeviceDetailsFunctionsInterface {
success: boolean; success: boolean;
msg: string; msg: string;
} }
export class GetDeviceDetailsFunctionsStatusInterface { export interface GetDeviceDetailsFunctionsStatusInterface {
result: [{ id: string; status: [] }]; result: [{ id: string; status: [] }];
success: boolean; success: boolean;
msg: string; 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, AddDeviceInRoomDto,
} from '../dtos/add.device.dto'; } from '../dtos/add.device.dto';
import { import {
DeviceInstructionResponse,
GetDeviceDetailsFunctionsInterface, GetDeviceDetailsFunctionsInterface,
GetDeviceDetailsFunctionsStatusInterface, GetDeviceDetailsFunctionsStatusInterface,
GetDeviceDetailsInterface, GetDeviceDetailsInterface,
GetDevicesByGroupIdInterface, GetDevicesByGroupIdInterface,
GetDevicesByRoomIdInterface, GetDevicesByRoomIdInterface,
GetProductInterface,
addDeviceInRoomInterface, addDeviceInRoomInterface,
controlDeviceInterface, controlDeviceInterface,
} from '../interfaces/get.device.interface'; } from '../interfaces/get.device.interface';
@ -19,14 +21,18 @@ import {
GetDeviceByRoomIdDto, GetDeviceByRoomIdDto,
} from '../dtos/get.device.dto'; } from '../dtos/get.device.dto';
import { ControlDeviceDto } from '../dtos/control.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() @Injectable()
export class DeviceService { export class DeviceService {
private tuya: TuyaContext; 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 accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
const secretKey = this.configService.get<string>('auth-config.SECRET_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({ this.tuya = new TuyaContext({
baseUrl: 'https://openapi.tuyaeu.com', baseUrl: 'https://openapi.tuyaeu.com',
accessKey, accessKey,
@ -45,7 +51,7 @@ export class DeviceService {
}; };
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error fetching devices', 'Error fetching devices by room',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
@ -67,7 +73,7 @@ export class DeviceService {
return response as GetDevicesByRoomIdInterface; return response as GetDevicesByRoomIdInterface;
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error fetching devices ', 'Error fetching devices by room from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
@ -89,7 +95,7 @@ export class DeviceService {
}; };
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error fetching devices', 'Error fetching devices by group',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
@ -111,7 +117,7 @@ export class DeviceService {
return response as GetDevicesByGroupIdInterface; return response as GetDevicesByGroupIdInterface;
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error fetching devices ', 'Error fetching devices by group from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
@ -149,7 +155,7 @@ export class DeviceService {
return response as addDeviceInRoomInterface; return response as addDeviceInRoomInterface;
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error adding device', 'Error adding device in room from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
@ -187,7 +193,7 @@ export class DeviceService {
return response as addDeviceInRoomInterface; return response as addDeviceInRoomInterface;
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error adding device', 'Error adding device in group from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
@ -223,15 +229,15 @@ export class DeviceService {
return response as controlDeviceInterface; return response as controlDeviceInterface;
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error control device', 'Error control device from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
} }
async getDevicesByDeviceId(deviceId: string) { async getDeviceDetailsByDeviceId(deviceId: string) {
try { try {
const response = await this.getDevicesByDeviceIdTuya(deviceId); const response = await this.getDeviceDetailsByDeviceIdTuya(deviceId);
return { return {
success: response.success, success: response.success,
@ -240,13 +246,12 @@ export class DeviceService {
}; };
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error fetching device', 'Error fetching device details',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
} }
async getDeviceDetailsByDeviceIdTuya(
async getDevicesByDeviceIdTuya(
deviceId: string, deviceId: string,
): Promise<GetDeviceDetailsInterface> { ): Promise<GetDeviceDetailsInterface> {
try { try {
@ -255,35 +260,109 @@ export class DeviceService {
method: 'GET', method: 'GET',
path, 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) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error fetching device ', 'Error fetching device details from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
} }
async getDevicesInstructionByDeviceId(deviceId: string) { async getProductIdByDeviceId(deviceId: string) {
try { 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 { return {
success: response.success, success: response.success,
result: { result: {
category: response.result.category, productId: productId,
function: response.result.functions, productType: productType,
functions: response.result.functions.map((fun: any) => {
return {
code: fun.code,
values: fun.values,
dataType: fun.type,
};
}),
}, },
msg: response.msg, msg: response.msg,
}; };
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error fetching device functions', 'Error fetching device functions by device id',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
} }
async getDevicesInstructionByDeviceIdTuya( async getDeviceInstructionByDeviceIdTuya(
deviceId: string, deviceId: string,
): Promise<GetDeviceDetailsFunctionsInterface> { ): Promise<GetDeviceDetailsFunctionsInterface> {
try { try {
@ -295,23 +374,29 @@ export class DeviceService {
return response as GetDeviceDetailsFunctionsInterface; return response as GetDeviceDetailsFunctionsInterface;
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error fetching device functions', 'Error fetching device functions from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
} }
async getDevicesInstructionStatus(deviceId: string) { async getDevicesInstructionStatus(deviceId: string) {
try { 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 { return {
result: response.result, result: {
success: response.success, productId: productId,
msg: response.msg, productType: productType,
status: deviceStatus.result[0].status,
},
success: deviceStatus.success,
msg: deviceStatus.msg,
}; };
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error fetching device functions', 'Error fetching device functions status',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
@ -332,7 +417,7 @@ export class DeviceService {
return response as unknown as GetDeviceDetailsFunctionsStatusInterface; return response as unknown as GetDeviceDetailsFunctionsStatusInterface;
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error fetching device functions', 'Error fetching device functions status from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }