From 8cb131f395ce119211aa03fd6851683442cf3f1b Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:14:58 +0300 Subject: [PATCH] test --- libs/common/src/config/mongoose.config.ts | 8 + libs/common/src/database/database.module.ts | 10 + libs/common/src/helper/helper.module.ts | 7 +- .../services/tuya.web.socket.service.ts | 15 +- .../src/mongoose/database/mongoose.config.ts | 23 ++ .../controllers/devices-status.controller.ts | 27 ++ .../devices-status/devices-status.module.ts | 23 ++ .../dtos/add.devices-status.dto.ts | 38 ++ .../devices-status.repository.interface.ts | 6 + .../repository/devices-status.repository.ts | 49 +++ .../schema/devices-status.schema.ts | 20 + .../services/devices-status.service.ts | 140 +++++++ .../src/mongoose/mongoose.shared.module.ts | 9 + package-lock.json | 363 ++++++++++++++++++ package.json | 2 + src/automation/automation.module.ts | 3 +- src/device/device.module.ts | 8 +- src/device/services/device.service.ts | 20 +- src/scene/scene.module.ts | 3 +- 19 files changed, 760 insertions(+), 14 deletions(-) create mode 100644 libs/common/src/config/mongoose.config.ts create mode 100644 libs/common/src/mongoose/database/mongoose.config.ts create mode 100644 libs/common/src/mongoose/devices-status/controllers/devices-status.controller.ts create mode 100644 libs/common/src/mongoose/devices-status/devices-status.module.ts create mode 100644 libs/common/src/mongoose/devices-status/dtos/add.devices-status.dto.ts create mode 100644 libs/common/src/mongoose/devices-status/interfaces/devices-status.repository.interface.ts create mode 100644 libs/common/src/mongoose/devices-status/repository/devices-status.repository.ts create mode 100644 libs/common/src/mongoose/devices-status/schema/devices-status.schema.ts create mode 100644 libs/common/src/mongoose/devices-status/services/devices-status.service.ts create mode 100644 libs/common/src/mongoose/mongoose.shared.module.ts diff --git a/libs/common/src/config/mongoose.config.ts b/libs/common/src/config/mongoose.config.ts new file mode 100644 index 0000000..6292f3c --- /dev/null +++ b/libs/common/src/config/mongoose.config.ts @@ -0,0 +1,8 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs( + 'mongoose-config', + (): Record => ({ + MONGODB_URI: process.env.MONGODB_URI, + }), +); diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index e6ddb6c..a4fa323 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -18,6 +18,9 @@ import { UserNotificationEntity } from '../modules/user-notification/entities'; import { DeviceNotificationEntity } from '../modules/device-notification/entities'; import { RegionEntity } from '../modules/region/entities'; import { TimeZoneEntity } from '../modules/timezone/entities'; +import { MongooseModule } from '@nestjs/mongoose'; +import { mongooseConfig } from '../mongoose/database/mongoose.config'; +import { DeviceStatusMongoModule } from '../mongoose/devices-status/devices-status.module'; @Module({ imports: [ @@ -65,6 +68,13 @@ import { TimeZoneEntity } from '../modules/timezone/entities'; ssl: Boolean(JSON.parse(configService.get('DB_SSL'))), }), }), + MongooseModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: async (configService: ConfigService) => + mongooseConfig(configService), + }), + DeviceStatusMongoModule, ], }) export class DatabaseModule {} diff --git a/libs/common/src/helper/helper.module.ts b/libs/common/src/helper/helper.module.ts index 3378051..75ec360 100644 --- a/libs/common/src/helper/helper.module.ts +++ b/libs/common/src/helper/helper.module.ts @@ -9,6 +9,7 @@ import { OneSignalService } from './services/onesignal.service'; import { DeviceMessagesService } from './services/device.messages.service'; import { DeviceNotificationRepositoryModule } from '../modules/device-notification/device.notification.module'; import { DeviceNotificationRepository } from '../modules/device-notification/repositories'; +import { DeviceStatusMongoModule } from '../mongoose/devices-status/devices-status.module'; @Global() @Module({ @@ -23,6 +24,10 @@ import { DeviceNotificationRepository } from '../modules/device-notification/rep ], exports: [HelperHashService, SpacePermissionService], controllers: [], - imports: [SpaceRepositoryModule, DeviceNotificationRepositoryModule], + imports: [ + SpaceRepositoryModule, + DeviceNotificationRepositoryModule, + DeviceStatusMongoModule, + ], }) export class HelperModule {} diff --git a/libs/common/src/helper/services/tuya.web.socket.service.ts b/libs/common/src/helper/services/tuya.web.socket.service.ts index cca7fc6..779cc35 100644 --- a/libs/common/src/helper/services/tuya.web.socket.service.ts +++ b/libs/common/src/helper/services/tuya.web.socket.service.ts @@ -1,8 +1,7 @@ import { Injectable } from '@nestjs/common'; import TuyaWebsocket from '../../config/tuya-web-socket-config'; import { ConfigService } from '@nestjs/config'; -import { OneSignalService } from './onesignal.service'; -import { DeviceMessagesService } from './device.messages.service'; +import { DeviceStatusMongoService } from '@app/common/mongoose/devices-status/services/devices-status.service'; @Injectable() export class TuyaWebSocketService { @@ -10,8 +9,7 @@ export class TuyaWebSocketService { constructor( private readonly configService: ConfigService, - private readonly oneSignalService: OneSignalService, - private readonly deviceMessagesService: DeviceMessagesService, + private readonly deviceStatusMongoService: DeviceStatusMongoService, ) { // Initialize the TuyaWebsocket client this.client = new TuyaWebsocket({ @@ -39,10 +37,11 @@ export class TuyaWebSocketService { this.client.message(async (ws: WebSocket, message: any) => { try { - await this.deviceMessagesService.getDevicesUserNotifications( - message.payload.data.bizData.devId, - message.payload.data.bizData, - ); + await this.deviceStatusMongoService.addDeviceStatusToMongo({ + deviceTuyaUuid: message.payload.data.bizData.devId, + status: message.payload.data.bizData.properties, + }); + this.client.ackMessage(message.messageId); } catch (error) { console.error('Error processing message:', error); diff --git a/libs/common/src/mongoose/database/mongoose.config.ts b/libs/common/src/mongoose/database/mongoose.config.ts new file mode 100644 index 0000000..dfb3ed8 --- /dev/null +++ b/libs/common/src/mongoose/database/mongoose.config.ts @@ -0,0 +1,23 @@ +import { MongooseModuleOptions } from '@nestjs/mongoose'; +import { ConfigService } from '@nestjs/config'; + +export const mongooseConfig = async ( + configService: ConfigService, +): Promise => { + return { + uri: configService.get('MONGODB_URI'), + dbName: 'syncrow-dev', // Specify your database name here + connectionFactory: (connection) => { + connection.on('connected', () => { + console.log('Mongoose connected to MongoDB'); + }); + connection.on('error', (err) => { + console.error('Mongoose connection error:', err); + }); + connection.on('disconnected', () => { + console.log('Mongoose disconnected from MongoDB'); + }); + return connection; + }, + }; +}; diff --git a/libs/common/src/mongoose/devices-status/controllers/devices-status.controller.ts b/libs/common/src/mongoose/devices-status/controllers/devices-status.controller.ts new file mode 100644 index 0000000..6cd98f6 --- /dev/null +++ b/libs/common/src/mongoose/devices-status/controllers/devices-status.controller.ts @@ -0,0 +1,27 @@ +import { Controller, Post, Param } from '@nestjs/common'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { AddDeviceStatusDto } from '../dtos/add.devices-status.dto'; +import { DeviceStatusMongoService } from '../services/devices-status.service'; + +@ApiTags('Device Status Mongo Module') +@Controller({ + version: '1', + path: 'device-status-mongo', +}) +export class DeviceStatusMongoController { + constructor( + private readonly deviceStatusMongoService: DeviceStatusMongoService, + ) {} + + @ApiBearerAuth() + @Post(':deviceTuyaUuid') + async addDeviceStatus( + @Param('deviceTuyaUuid') deviceTuyaUuid: string, + ): Promise { + return this.deviceStatusMongoService.addDeviceStatusByDeviceUuid( + deviceTuyaUuid, + ); + } + + // Add other endpoints as needed +} diff --git a/libs/common/src/mongoose/devices-status/devices-status.module.ts b/libs/common/src/mongoose/devices-status/devices-status.module.ts new file mode 100644 index 0000000..3e9a0bf --- /dev/null +++ b/libs/common/src/mongoose/devices-status/devices-status.module.ts @@ -0,0 +1,23 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { DeviceStatusRepository } from './repository/devices-status.repository'; +import { DeviceStatusSchema } from './schema/devices-status.schema'; +import { DeviceStatusMongoController } from './controllers/devices-status.controller'; +import { DeviceStatusMongoService } from './services/devices-status.service'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; + +@Module({ + imports: [ + MongooseModule.forFeature([ + { name: 'DeviceStatus', schema: DeviceStatusSchema }, + ]), + ], + providers: [ + DeviceStatusMongoService, + DeviceStatusRepository, + DeviceRepository, + ], + controllers: [DeviceStatusMongoController], + exports: [DeviceStatusMongoService, DeviceStatusRepository], +}) +export class DeviceStatusMongoModule {} diff --git a/libs/common/src/mongoose/devices-status/dtos/add.devices-status.dto.ts b/libs/common/src/mongoose/devices-status/dtos/add.devices-status.dto.ts new file mode 100644 index 0000000..9ad7a11 --- /dev/null +++ b/libs/common/src/mongoose/devices-status/dtos/add.devices-status.dto.ts @@ -0,0 +1,38 @@ +import { + IsString, + IsArray, + ValidateNested, + IsNotEmpty, + IsOptional, +} from 'class-validator'; +import { Type } from 'class-transformer'; + +class StatusDto { + @IsString() + code: string; + + @IsNotEmpty() + value: any; +} + +export class AddDeviceStatusDto { + @IsString() + @IsOptional() + deviceUuid?: string; + + @IsString() + deviceTuyaUuid: string; + + @IsString() + @IsOptional() + productUuid?: string; + + @IsString() + @IsOptional() + productType?: string; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => StatusDto) + status: StatusDto[]; +} diff --git a/libs/common/src/mongoose/devices-status/interfaces/devices-status.repository.interface.ts b/libs/common/src/mongoose/devices-status/interfaces/devices-status.repository.interface.ts new file mode 100644 index 0000000..0750c3f --- /dev/null +++ b/libs/common/src/mongoose/devices-status/interfaces/devices-status.repository.interface.ts @@ -0,0 +1,6 @@ +import { Document } from 'mongoose'; + +export interface IRepository { + create(dto: Partial): Promise; + // Add more methods as needed (e.g., find, update, delete) +} diff --git a/libs/common/src/mongoose/devices-status/repository/devices-status.repository.ts b/libs/common/src/mongoose/devices-status/repository/devices-status.repository.ts new file mode 100644 index 0000000..a515842 --- /dev/null +++ b/libs/common/src/mongoose/devices-status/repository/devices-status.repository.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { AddDeviceStatusInterface } from '../schema/devices-status.schema'; +import { AddDeviceStatusDto } from '../dtos/add.devices-status.dto'; + +@Injectable() +export class DeviceStatusRepository { + constructor( + @InjectModel('DeviceStatus') + private readonly deviceStatusModel: Model, + ) {} + + async addDeviceStatus( + addDeviceStatusDto: AddDeviceStatusDto, + ): Promise { + const { deviceTuyaUuid, status } = addDeviceStatusDto; + + // Update or insert the device document + await this.deviceStatusModel.findOneAndUpdate( + { deviceTuyaUuid }, + { + $set: { + deviceUuid: addDeviceStatusDto.deviceUuid, + productUuid: addDeviceStatusDto.productUuid, + productType: addDeviceStatusDto.productType, + }, + }, + { upsert: true, new: true, runValidators: true }, + ); + + // Update the status array + for (const statusItem of status) { + await this.deviceStatusModel.updateOne( + { deviceTuyaUuid, 'status.code': statusItem.code }, // Filter to find if the code already exists + { $set: { 'status.$.value': statusItem.value } }, // Update the value if code exists + ); + + // If the code doesn't exist, add it to the array + await this.deviceStatusModel.updateOne( + { deviceTuyaUuid, 'status.code': { $ne: statusItem.code } }, // Check if the code does not exist + { $push: { status: statusItem } }, // Add the new status item + ); + } + + // Return the updated document + return this.deviceStatusModel.findOne({ deviceTuyaUuid }); + } +} diff --git a/libs/common/src/mongoose/devices-status/schema/devices-status.schema.ts b/libs/common/src/mongoose/devices-status/schema/devices-status.schema.ts new file mode 100644 index 0000000..2ae0041 --- /dev/null +++ b/libs/common/src/mongoose/devices-status/schema/devices-status.schema.ts @@ -0,0 +1,20 @@ +import { Schema, Document } from 'mongoose'; + +export interface AddDeviceStatusInterface extends Document { + deviceUuid: string; + deviceTuyaUuid: string; + productUuid: string; + productType: string; + status: []; +} + +export const DeviceStatusSchema = new Schema( + { + deviceUuid: { type: String, required: true }, + deviceTuyaUuid: { type: String, required: true }, + productUuid: { type: String, required: true }, + productType: { type: String, required: true }, + status: [], + }, + { collection: 'devices-status' }, +); diff --git a/libs/common/src/mongoose/devices-status/services/devices-status.service.ts b/libs/common/src/mongoose/devices-status/services/devices-status.service.ts new file mode 100644 index 0000000..3715975 --- /dev/null +++ b/libs/common/src/mongoose/devices-status/services/devices-status.service.ts @@ -0,0 +1,140 @@ +import { + HttpException, + HttpStatus, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { DeviceStatusRepository } from '../repository/devices-status.repository'; +import { AddDeviceStatusDto } from '../dtos/add.devices-status.dto'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { GetDeviceDetailsFunctionsStatusInterface } from 'src/device/interfaces/get.device.interface'; +import { TuyaContext } from '@tuya/tuya-connector-nodejs'; +import { ConfigService } from '@nestjs/config'; +@Injectable() +export class DeviceStatusMongoService { + private tuya: TuyaContext; + constructor( + private readonly configService: ConfigService, + private readonly deviceStatusRepository: DeviceStatusRepository, + private readonly deviceRepository: DeviceRepository, + ) { + 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 addDeviceStatusByDeviceUuid( + deviceTuyaUuid: string, + ): Promise { + try { + const device = await this.getDeviceByDeviceTuyaUuid(deviceTuyaUuid); + if (device.uuid) { + const deviceStatus = await this.getDevicesInstructionStatus( + device.uuid, + ); + if (deviceStatus.productUuid) { + const deviceStatusSaved = await this.addDeviceStatusToMongo({ + deviceUuid: device.uuid, + deviceTuyaUuid: deviceTuyaUuid, + status: deviceStatus.status, + productUuid: deviceStatus.productUuid, + productType: deviceStatus.productType, + }); + + return deviceStatusSaved; + } + } + } catch (error) { + throw new HttpException( + 'Device Tuya UUID not found', + error.status || HttpStatus.BAD_REQUEST, + ); + } + } + async addDeviceStatusToMongo( + addDeviceStatusDto: AddDeviceStatusDto, + ): Promise { + try { + const device = await this.getDeviceByDeviceTuyaUuid( + addDeviceStatusDto.deviceTuyaUuid, + ); + if (device?.uuid) { + return await this.deviceStatusRepository.addDeviceStatus( + addDeviceStatusDto, + ); + } + // Return null if device not found or no UUID + return null; + } catch (error) { + // Handle the error silently, perhaps log it internally or ignore it + return null; + } + } + + async getDeviceByDeviceTuyaUuid(deviceTuyaUuid: string) { + return await this.deviceRepository.findOne({ + where: { + deviceTuyaUuid, + }, + relations: ['productDevice'], + }); + } + async getDevicesInstructionStatus(deviceUuid: string) { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + + if (!deviceDetails) { + throw new NotFoundException('Device Not Found'); + } + const deviceStatus = await this.getDevicesInstructionStatusTuya( + deviceDetails.deviceTuyaUuid, + ); + + return { + productUuid: deviceDetails.productDevice.uuid, + productType: deviceDetails.productDevice.prodType, + status: deviceStatus.result[0].status, + }; + } catch (error) { + throw new HttpException( + 'Error fetching device functions status', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getDevicesInstructionStatusTuya( + deviceUuid: string, + ): Promise { + try { + const path = `/v1.0/iot-03/devices/status`; + const response = await this.tuya.request({ + method: 'GET', + path, + query: { + device_ids: deviceUuid, + }, + }); + return response as GetDeviceDetailsFunctionsStatusInterface; + } catch (error) { + throw new HttpException( + 'Error fetching device functions status from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getDeviceByDeviceUuid( + deviceUuid: string, + withProductDevice: boolean = true, + ) { + return await this.deviceRepository.findOne({ + where: { + uuid: deviceUuid, + }, + ...(withProductDevice && { relations: ['productDevice'] }), + }); + } +} diff --git a/libs/common/src/mongoose/mongoose.shared.module.ts b/libs/common/src/mongoose/mongoose.shared.module.ts new file mode 100644 index 0000000..de79714 --- /dev/null +++ b/libs/common/src/mongoose/mongoose.shared.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { DeviceStatusMongoModule } from './devices-status/devices-status.module'; // Adjust path as needed + +@Module({ + imports: [DeviceStatusMongoModule], + providers: [], // Providers specific to MongooseSharedModule, if any + exports: [], // Export anything specific to MongooseSharedModule, if needed +}) +export class MongooseSharedModule {} diff --git a/package-lock.json b/package-lock.json index 7267182..b5ac870 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@nestjs/config": "^3.2.0", "@nestjs/core": "^10.0.0", "@nestjs/jwt": "^10.2.0", + "@nestjs/mongoose": "^10.0.10", "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.3.0", @@ -28,6 +29,7 @@ "express-rate-limit": "^7.1.5", "helmet": "^7.1.0", "ioredis": "^5.3.2", + "mongoose": "^8.5.1", "morgan": "^1.10.0", "nodemailer": "^6.9.10", "onesignal-node": "^3.4.0", @@ -1710,6 +1712,14 @@ "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==" }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.8.tgz", + "integrity": "sha512-qKwC/M/nNNaKUBMQ0nuzm47b7ZYWQHN3pcXq4IIcoSBc2hOIrflAxJduIvvqmhoz3gR2TacTAs8vlsCVPkiEdQ==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nestjs/cli": { "version": "10.3.2", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.3.2.tgz", @@ -1938,6 +1948,17 @@ } } }, + "node_modules/@nestjs/mongoose": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@nestjs/mongoose/-/mongoose-10.0.10.tgz", + "integrity": "sha512-3Ff60ock8nwlAJC823TG91Qy+Qc6av+ddIb6n6wlFsTK0akDF/aTcagX8cF8uI8mWxCWjEwEsgv99vo6p0yJ+w==", + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", + "mongoose": "^6.0.2 || ^7.0.0 || ^8.0.0", + "rxjs": "^7.0.0" + } + }, "node_modules/@nestjs/passport": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-10.0.3.tgz", @@ -2506,6 +2527,22 @@ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.9.tgz", "integrity": "sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw==" }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -3486,6 +3523,14 @@ "node-int64": "^0.4.0" } }, + "node_modules/bson": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz", + "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -5861,6 +5906,34 @@ "url": "https://opencollective.com/ioredis" } }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "optional": true, + "peer": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "optional": true, + "peer": true + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "optional": true, + "peer": true + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -6930,6 +7003,14 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -7154,6 +7235,11 @@ "node": ">= 4.0.0" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -7287,6 +7373,225 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mongodb": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", + "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==", + "optional": true, + "peer": true, + "dependencies": { + "bson": "^5.5.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "optionalDependencies": { + "@mongodb-js/saslprep": "^1.1.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.0.0", + "kerberos": "^1.0.0 || ^2.0.0", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "optional": true, + "peer": true, + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "optional": true, + "peer": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "optional": true, + "peer": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb/node_modules/bson": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", + "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==", + "optional": true, + "peer": true, + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongoose": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.5.1.tgz", + "integrity": "sha512-OhVcwVl91A1G6+XpjDcpkGP7l7ikZkxa0DylX7NT/lcEqAjggzSdqDxb48A+xsDxqNAr0ntSJ1yiE3+KJTOd5Q==", + "dependencies": { + "bson": "^6.7.0", + "kareem": "2.6.3", + "mongodb": "6.7.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/mongoose/node_modules/mongodb": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.7.0.tgz", + "integrity": "sha512-TMKyHdtMcO0fYBNORiYdmM25ijsHs+Njs963r4Tro4OQZzqYigAzYQouwWRg4OIaiLRUEGUh/1UAcH5lxdSLIA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.7.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongoose/node_modules/mongodb-connection-string-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", + "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mongoose/node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/mongoose/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/mongoose/node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -7326,6 +7631,25 @@ "node": ">= 0.8" } }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -8936,6 +9260,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -8962,6 +9291,32 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "optional": true, + "peer": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -8990,6 +9345,14 @@ "node": ">=0.10.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/spawn-command": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", diff --git a/package.json b/package.json index 52c5cf7..0c8fb38 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@nestjs/config": "^3.2.0", "@nestjs/core": "^10.0.0", "@nestjs/jwt": "^10.2.0", + "@nestjs/mongoose": "^10.0.10", "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.3.0", @@ -39,6 +40,7 @@ "express-rate-limit": "^7.1.5", "helmet": "^7.1.0", "ioredis": "^5.3.2", + "mongoose": "^8.5.1", "morgan": "^1.10.0", "nodemailer": "^6.9.10", "onesignal-node": "^3.4.0", diff --git a/src/automation/automation.module.ts b/src/automation/automation.module.ts index ebb8bf0..7139f6c 100644 --- a/src/automation/automation.module.ts +++ b/src/automation/automation.module.ts @@ -7,9 +7,10 @@ import { SpaceRepository } from '@app/common/modules/space/repositories'; import { DeviceService } from 'src/device/services'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { ProductRepository } from '@app/common/modules/product/repositories'; +import { DeviceStatusMongoModule } from '@app/common/mongoose/devices-status/devices-status.module'; @Module({ - imports: [ConfigModule, SpaceRepositoryModule], + imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusMongoModule], controllers: [AutomationController], providers: [ AutomationService, diff --git a/src/device/device.module.ts b/src/device/device.module.ts index 07d1ad0..ff9b7fb 100644 --- a/src/device/device.module.ts +++ b/src/device/device.module.ts @@ -10,8 +10,14 @@ import { PermissionTypeRepository } from '@app/common/modules/permission/reposit import { SpaceRepository } from '@app/common/modules/space/repositories'; import { DeviceUserPermissionRepository } from '@app/common/modules/device-user-permission/repositories'; import { UserRepository } from '@app/common/modules/user/repositories'; +import { DeviceStatusMongoModule } from '@app/common/mongoose/devices-status/devices-status.module'; @Module({ - imports: [ConfigModule, ProductRepositoryModule, DeviceRepositoryModule], + imports: [ + ConfigModule, + ProductRepositoryModule, + DeviceRepositoryModule, + DeviceStatusMongoModule, + ], controllers: [DeviceController], providers: [ DeviceService, diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 0848bb3..fdf9aeb 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -25,6 +25,7 @@ import { PermissionType } from '@app/common/constants/permission-type.enum'; import { In } from 'typeorm'; import { ProductType } from '@app/common/constants/product-type.enum'; import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { DeviceStatusMongoService } from '@app/common/mongoose/devices-status/services/devices-status.service'; @Injectable() export class DeviceService { @@ -33,6 +34,7 @@ export class DeviceService { private readonly configService: ConfigService, private readonly deviceRepository: DeviceRepository, private readonly productRepository: ProductRepository, + private readonly deviceStatusMongoService: DeviceStatusMongoService, private readonly spaceRepository: SpaceRepository, ) { const accessKey = this.configService.get('auth-config.ACCESS_KEY'); @@ -72,14 +74,28 @@ export class DeviceService { if (!device.productUuid) { throw new Error('Product UUID is missing for the device.'); } - - return await this.deviceRepository.save({ + const deviceSaved = await this.deviceRepository.save({ deviceTuyaUuid: addDeviceDto.deviceTuyaUuid, productDevice: { uuid: device.productUuid }, user: { uuid: addDeviceDto.userUuid, }, }); + if (deviceSaved.uuid) { + const deviceStatus = await this.getDevicesInstructionStatus( + deviceSaved.uuid, + ); + if (deviceStatus.productUuid) { + await this.deviceStatusMongoService.addDeviceStatusToMongo({ + deviceUuid: deviceSaved.uuid, + deviceTuyaUuid: addDeviceDto.deviceTuyaUuid, + status: deviceStatus.status, + productUuid: deviceStatus.productUuid, + productType: deviceStatus.productType, + }); + } + } + return deviceSaved; } catch (error) { if (error.code === '23505') { throw new HttpException( diff --git a/src/scene/scene.module.ts b/src/scene/scene.module.ts index 248f2e6..e378058 100644 --- a/src/scene/scene.module.ts +++ b/src/scene/scene.module.ts @@ -7,9 +7,10 @@ import { SpaceRepository } from '@app/common/modules/space/repositories'; import { DeviceService } from 'src/device/services'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { ProductRepository } from '@app/common/modules/product/repositories'; +import { DeviceStatusMongoModule } from '@app/common/mongoose/devices-status/devices-status.module'; @Module({ - imports: [ConfigModule, SpaceRepositoryModule], + imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusMongoModule], controllers: [SceneController], providers: [ SceneService,