mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-11-26 07:54:53 +00:00
Merge pull request #18 from SyncrowIOT/device-config-product-type
Device config product type
This commit is contained in:
@ -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,
|
||||
|
||||
20
libs/common/src/helper/camelCaseConverter.ts
Normal file
20
libs/common/src/helper/camelCaseConverter.ts
Normal 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;
|
||||
}
|
||||
@ -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' })
|
||||
|
||||
1
libs/common/src/modules/product/dtos/index.ts
Normal file
1
libs/common/src/modules/product/dtos/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './product.dto';
|
||||
19
libs/common/src/modules/product/dtos/product.dto.ts
Normal file
19
libs/common/src/modules/product/dtos/product.dto.ts
Normal 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;
|
||||
}
|
||||
1
libs/common/src/modules/product/entities/index.ts
Normal file
1
libs/common/src/modules/product/entities/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './product.entity';
|
||||
27
libs/common/src/modules/product/entities/product.entity.ts
Normal file
27
libs/common/src/modules/product/entities/product.entity.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
11
libs/common/src/modules/product/product.repository.module.ts
Normal file
11
libs/common/src/modules/product/product.repository.module.ts
Normal 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 {}
|
||||
1
libs/common/src/modules/product/repositories/index.ts
Normal file
1
libs/common/src/modules/product/repositories/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './product.repository';
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -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');
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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 {}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user