diff --git a/.env.example b/.env.example index 54773c4..8fb1460 100644 --- a/.env.example +++ b/.env.example @@ -90,3 +90,6 @@ FIREBASE_DATABASE_URL= OTP_LIMITER= +GOOGLE_CLIENT_ID= + +GOOGLE_CLIENT_SECRET= \ No newline at end of file diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index af9d047..5792eb0 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -1,4 +1,8 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; +import { + BadRequestException, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import * as argon2 from 'argon2'; import { HelperHashService } from '../../helper/services'; @@ -6,16 +10,20 @@ import { UserRepository } from '../../../../common/src/modules/user/repositories import { UserSessionRepository } from '../../../../common/src/modules/session/repositories/session.repository'; import { UserSessionEntity } from '../../../../common/src/modules/session/entities'; import { ConfigService } from '@nestjs/config'; +import { OAuth2Client } from 'google-auth-library'; @Injectable() export class AuthService { + private client: OAuth2Client; constructor( private jwtService: JwtService, private readonly userRepository: UserRepository, private readonly sessionRepository: UserSessionRepository, private readonly helperHashService: HelperHashService, private readonly configService: ConfigService, - ) {} + ) { + this.client = new OAuth2Client(this.configService.get('GOOGLE_CLIENT_ID')); + } async validateUser( email: string, @@ -80,8 +88,17 @@ export class AuthService { type: user.type, sessionId: user.sessionId, roles: user?.roles, + googleCode: user.googleCode, }; - + if (payload.googleCode) { + const profile = await this.getProfile(payload.googleCode); + user = await this.userRepository.findOne({ + where: { email: profile.email }, + }); + if (!user) { + return { profile }; + } + } const tokens = await this.getTokens(payload); await this.updateRefreshToken(user.uuid, tokens.refreshToken); return tokens; @@ -100,4 +117,19 @@ export class AuthService { hashData(data: string) { return argon2.hash(data); } + + async getProfile(googleCode: string) { + try { + const ticket = await this.client.verifyIdToken({ + idToken: googleCode, + audience: this.configService.get('GOOGLE_CLIENT_ID'), + }); + const payload = ticket.getPayload(); + return { + ...payload, + }; + } catch (error) { + throw new UnauthorizedException('Google login failed'); + } + } } diff --git a/libs/common/src/common.module.ts b/libs/common/src/common.module.ts index 8c3e78c..d156de3 100644 --- a/libs/common/src/common.module.ts +++ b/libs/common/src/common.module.ts @@ -6,10 +6,16 @@ import { AuthModule } from './auth/auth.module'; import { ConfigModule } from '@nestjs/config'; import config from './config'; import { EmailService } from './util/email.service'; - +import { ErrorMessageService } from 'src/error-message/error-message.service'; @Module({ - providers: [CommonService, EmailService], - exports: [CommonService, HelperModule, AuthModule, EmailService], + providers: [CommonService, EmailService, ErrorMessageService], + exports: [ + CommonService, + HelperModule, + AuthModule, + EmailService, + ErrorMessageService, + ], imports: [ ConfigModule.forRoot({ load: config, diff --git a/libs/common/src/config/email.config.ts b/libs/common/src/config/email.config.ts index 11e8c6f..3a5c21e 100644 --- a/libs/common/src/config/email.config.ts +++ b/libs/common/src/config/email.config.ts @@ -1,11 +1,12 @@ import { registerAs } from '@nestjs/config'; +import { BooleanValues } from '../constants/boolean-values.enum'; export default registerAs( 'email-config', (): Record => ({ SMTP_HOST: process.env.SMTP_HOST, SMTP_PORT: parseInt(process.env.SMTP_PORT), - SMTP_SECURE: process.env.SMTP_SECURE === 'true', + SMTP_SECURE: process.env.SMTP_SECURE === BooleanValues.TRUE, SMTP_USER: process.env.SMTP_USER, SMTP_SENDER: process.env.SMTP_SENDER, SMTP_PASSWORD: process.env.SMTP_PASSWORD, diff --git a/libs/common/src/config/tuya.config.ts b/libs/common/src/config/tuya.config.ts index ba3344e..bd0ed8e 100644 --- a/libs/common/src/config/tuya.config.ts +++ b/libs/common/src/config/tuya.config.ts @@ -1,4 +1,5 @@ import { registerAs } from '@nestjs/config'; +import { BooleanValues } from '../constants/boolean-values.enum'; export default registerAs( 'tuya-config', @@ -7,6 +8,6 @@ export default registerAs( TUYA_ACCESS_KEY: process.env.TUYA_ACCESS_KEY, TUYA_EU_URL: process.env.TUYA_EU_URL, TRUN_ON_TUYA_SOCKET: - process.env.TRUN_ON_TUYA_SOCKET === 'true' ? true : false, + process.env.TRUN_ON_TUYA_SOCKET === BooleanValues.TRUE ? true : false, }), ); diff --git a/libs/common/src/constants/automation.enum.ts b/libs/common/src/constants/automation.enum.ts new file mode 100644 index 0000000..7e431eb --- /dev/null +++ b/libs/common/src/constants/automation.enum.ts @@ -0,0 +1,9 @@ +// automation.enum.ts +export enum ActionExecutorEnum { + DEVICE_ISSUE = 'device_issue', + DELAY = 'delay', +} + +export enum EntityTypeEnum { + DEVICE_REPORT = 'device_report', +} diff --git a/libs/common/src/constants/battery-status.enum.ts b/libs/common/src/constants/battery-status.enum.ts new file mode 100644 index 0000000..de500c1 --- /dev/null +++ b/libs/common/src/constants/battery-status.enum.ts @@ -0,0 +1,4 @@ +export enum BatteryStatus { + RESIDUAL_ELECTRICITY = 'residual_electricity', + BATTERY_PERCENTAGE = 'battery_percentage', +} diff --git a/libs/common/src/constants/boolean-values.enum.ts b/libs/common/src/constants/boolean-values.enum.ts new file mode 100644 index 0000000..ee0d4aa --- /dev/null +++ b/libs/common/src/constants/boolean-values.enum.ts @@ -0,0 +1,4 @@ +export enum BooleanValues { + TRUE = 'true', + FALSE = 'false', +} diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts new file mode 100644 index 0000000..a1e43ee --- /dev/null +++ b/libs/common/src/constants/controller-route.ts @@ -0,0 +1,11 @@ +export class ControllerRoute { + static REGION = class { + public static readonly ROUTE = 'region'; + static ACTIONS = class { + public static readonly GET_REGIONS_SUMMARY = 'Get list of all regions'; + + public static readonly GET_REGIONS_DESCRIPTION = + 'Retrieve the list of all regions registered in Syncrow.'; + }; + }; +} diff --git a/libs/common/src/constants/days.enum.ts b/libs/common/src/constants/days.enum.ts new file mode 100644 index 0000000..737c6cf --- /dev/null +++ b/libs/common/src/constants/days.enum.ts @@ -0,0 +1,14 @@ +export enum DaysEnum { + SUN = 'Sun', + MON = 'Mon', + TUE = 'Tue', + WED = 'Wed', + THU = 'Thu', + FRI = 'Fri', + SAT = 'Sat', +} + +export enum EnableDisableStatusEnum { + DISABLED = '0', + ENABLED = '1', +} diff --git a/libs/common/src/constants/device-status.enum.ts b/libs/common/src/constants/device-status.enum.ts new file mode 100644 index 0000000..c1b0ebb --- /dev/null +++ b/libs/common/src/constants/device-status.enum.ts @@ -0,0 +1,4 @@ +export enum DeviceStatuses { + REJECTED = 'rejected', + FULLFILLED = 'fulfilled', +} diff --git a/libs/common/src/constants/error-codes.enum.ts b/libs/common/src/constants/error-codes.enum.ts new file mode 100644 index 0000000..c22ed89 --- /dev/null +++ b/libs/common/src/constants/error-codes.enum.ts @@ -0,0 +1,3 @@ +export enum CommonErrorCodes { + DUPLICATE_ENTITY = '23505', +} diff --git a/libs/common/src/constants/hours-minutes.enum.ts b/libs/common/src/constants/hours-minutes.enum.ts new file mode 100644 index 0000000..cff3350 --- /dev/null +++ b/libs/common/src/constants/hours-minutes.enum.ts @@ -0,0 +1,99 @@ +export enum CommonHours { + ONE = '01:00', + ONE_THIRTY = '01:30', + TWO = '02:00', + TWO_THIRTY = '02:30', + THREE = '03:00', + THREE_THIRTY = '03:30', + FOUR = '04:00', + FOUR_THIRTY = '04:30', + FIVE = '05:00', + FIVE_THIRTY = '05:30', + SIX = '06:00', + SIX_THIRTY = '06:30', + SEVEN = '07:00', + SEVEN_THIRTY = '07:30', + EIGHT = '08:00', + EIGHT_THIRTY = '08:30', + NINE = '09:00', + NINE_THIRTY = '09:30', + TEN = '10:00', + TEN_THIRTY = '10:30', + ELEVEN = '11:00', + ELEVEN_THIRTY = '11:30', + TWELVE = '12:00', + TWELVE_THIRTY = '12:30', + THIRTEEN = '13:00', + THIRTEEN_THIRTY = '13:30', + FOURTEEN = '14:00', + FOURTEEN_THIRTY = '14:30', + FIFTEEN = '15:00', + FIFTEEN_THIRTY = '15:30', + SIXTEEN = '16:00', + SIXTEEN_THIRTY = '16:30', + SEVENTEEN = '17:00', + SEVENTEEN_THIRTY = '17:30', + EIGHTEEN = '18:00', + EIGHTEEN_THIRTY = '18:30', + NINETEEN = '19:00', + NINETEEN_THIRTY = '19:30', + TWENTY = '20:00', + TWENTY_THIRTY = '20:30', + TWENTY_ONE = '21:00', + TWENTY_ONE_THIRTY = '21:30', + TWENTY_TWO = '22:00', + TWENTY_TWO_THIRTY = '22:30', + TWENTY_THREE = '23:00', + TWENTY_THREE_THIRTY = '23:30', + TWENTY_FOUR = '24:00', +} + +export enum CommonHourMinutes { + ONE = 60, + ONE_THIRTY = 90, + TWO = 120, + TWO_THIRTY = 150, + THREE = 180, + THREE_THIRTY = 210, + FOUR = 240, + FOUR_THIRTY = 270, + FIVE = 300, + FIVE_THIRTY = 330, + SIX = 360, + SIX_THIRTY = 390, + SEVEN = 420, + SEVEN_THIRTY = 450, + EIGHT = 480, + EIGHT_THIRTY = 510, + NINE = 540, + NINE_THIRTY = 570, + TEN = 600, + TEN_THIRTY = 630, + ELEVEN = 660, + ELEVEN_THIRTY = 690, + TWELVE = 720, + TWELVE_THIRTY = 750, + THIRTEEN = 780, + THIRTEEN_THIRTY = 810, + FOURTEEN = 840, + FOURTEEN_THIRTY = 870, + FIFTEEN = 900, + FIFTEEN_THIRTY = 930, + SIXTEEN = 960, + SIXTEEN_THIRTY = 990, + SEVENTEEN = 1020, + SEVENTEEN_THIRTY = 1050, + EIGHTEEN = 1080, + EIGHTEEN_THIRTY = 1110, + NINETEEN = 1140, + NINETEEN_THIRTY = 1170, + TWENTY = 1200, + TWENTY_THIRTY = 1230, + TWENTY_ONE = 1260, + TWENTY_ONE_THIRTY = 1290, + TWENTY_TWO = 1320, + TWENTY_TWO_THIRTY = 1350, + TWENTY_THREE = 1380, + TWENTY_THREE_THIRTY = 1410, + TWENTY_FOUR = 1440, +} diff --git a/libs/common/src/constants/password-type.enum.ts b/libs/common/src/constants/password-type.enum.ts new file mode 100644 index 0000000..904b584 --- /dev/null +++ b/libs/common/src/constants/password-type.enum.ts @@ -0,0 +1,4 @@ +export enum PasswordType { + SINGLE = 'single', + MULTIPLE = 'multiple', +} diff --git a/libs/common/src/constants/product-type.enum.ts b/libs/common/src/constants/product-type.enum.ts index f0d24ad..6442f87 100644 --- a/libs/common/src/constants/product-type.enum.ts +++ b/libs/common/src/constants/product-type.enum.ts @@ -4,5 +4,15 @@ export enum ProductType { CPS = 'CPS', DL = 'DL', WPS = 'WPS', - TH_G = '3G', + THREE_G = '3G', + TWO_G = '2G', + ONE_G = '1G', + WH = 'WH', + DS = 'DS', + THREE_3TG = '3GT', + TWO_2TG = '2GT', + ONE_1TG = '1GT', + WL = 'WL', + GD = 'GD', + CUR = 'CUR', } diff --git a/libs/common/src/constants/working-days.ts b/libs/common/src/constants/working-days.ts index d870d3c..38fc530 100644 --- a/libs/common/src/constants/working-days.ts +++ b/libs/common/src/constants/working-days.ts @@ -1,9 +1,11 @@ +import { DaysEnum } from './days.enum'; + export enum WorkingDays { - Sun = 'Sun', - Mon = 'Mon', - Tue = 'Tue', - Wed = 'Wed', - Thu = 'Thu', - Fri = 'Fri', - Sat = 'Sat', + Sun = DaysEnum.SUN, + Mon = DaysEnum.MON, + Tue = DaysEnum.TUE, + Wed = DaysEnum.WED, + Thu = DaysEnum.THU, + Fri = DaysEnum.FRI, + Sat = DaysEnum.SAT, } diff --git a/libs/common/src/firebase/devices-status/controllers/devices-status.controller.ts b/libs/common/src/firebase/devices-status/controllers/devices-status.controller.ts index 70506c2..4a3458d 100644 --- a/libs/common/src/firebase/devices-status/controllers/devices-status.controller.ts +++ b/libs/common/src/firebase/devices-status/controllers/devices-status.controller.ts @@ -2,10 +2,11 @@ import { Controller, Post, Param } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { AddDeviceStatusDto } from '../dtos/add.devices-status.dto'; import { DeviceStatusFirebaseService } from '../services/devices-status.service'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @ApiTags('Device Status Firebase Module') @Controller({ - version: '1', + version: EnableDisableStatusEnum.ENABLED, path: 'device-status-firebase', }) export class DeviceStatusFirebaseController { diff --git a/libs/common/src/firebase/devices-status/services/devices-status.service.ts b/libs/common/src/firebase/devices-status/services/devices-status.service.ts index 267d6b6..f075f34 100644 --- a/libs/common/src/firebase/devices-status/services/devices-status.service.ts +++ b/libs/common/src/firebase/devices-status/services/devices-status.service.ts @@ -85,6 +85,7 @@ export class DeviceStatusFirebaseService { return await this.deviceRepository.findOne({ where: { deviceTuyaUuid, + isActive: true, }, relations: ['productDevice'], }); @@ -139,6 +140,7 @@ export class DeviceStatusFirebaseService { return await this.deviceRepository.findOne({ where: { uuid: deviceUuid, + isActive: true, }, ...(withProductDevice && { relations: ['productDevice'] }), }); diff --git a/libs/common/src/helper/convertTimestampToDubaiTime.ts b/libs/common/src/helper/convertTimestampToDubaiTime.ts new file mode 100644 index 0000000..e1c235b --- /dev/null +++ b/libs/common/src/helper/convertTimestampToDubaiTime.ts @@ -0,0 +1,37 @@ +import { EnableDisableStatusEnum } from '../constants/days.enum'; + +export function convertTimestampToDubaiTime(timestamp) { + // Convert timestamp to milliseconds + const date = new Date(timestamp * 1000); + + // Convert to Dubai time (UTC+3) + const dubaiTimeOffset = 3 * 60; // 3 hours in minutes + const dubaiTime = new Date(date.getTime() + dubaiTimeOffset * 60 * 1000); + + // Format the date as YYYYMMDD + const year = dubaiTime.getUTCFullYear(); + const month = String(dubaiTime.getUTCMonth() + 1).padStart( + 2, + EnableDisableStatusEnum.DISABLED, + ); // Months are zero-based + const day = String(dubaiTime.getUTCDate()).padStart( + 2, + EnableDisableStatusEnum.DISABLED, + ); + + // Format the time as HH:MM (24-hour format) + const hours = String(dubaiTime.getUTCHours()).padStart( + 2, + EnableDisableStatusEnum.DISABLED, + ); + const minutes = String(dubaiTime.getUTCMinutes()).padStart( + 2, + EnableDisableStatusEnum.DISABLED, + ); + + // Return formatted date and time + return { + date: `${year}${month}${day}`, + time: `${hours}:${minutes}`, + }; +} diff --git a/libs/common/src/helper/getScheduleStatus.ts b/libs/common/src/helper/getScheduleStatus.ts new file mode 100644 index 0000000..3daf5c3 --- /dev/null +++ b/libs/common/src/helper/getScheduleStatus.ts @@ -0,0 +1,45 @@ +import { DaysEnum, EnableDisableStatusEnum } from '../constants/days.enum'; + +export function getScheduleStatus(daysEnabled: string[]): string { + const daysMap: string[] = [ + DaysEnum.SUN, + DaysEnum.MON, + DaysEnum.TUE, + DaysEnum.WED, + DaysEnum.THU, + DaysEnum.FRI, + DaysEnum.SAT, + ]; + + const schedule: string[] = Array(7).fill(EnableDisableStatusEnum.DISABLED); + + daysEnabled.forEach((day) => { + const index: number = daysMap.indexOf(day); + if (index !== -1) { + schedule[index] = EnableDisableStatusEnum.ENABLED; + } + }); + + return schedule.join(''); +} +export function getEnabledDays(schedule: string): string[] { + const daysMap: string[] = [ + DaysEnum.SUN, + DaysEnum.MON, + DaysEnum.TUE, + DaysEnum.WED, + DaysEnum.THU, + DaysEnum.FRI, + DaysEnum.SAT, + ]; + const enabledDays: string[] = []; + + // Iterate through the schedule string + for (let i = 0; i < schedule.length; i++) { + if (schedule[i] === EnableDisableStatusEnum.ENABLED) { + enabledDays.push(daysMap[i]); + } + } + + return enabledDays; +} diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index c02f8a1..5a96255 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -18,8 +18,9 @@ export class DeviceEntity extends AbstractEntity { @Column({ nullable: true, default: true, + type: 'boolean', }) - isActive: true; + isActive: boolean; @ManyToOne(() => UserEntity, (user) => user.userSpaces, { nullable: false }) user: UserEntity; diff --git a/package-lock.json b/package-lock.json index db7b28d..9a92384 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,13 +20,14 @@ "@nestjs/websockets": "^10.3.8", "@tuya/tuya-connector-nodejs": "^2.1.2", "argon2": "^0.40.1", - "axios": "^1.6.7", + "axios": "^1.7.7", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "crypto-js": "^4.2.0", "express-rate-limit": "^7.1.5", "firebase": "^10.12.5", + "google-auth-library": "^9.14.1", "helmet": "^7.1.0", "ioredis": "^5.3.2", "morgan": "^1.10.0", @@ -2258,6 +2259,8 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.8.tgz", "integrity": "sha512-qKwC/M/nNNaKUBMQ0nuzm47b7ZYWQHN3pcXq4IIcoSBc2hOIrflAxJduIvvqmhoz3gR2TacTAs8vlsCVPkiEdQ==", + "optional": true, + "peer": true, "dependencies": { "sparse-bitfield": "^3.0.3" } @@ -2490,17 +2493,6 @@ } } }, - "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", @@ -3138,7 +3130,9 @@ "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==" + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "optional": true, + "peer": true }, "node_modules/@types/whatwg-url": { "version": "8.2.2", @@ -3571,6 +3565,18 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -3791,11 +3797,12 @@ "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==" }, "node_modules/axios": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", - "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.4", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -3969,6 +3976,15 @@ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -4131,14 +4147,6 @@ "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", @@ -5902,15 +5910,16 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -6071,6 +6080,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -6209,6 +6247,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/google-auth-library": { + "version": "9.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.14.1.tgz", + "integrity": "sha512-Rj+PMjoNFGFTmtItH7gHfbHpGVSb3vmnGK3nwNBqxQF9NoBpttSZI/rc0WiM63ma2uGDQtYEkMHkK9U6937NiA==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-auth-library/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-auth-library/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -6232,6 +6308,40 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/gtoken/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/gtoken/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -6399,6 +6509,19 @@ "npm": ">=1.3.7" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -6704,7 +6827,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, "engines": { "node": ">=8" }, @@ -7548,6 +7670,15 @@ "node": ">=4" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -7666,14 +7797,6 @@ "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", @@ -7911,7 +8034,9 @@ "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==" + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true, + "peer": true }, "node_modules/merge-descriptors": { "version": "1.0.1", @@ -8146,125 +8271,6 @@ "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", @@ -8304,25 +8310,6 @@ "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", @@ -9956,11 +9943,6 @@ "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", @@ -10045,6 +10027,8 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "optional": true, + "peer": true, "dependencies": { "memory-pager": "^1.0.2" } diff --git a/package.json b/package.json index 4883516..d3c90e0 100644 --- a/package.json +++ b/package.json @@ -31,13 +31,14 @@ "@nestjs/websockets": "^10.3.8", "@tuya/tuya-connector-nodejs": "^2.1.2", "argon2": "^0.40.1", - "axios": "^1.6.7", + "axios": "^1.7.7", "bcryptjs": "^2.4.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "crypto-js": "^4.2.0", "express-rate-limit": "^7.1.5", "firebase": "^10.12.5", + "google-auth-library": "^9.14.1", "helmet": "^7.1.0", "ioredis": "^5.3.2", "morgan": "^1.10.0", diff --git a/src/app.module.ts b/src/app.module.ts index 2970185..5c3a52c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -2,7 +2,6 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import config from './config'; import { AuthenticationModule } from './auth/auth.module'; -import { AuthenticationController } from './auth/controllers/authentication.controller'; import { UserModule } from './users/user.module'; import { RoomModule } from './room/room.module'; import { GroupModule } from './group/group.module'; @@ -24,6 +23,7 @@ import { AutomationModule } from './automation/automation.module'; import { RegionModule } from './region/region.module'; import { TimeZoneModule } from './timezone/timezone.module'; import { VisitorPasswordModule } from './vistor-password/visitor-password.module'; +import { ScheduleModule } from './schedule/schedule.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -50,8 +50,8 @@ import { VisitorPasswordModule } from './vistor-password/visitor-password.module RegionModule, TimeZoneModule, VisitorPasswordModule, + ScheduleModule, ], - controllers: [AuthenticationController], providers: [ { provide: APP_INTERCEPTOR, diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 012312d..66d335d 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -1,6 +1,4 @@ import { Module } from '@nestjs/common'; -import { AuthenticationController } from './controllers/authentication.controller'; -import { AuthenticationService } from './services/authentication.service'; import { ConfigModule } from '@nestjs/config'; import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; import { CommonModule } from '../../libs/common/src'; @@ -16,9 +14,8 @@ import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; @Module({ imports: [ConfigModule, UserRepositoryModule, CommonModule], - controllers: [AuthenticationController, UserAuthController], + controllers: [UserAuthController], providers: [ - AuthenticationService, UserAuthService, UserRepository, UserSessionRepository, @@ -26,6 +23,6 @@ import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; UserRoleRepository, RoleTypeRepository, ], - exports: [AuthenticationService, UserAuthService], + exports: [UserAuthService], }) export class AuthenticationModule {} diff --git a/src/auth/controllers/authentication.controller.ts b/src/auth/controllers/authentication.controller.ts deleted file mode 100644 index ace6525..0000000 --- a/src/auth/controllers/authentication.controller.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Controller, Post } from '@nestjs/common'; -import { AuthenticationService } from '../services/authentication.service'; -import { ApiTags } from '@nestjs/swagger'; - -@Controller({ - version: '1', - path: 'authentication', -}) -@ApiTags('Tuya Auth') -export class AuthenticationController { - constructor(private readonly authenticationService: AuthenticationService) {} - @Post('auth2') - async Authentication() { - return await this.authenticationService.main(); - } -} diff --git a/src/auth/controllers/index.ts b/src/auth/controllers/index.ts index 2ce466d..f63ac56 100644 --- a/src/auth/controllers/index.ts +++ b/src/auth/controllers/index.ts @@ -1,2 +1 @@ -export * from './authentication.controller'; export * from './user-auth.controller'; diff --git a/src/auth/controllers/user-auth.controller.ts b/src/auth/controllers/user-auth.controller.ts index e650d39..bc9fc3c 100644 --- a/src/auth/controllers/user-auth.controller.ts +++ b/src/auth/controllers/user-auth.controller.ts @@ -1,10 +1,8 @@ import { Body, Controller, - Delete, Get, HttpStatus, - Param, Post, Req, UseGuards, @@ -17,9 +15,10 @@ import { UserLoginDto } from '../dtos/user-login.dto'; import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos'; import { RefreshTokenGuard } from '@app/common/guards/jwt-refresh.auth.guard'; import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @Controller({ - version: '1', + version: EnableDisableStatusEnum.ENABLED, path: 'authentication', }) @ApiTags('Auth') @@ -51,20 +50,6 @@ export class UserAuthController { }; } - @ApiBearerAuth() - @UseGuards(SuperAdminRoleGuard) - @Delete('user/delete/:id') - async userDelete(@Param('id') id: string) { - await this.userAuthService.deleteUser(id); - return { - statusCode: HttpStatus.OK, - data: { - id, - }, - message: 'User Deleted Successfully', - }; - } - @Post('user/send-otp') async sendOtp(@Body() otpDto: UserOtpDto) { const otpCode = await this.userAuthService.generateOTP(otpDto); @@ -99,7 +84,7 @@ export class UserAuthController { @ApiBearerAuth() @UseGuards(SuperAdminRoleGuard) - @Get('user/list') + @Get('user') async userList() { const userList = await this.userAuthService.userList(); return { diff --git a/src/auth/dtos/user-auth.dto.ts b/src/auth/dtos/user-auth.dto.ts index b934c4c..dad1e07 100644 --- a/src/auth/dtos/user-auth.dto.ts +++ b/src/auth/dtos/user-auth.dto.ts @@ -41,5 +41,5 @@ export class UserSignUpDto { @IsString() @IsOptional() - public regionUuid: string; + public regionUuid?: string; } diff --git a/src/auth/dtos/user-login.dto.ts b/src/auth/dtos/user-login.dto.ts index 1f3662b..198ae12 100644 --- a/src/auth/dtos/user-login.dto.ts +++ b/src/auth/dtos/user-login.dto.ts @@ -1,19 +1,23 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { IsEmail, IsOptional, IsString } from 'class-validator'; export class UserLoginDto { @ApiProperty() @IsEmail() - @IsNotEmpty() - email: string; + @IsOptional() + email?: string; @ApiProperty() @IsString() @IsOptional() - password: string; + password?: string; @ApiProperty() @IsString() @IsOptional() regionUuid?: string; + + @IsOptional() + @IsString() + googleCode?: string; } diff --git a/src/auth/services/authentication.service.ts b/src/auth/services/authentication.service.ts deleted file mode 100644 index 1d5d580..0000000 --- a/src/auth/services/authentication.service.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import * as qs from 'qs'; -import * as crypto from 'crypto'; -import { ConfigService } from '@nestjs/config'; -import axios from 'axios'; -@Injectable() -export class AuthenticationService { - private token: string; - private deviceId: string; - private accessKey: string; - private secretKey: string; - - constructor(private readonly configService: ConfigService) { - (this.deviceId = this.configService.get('auth-config.DEVICE_ID')), - (this.accessKey = this.configService.get( - 'auth-config.ACCESS_KEY', - )), - (this.secretKey = this.configService.get( - 'auth-config.SECRET_KEY', - )); - } - - async main() { - await this.getToken(); - const data = await this.getDeviceInfo(this.deviceId); - console.log('fetch success: ', JSON.stringify(data)); - return JSON.stringify(data); - } - - async getToken() { - const method = 'GET'; - const timestamp = Date.now().toString(); - const signUrl = 'https://openapi.tuyaeu.com/v1.0/token?grant_type=1'; - const contentHash = crypto.createHash('sha256').update('').digest('hex'); - const stringToSign = [method, contentHash, '', signUrl].join('\n'); - const signStr = this.accessKey + timestamp + stringToSign; - - const headers = { - t: timestamp, - sign_method: 'HMAC-SHA256', - client_id: this.accessKey, - sign: await this.encryptStr(signStr, this.secretKey), - }; - - const { data: login } = await axios.get( - 'https://openapi.tuyaeu.com/v1.0/token', - { - params: { - grant_type: 1, - }, - headers, - }, - ); - - if (!login || !login.success) { - throw new Error(`fetch failed: ${login.msg}`); - } - this.token = login.result.access_token; - } - - async getDeviceInfo(deviceId: string) { - const query = {}; - const method = 'POST'; - const url = `https://openapi.tuyaeu.com/v1.0/devices/${deviceId}/commands`; - const reqHeaders: { [k: string]: string } = await this.getRequestSign( - url, - method, - {}, - query, - ); - - const { data } = await axios.post(url, {}, reqHeaders); - - if (!data || !data.success) { - throw new Error(`request api failed: ${data.msg}`); - } - - return data; - } - - async encryptStr(str: string, secret: string): Promise { - return crypto - .createHmac('sha256', secret) - .update(str, 'utf8') - .digest('hex') - .toUpperCase(); - } - - async getRequestSign( - path: string, - method: string, - query: { [k: string]: any } = {}, - body: { [k: string]: any } = {}, - ) { - const t = Date.now().toString(); - const [uri, pathQuery] = path.split('?'); - const queryMerged = Object.assign(query, qs.parse(pathQuery)); - const sortedQuery: { [k: string]: string } = {}; - Object.keys(queryMerged) - .sort() - .forEach((i) => (sortedQuery[i] = query[i])); - - const querystring = decodeURIComponent(qs.stringify(sortedQuery)); - const url = querystring ? `${uri}?${querystring}` : uri; - const contentHash = crypto - .createHash('sha256') - .update(JSON.stringify(body)) - .digest('hex'); - const stringToSign = [method, contentHash, '', url].join('\n'); - const signStr = this.accessKey + this.token + t + stringToSign; - return { - t, - path: url, - client_id: 'this.accessKey', - sign: await this.encryptStr(signStr, this.secretKey), - sign_method: 'HMAC-SHA256', - access_token: this.token, - }; - } -} diff --git a/src/auth/services/index.ts b/src/auth/services/index.ts index ac532d6..aa322a1 100644 --- a/src/auth/services/index.ts +++ b/src/auth/services/index.ts @@ -1,2 +1 @@ -export * from './authentication.service'; export * from './user-auth.service'; diff --git a/src/auth/services/user-auth.service.ts b/src/auth/services/user-auth.service.ts index 7b539e2..9fd9fe3 100644 --- a/src/auth/services/user-auth.service.ts +++ b/src/auth/services/user-auth.service.ts @@ -1,11 +1,8 @@ -import { RoleTypeRepository } from './../../../libs/common/src/modules/role-type/repositories/role.type.repository'; -import { UserRoleRepository } from './../../../libs/common/src/modules/user/repositories/user.repository'; import { UserRepository } from '../../../libs/common/src/modules/user/repositories'; import { BadRequestException, ForbiddenException, Injectable, - UnauthorizedException, } from '@nestjs/common'; import { UserSignUpDto } from '../dtos/user-auth.dto'; import { HelperHashService } from '../../../libs/common/src/helper/services'; @@ -21,6 +18,7 @@ import * as argon2 from 'argon2'; import { differenceInSeconds } from '@app/common/helper/differenceInSeconds'; import { LessThan, MoreThan } from 'typeorm'; import { ConfigService } from '@nestjs/config'; +import { UUID } from 'typeorm/driver/mongodb/bson.typings'; @Injectable() export class UserAuthService { @@ -31,8 +29,6 @@ export class UserAuthService { private readonly helperHashService: HelperHashService, private readonly authService: AuthService, private readonly emailService: EmailService, - private readonly userRoleRepository: UserRoleRepository, - private readonly roleTypeRepository: RoleTypeRepository, private readonly configService: ConfigService, ) {} @@ -93,13 +89,38 @@ export class UserAuthService { async userLogin(data: UserLoginDto) { try { - const user = await this.authService.validateUser( - data.email, - data.password, - data.regionUuid, - ); - if (!user) { - throw new UnauthorizedException('Invalid login credentials.'); + let user; + if (data.googleCode) { + const googleUserData = await this.authService.login({ + googleCode: data.googleCode, + }); + const userExists = await this.userRepository.exists({ + where: { + email: googleUserData['email'], + }, + }); + user = await this.userRepository.findOne({ + where: { + email: googleUserData['email'], + }, + }); + if (!userExists) { + await this.signUp({ + email: googleUserData['email'], + firstName: googleUserData['given_name'], + lastName: googleUserData['family_name'], + password: googleUserData['email'], + }); + } + data.email = googleUserData['email']; + data.password = googleUserData['password']; + } + if (!data.googleCode) { + user = await this.authService.validateUser( + data.email, + data.password, + data.regionUuid, + ); } const session = await Promise.all([ await this.sessionRepository.update( @@ -114,7 +135,7 @@ export class UserAuthService { isLoggedOut: false, }), ]); - return await this.authService.login({ + const res = await this.authService.login({ email: user.email, userId: user.uuid, uuid: user.uuid, @@ -123,19 +144,12 @@ export class UserAuthService { }), sessionId: session[1].uuid, }); + return res; } catch (error) { throw new BadRequestException('Invalid credentials'); } } - async deleteUser(uuid: string) { - const user = await this.findOneById(uuid); - if (!user) { - throw new BadRequestException('User not found'); - } - return await this.userRepository.update({ uuid }, { isActive: false }); - } - async findOneById(id: string): Promise { return await this.userRepository.findOne({ where: { uuid: id } }); } @@ -225,7 +239,15 @@ export class UserAuthService { }); if (!otp) { - throw new BadRequestException('this email is not registered'); + const user = await this.userRepository.findOne({ + where: { + email: data.email, + }, + }); + if (!user) { + throw new BadRequestException('this email is not registered'); + } + throw new BadRequestException('You entered wrong otp'); } if (otp.otpCode !== data.otpCode) { diff --git a/src/automation/controllers/automation.controller.ts b/src/automation/controllers/automation.controller.ts index 4328602..af5ee30 100644 --- a/src/automation/controllers/automation.controller.ts +++ b/src/automation/controllers/automation.controller.ts @@ -4,7 +4,6 @@ import { Controller, Delete, Get, - HttpException, HttpStatus, Param, Post, @@ -18,10 +17,11 @@ import { UpdateAutomationStatusDto, } from '../dtos/automation.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @ApiTags('Automation Module') @Controller({ - version: '1', + version: EnableDisableStatusEnum.ENABLED, path: 'automation', }) export class AutomationController { @@ -31,52 +31,30 @@ export class AutomationController { @UseGuards(JwtAuthGuard) @Post() async addAutomation(@Body() addAutomationDto: AddAutomationDto) { - try { - const automation = - await this.automationService.addAutomation(addAutomationDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Automation added successfully', - data: automation, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const automation = + await this.automationService.addAutomation(addAutomationDto); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'Automation added successfully', + data: automation, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':unitUuid') async getAutomationByUnit(@Param('unitUuid') unitUuid: string) { - try { - const automation = - await this.automationService.getAutomationByUnit(unitUuid); - return automation; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const automation = + await this.automationService.getAutomationByUnit(unitUuid); + return automation; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('details/:automationId') async getAutomationDetails(@Param('automationId') automationId: string) { - try { - const automation = - await this.automationService.getAutomationDetails(automationId); - return automation; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - ``; - } + const automation = + await this.automationService.getAutomationDetails(automationId); + return automation; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -85,18 +63,11 @@ export class AutomationController { @Param('unitUuid') unitUuid: string, @Param('automationId') automationId: string, ) { - try { - await this.automationService.deleteAutomation(unitUuid, automationId); - return { - statusCode: HttpStatus.OK, - message: 'Automation Deleted Successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.automationService.deleteAutomation(unitUuid, automationId); + return { + statusCode: HttpStatus.OK, + message: 'Automation Deleted Successfully', + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -105,23 +76,16 @@ export class AutomationController { @Body() updateAutomationDto: UpdateAutomationDto, @Param('automationId') automationId: string, ) { - try { - const automation = await this.automationService.updateAutomation( - updateAutomationDto, - automationId, - ); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Automation updated successfully', - data: automation, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const automation = await this.automationService.updateAutomation( + updateAutomationDto, + automationId, + ); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'Automation updated successfully', + data: automation, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -130,21 +94,14 @@ export class AutomationController { @Body() updateAutomationStatusDto: UpdateAutomationStatusDto, @Param('automationId') automationId: string, ) { - try { - await this.automationService.updateAutomationStatus( - updateAutomationStatusDto, - automationId, - ); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Automation status updated successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.automationService.updateAutomationStatus( + updateAutomationStatusDto, + automationId, + ); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'Automation status updated successfully', + }; } } diff --git a/src/automation/services/automation.service.ts b/src/automation/services/automation.service.ts index bbb7651..3f5a4d5 100644 --- a/src/automation/services/automation.service.ts +++ b/src/automation/services/automation.service.ts @@ -23,6 +23,11 @@ import { GetAutomationByUnitInterface, } from '../interface/automation.interface'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; +import { SpaceType } from '@app/common/constants/space-type.enum'; +import { + ActionExecutorEnum, + EntityTypeEnum, +} from '@app/common/constants/automation.enum'; @Injectable() export class AutomationService { @@ -64,7 +69,7 @@ export class AutomationService { ); for (const action of actions) { - if (action.action_executor === 'device_issue') { + if (action.action_executor === ActionExecutorEnum.DEVICE_ISSUE) { const device = await this.deviceService.getDeviceByDeviceUuid( action.entity_id, false, @@ -76,7 +81,7 @@ export class AutomationService { } for (const condition of conditions) { - if (condition.entity_type === 'device_report') { + if (condition.entity_type === EntityTypeEnum.DEVICE_REPORT) { const device = await this.deviceService.getDeviceByDeviceUuid( condition.entity_id, false, @@ -127,12 +132,12 @@ export class AutomationService { where: { uuid: unitUuid, spaceType: { - type: 'unit', + type: SpaceType.UNIT, }, }, relations: ['spaceType'], }); - if (!unit || !unit.spaceType || unit.spaceType.type !== 'unit') { + if (!unit || !unit.spaceType || unit.spaceType.type !== SpaceType.UNIT) { throw new BadRequestException('Invalid unit UUID'); } return { @@ -240,7 +245,7 @@ export class AutomationService { })); for (const action of actions) { - if (action.actionExecutor === 'device_issue') { + if (action.actionExecutor === ActionExecutorEnum.DEVICE_ISSUE) { const device = await this.deviceService.getDeviceByDeviceTuyaUuid( action.entityId, ); @@ -249,8 +254,8 @@ export class AutomationService { action.entityId = device.uuid; } } else if ( - action.actionExecutor !== 'device_issue' && - action.actionExecutor !== 'delay' + action.actionExecutor !== ActionExecutorEnum.DEVICE_ISSUE && + action.actionExecutor !== ActionExecutorEnum.DELAY ) { const sceneDetails = await this.getTapToRunSceneDetailsTuya( action.entityId, @@ -268,7 +273,7 @@ export class AutomationService { })); for (const condition of conditions) { - if (condition.entityType === 'device_report') { + if (condition.entityType === EntityTypeEnum.DEVICE_REPORT) { const device = await this.deviceService.getDeviceByDeviceTuyaUuid( condition.entityId, ); diff --git a/src/building/controllers/building.controller.ts b/src/building/controllers/building.controller.ts index a76d620..8c7d1b1 100644 --- a/src/building/controllers/building.controller.ts +++ b/src/building/controllers/building.controller.ts @@ -3,7 +3,6 @@ import { Body, Controller, Get, - HttpException, HttpStatus, Param, Post, @@ -20,11 +19,13 @@ import { CheckUserBuildingGuard } from 'src/guards/user.building.guard'; import { AdminRoleGuard } from 'src/guards/admin.role.guard'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { BuildingPermissionGuard } from 'src/guards/building.permission.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { SpaceType } from '@app/common/constants/space-type.enum'; @ApiTags('Building Module') @Controller({ - version: '1', - path: 'building', + version: EnableDisableStatusEnum.ENABLED, + path: SpaceType.BUILDING, }) export class BuildingController { constructor(private readonly buildingService: BuildingService) {} @@ -33,36 +34,21 @@ export class BuildingController { @UseGuards(JwtAuthGuard, CheckCommunityTypeGuard) @Post() async addBuilding(@Body() addBuildingDto: AddBuildingDto) { - try { - const building = await this.buildingService.addBuilding(addBuildingDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Building added successfully', - data: building, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const building = await this.buildingService.addBuilding(addBuildingDto); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'Building added successfully', + data: building, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard, BuildingPermissionGuard) @Get(':buildingUuid') async getBuildingByUuid(@Param('buildingUuid') buildingUuid: string) { - try { - const building = - await this.buildingService.getBuildingByUuid(buildingUuid); - return building; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const building = await this.buildingService.getBuildingByUuid(buildingUuid); + return building; } @ApiBearerAuth() @@ -72,84 +58,49 @@ export class BuildingController { @Param('buildingUuid') buildingUuid: string, @Query() query: GetBuildingChildDto, ) { - try { - const building = await this.buildingService.getBuildingChildByUuid( - buildingUuid, - query, - ); - return building; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const building = await this.buildingService.getBuildingChildByUuid( + buildingUuid, + query, + ); + return building; } @ApiBearerAuth() @UseGuards(JwtAuthGuard, BuildingPermissionGuard) @Get('parent/:buildingUuid') async getBuildingParentByUuid(@Param('buildingUuid') buildingUuid: string) { - try { - const building = - await this.buildingService.getBuildingParentByUuid(buildingUuid); - return building; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const building = + await this.buildingService.getBuildingParentByUuid(buildingUuid); + return building; } @ApiBearerAuth() @UseGuards(AdminRoleGuard, CheckUserBuildingGuard) @Post('user') async addUserBuilding(@Body() addUserBuildingDto: AddUserBuildingDto) { - try { - await this.buildingService.addUserBuilding(addUserBuildingDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'user building added successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.buildingService.addUserBuilding(addUserBuildingDto); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'user building added successfully', + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('user/:userUuid') async getBuildingsByUserId(@Param('userUuid') userUuid: string) { - try { - return await this.buildingService.getBuildingsByUserId(userUuid); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.buildingService.getBuildingsByUserId(userUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard, BuildingPermissionGuard) - @Put('rename/:buildingUuid') + @Put(':buildingUuid') async renameBuildingByUuid( @Param('buildingUuid') buildingUuid: string, @Body() updateBuildingDto: UpdateBuildingNameDto, ) { - try { - const building = await this.buildingService.renameBuildingByUuid( - buildingUuid, - updateBuildingDto, - ); - return building; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const building = await this.buildingService.renameBuildingByUuid( + buildingUuid, + updateBuildingDto, + ); + return building; } } diff --git a/src/building/dtos/get.building.dto.ts b/src/building/dtos/get.building.dto.ts index f762469..fae0c6b 100644 --- a/src/building/dtos/get.building.dto.ts +++ b/src/building/dtos/get.building.dto.ts @@ -1,3 +1,4 @@ +import { BooleanValues } from '@app/common/constants/boolean-values.enum'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { @@ -45,7 +46,7 @@ export class GetBuildingChildDto { @IsOptional() @IsBoolean() @Transform((value) => { - return value.obj.includeSubSpaces === 'true'; + return value.obj.includeSubSpaces === BooleanValues.TRUE; }) public includeSubSpaces: boolean = false; } diff --git a/src/building/services/building.service.ts b/src/building/services/building.service.ts index c9f8273..b3de5d4 100644 --- a/src/building/services/building.service.ts +++ b/src/building/services/building.service.ts @@ -18,6 +18,8 @@ import { import { SpaceEntity } from '@app/common/modules/space/entities'; import { UpdateBuildingNameDto } from '../dtos/update.building.dto'; import { UserSpaceRepository } from '@app/common/modules/user/repositories'; +import { SpaceType } from '@app/common/constants/space-type.enum'; +import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; @Injectable() export class BuildingService { @@ -31,7 +33,7 @@ export class BuildingService { try { const spaceType = await this.spaceTypeRepository.findOne({ where: { - type: 'building', + type: SpaceType.BUILDING, }, }); @@ -61,7 +63,7 @@ export class BuildingService { where: { uuid: buildingUuid, spaceType: { - type: 'building', + type: SpaceType.BUILDING, }, }, relations: ['spaceType'], @@ -69,7 +71,7 @@ export class BuildingService { if ( !building || !building.spaceType || - building.spaceType.type !== 'building' + building.spaceType.type !== SpaceType.BUILDING ) { throw new BadRequestException('Invalid building UUID'); } @@ -99,7 +101,11 @@ export class BuildingService { where: { uuid: buildingUuid }, relations: ['children', 'spaceType'], }); - if (!space || !space.spaceType || space.spaceType.type !== 'building') { + if ( + !space || + !space.spaceType || + space.spaceType.type !== SpaceType.BUILDING + ) { throw new BadRequestException('Invalid building UUID'); } @@ -147,8 +153,8 @@ export class BuildingService { return children .filter( (child) => - child.spaceType.type !== 'building' && - child.spaceType.type !== 'community', + child.spaceType.type !== SpaceType.BUILDING && + child.spaceType.type !== SpaceType.COMMUNITY, ) // Filter remaining building and community types .map((child) => ({ uuid: child.uuid, @@ -161,8 +167,8 @@ export class BuildingService { children .filter( (child) => - child.spaceType.type !== 'building' && - child.spaceType.type !== 'community', + child.spaceType.type !== SpaceType.BUILDING && + child.spaceType.type !== SpaceType.COMMUNITY, ) // Filter remaining building and community types .map(async (child) => ({ uuid: child.uuid, @@ -183,7 +189,7 @@ export class BuildingService { where: { uuid: buildingUuid, spaceType: { - type: 'building', + type: SpaceType.BUILDING, }, }, relations: ['spaceType', 'parent', 'parent.spaceType'], @@ -191,7 +197,7 @@ export class BuildingService { if ( !building || !building.spaceType || - building.spaceType.type !== 'building' + building.spaceType.type !== SpaceType.BUILDING ) { throw new BadRequestException('Invalid building UUID'); } @@ -222,7 +228,7 @@ export class BuildingService { relations: ['space', 'space.spaceType'], where: { user: { uuid: userUuid }, - space: { spaceType: { type: 'building' } }, + space: { spaceType: { type: SpaceType.BUILDING } }, }, }); @@ -254,7 +260,7 @@ export class BuildingService { space: { uuid: addUserBuildingDto.buildingUuid }, }); } catch (err) { - if (err.code === '23505') { + if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) { throw new HttpException( 'User already belongs to this building', HttpStatus.BAD_REQUEST, @@ -279,7 +285,7 @@ export class BuildingService { if ( !building || !building.spaceType || - building.spaceType.type !== 'building' + building.spaceType.type !== SpaceType.BUILDING ) { throw new BadRequestException('Invalid building UUID'); } diff --git a/src/common/filters/http-exception/http-exception.filter.spec.ts b/src/common/filters/http-exception/http-exception.filter.spec.ts new file mode 100644 index 0000000..8f016dd --- /dev/null +++ b/src/common/filters/http-exception/http-exception.filter.spec.ts @@ -0,0 +1,7 @@ +import { HttpExceptionFilter } from './http-exception.filter'; + +describe('HttpExceptionFilter', () => { + it('should be defined', () => { + expect(new HttpExceptionFilter()).toBeDefined(); + }); +}); diff --git a/src/common/filters/http-exception/http-exception.filter.ts b/src/common/filters/http-exception/http-exception.filter.ts new file mode 100644 index 0000000..c587769 --- /dev/null +++ b/src/common/filters/http-exception/http-exception.filter.ts @@ -0,0 +1,38 @@ +import { + ExceptionFilter, + Catch, + ArgumentsHost, + HttpException, + HttpStatus, +} from '@nestjs/common'; +import { Response } from 'express'; + +@Catch() +export class HttpExceptionFilter implements ExceptionFilter { + catch(exception: unknown, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + const status = + exception instanceof HttpException + ? exception.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + + const message = + exception instanceof HttpException + ? exception.getResponse() + : 'Internal server error'; + + const errorResponse = { + statusCode: status, + timestamp: new Date().toISOString(), + path: request.url, + error: message, + }; + + // Optionally log the exception + console.error(`Error occurred:`, exception); + + response.status(status).json(errorResponse); + } +} diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index af8d137..356c90c 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -3,7 +3,6 @@ import { Body, Controller, Get, - HttpException, HttpStatus, Param, Post, @@ -18,15 +17,16 @@ import { } from '../dtos/add.community.dto'; import { GetCommunityChildDto } from '../dtos/get.community.dto'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; -// import { CheckUserCommunityGuard } from 'src/guards/user.community.guard'; import { AdminRoleGuard } from 'src/guards/admin.role.guard'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { SpaceType } from '@app/common/constants/space-type.enum'; // import { CommunityPermissionGuard } from 'src/guards/community.permission.guard'; @ApiTags('Community Module') @Controller({ - version: '1', - path: 'community', + version: EnableDisableStatusEnum.ENABLED, + path: SpaceType.COMMUNITY, }) export class CommunityController { constructor(private readonly communityService: CommunityService) {} @@ -35,51 +35,29 @@ export class CommunityController { @UseGuards(JwtAuthGuard) @Post() async addCommunity(@Body() addCommunityDto: AddCommunityDto) { - try { - const community = - await this.communityService.addCommunity(addCommunityDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Community added successfully', - data: community, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const community = await this.communityService.addCommunity(addCommunityDto); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'Community added successfully', + data: community, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':communityUuid') async getCommunityByUuid(@Param('communityUuid') communityUuid: string) { - try { - const community = - await this.communityService.getCommunityByUuid(communityUuid); - return community; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const community = + await this.communityService.getCommunityByUuid(communityUuid); + return community; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get() async getCommunities() { - try { - const communities = await this.communityService.getCommunities(); - return communities; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const communities = await this.communityService.getCommunities(); + return communities; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -88,69 +66,41 @@ export class CommunityController { @Param('communityUuid') communityUuid: string, @Query() query: GetCommunityChildDto, ) { - try { - const community = await this.communityService.getCommunityChildByUuid( - communityUuid, - query, - ); - return community; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const community = await this.communityService.getCommunityChildByUuid( + communityUuid, + query, + ); + return community; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('user/:userUuid') async getCommunitiesByUserId(@Param('userUuid') userUuid: string) { - try { - return await this.communityService.getCommunitiesByUserId(userUuid); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.communityService.getCommunitiesByUserId(userUuid); } @ApiBearerAuth() @UseGuards(AdminRoleGuard) @Post('user') async addUserCommunity(@Body() addUserCommunityDto: AddUserCommunityDto) { - try { - await this.communityService.addUserCommunity(addUserCommunityDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'user community added successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.communityService.addUserCommunity(addUserCommunityDto); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'user community added successfully', + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Put('rename/:communityUuid') + @Put(':communityUuid') async renameCommunityByUuid( @Param('communityUuid') communityUuid: string, @Body() updateCommunityDto: UpdateCommunityNameDto, ) { - try { - const community = await this.communityService.renameCommunityByUuid( - communityUuid, - updateCommunityDto, - ); - return community; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const community = await this.communityService.renameCommunityByUuid( + communityUuid, + updateCommunityDto, + ); + return community; } } diff --git a/src/community/dtos/get.community.dto.ts b/src/community/dtos/get.community.dto.ts index be614e5..cae892b 100644 --- a/src/community/dtos/get.community.dto.ts +++ b/src/community/dtos/get.community.dto.ts @@ -1,3 +1,4 @@ +import { BooleanValues } from '@app/common/constants/boolean-values.enum'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { @@ -45,7 +46,7 @@ export class GetCommunityChildDto { @IsOptional() @IsBoolean() @Transform((value) => { - return value.obj.includeSubSpaces === 'true'; + return value.obj.includeSubSpaces === BooleanValues.TRUE; }) public includeSubSpaces: boolean = false; } diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index 27d7bcf..145dc13 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -18,6 +18,8 @@ import { import { SpaceEntity } from '@app/common/modules/space/entities'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; import { UserSpaceRepository } from '@app/common/modules/user/repositories'; +import { SpaceType } from '@app/common/constants/space-type.enum'; +import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; @Injectable() export class CommunityService { @@ -31,7 +33,7 @@ export class CommunityService { try { const spaceType = await this.spaceTypeRepository.findOne({ where: { - type: 'community', + type: SpaceType.COMMUNITY, }, }); @@ -53,7 +55,7 @@ export class CommunityService { where: { uuid: communityUuid, spaceType: { - type: 'community', + type: SpaceType.COMMUNITY, }, }, relations: ['spaceType'], @@ -61,7 +63,7 @@ export class CommunityService { if ( !community || !community.spaceType || - community.spaceType.type !== 'community' + community.spaceType.type !== SpaceType.COMMUNITY ) { throw new BadRequestException('Invalid community UUID'); } @@ -83,7 +85,7 @@ export class CommunityService { async getCommunities(): Promise { try { const community = await this.spaceRepository.find({ - where: { spaceType: { type: 'community' } }, + where: { spaceType: { type: SpaceType.COMMUNITY } }, relations: ['spaceType'], }); return community.map((community) => ({ @@ -109,7 +111,11 @@ export class CommunityService { relations: ['children', 'spaceType'], }); - if (!space || !space.spaceType || space.spaceType.type !== 'community') { + if ( + !space || + !space.spaceType || + space.spaceType.type !== SpaceType.COMMUNITY + ) { throw new BadRequestException('Invalid community UUID'); } const totalCount = await this.spaceRepository.count({ @@ -152,7 +158,7 @@ export class CommunityService { if (!children || children.length === 0 || !includeSubSpaces) { return children - .filter((child) => child.spaceType.type !== 'community') // Filter remaining community type + .filter((child) => child.spaceType.type !== SpaceType.COMMUNITY) // Filter remaining community type .map((child) => ({ uuid: child.uuid, name: child.spaceName, @@ -162,7 +168,7 @@ export class CommunityService { const childHierarchies = await Promise.all( children - .filter((child) => child.spaceType.type !== 'community') // Filter remaining community type + .filter((child) => child.spaceType.type !== SpaceType.COMMUNITY) // Filter remaining community type .map(async (child) => ({ uuid: child.uuid, name: child.spaceName, @@ -182,7 +188,7 @@ export class CommunityService { relations: ['space', 'space.spaceType'], where: { user: { uuid: userUuid }, - space: { spaceType: { type: 'community' } }, + space: { spaceType: { type: SpaceType.COMMUNITY } }, }, }); @@ -215,7 +221,7 @@ export class CommunityService { space: { uuid: addUserCommunityDto.communityUuid }, }); } catch (err) { - if (err.code === '23505') { + if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) { throw new HttpException( 'User already belongs to this community', HttpStatus.BAD_REQUEST, @@ -240,7 +246,7 @@ export class CommunityService { if ( !community || !community.spaceType || - community.spaceType.type !== 'community' + community.spaceType.type !== SpaceType.COMMUNITY ) { throw new BadRequestException('Invalid community UUID'); } diff --git a/src/device-messages/controllers/device-messages.controller.ts b/src/device-messages/controllers/device-messages.controller.ts index c8f7834..6f942b4 100644 --- a/src/device-messages/controllers/device-messages.controller.ts +++ b/src/device-messages/controllers/device-messages.controller.ts @@ -3,7 +3,6 @@ import { Controller, Delete, Get, - HttpException, HttpStatus, Param, Post, @@ -14,10 +13,11 @@ import { DeviceMessagesSubscriptionService } from '../services/device-messages.s import { DeviceMessagesAddDto } from '../dtos/device-messages.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @ApiTags('Device Messages Status Module') @Controller({ - version: '1', + version: EnableDisableStatusEnum.ENABLED, path: 'device-messages/subscription', }) export class DeviceMessagesSubscriptionController { @@ -31,22 +31,15 @@ export class DeviceMessagesSubscriptionController { async addDeviceMessagesSubscription( @Body() deviceMessagesAddDto: DeviceMessagesAddDto, ) { - try { - const addDetails = - await this.deviceMessagesSubscriptionService.addDeviceMessagesSubscription( - deviceMessagesAddDto, - ); - return { - statusCode: HttpStatus.CREATED, - message: 'Device Messages Subscription Added Successfully', - data: addDetails, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, + const addDetails = + await this.deviceMessagesSubscriptionService.addDeviceMessagesSubscription( + deviceMessagesAddDto, ); - } + return { + statusCode: HttpStatus.CREATED, + message: 'Device Messages Subscription Added Successfully', + data: addDetails, + }; } @ApiBearerAuth() @@ -56,23 +49,16 @@ export class DeviceMessagesSubscriptionController { @Param('deviceUuid') deviceUuid: string, @Param('userUuid') userUuid: string, ) { - try { - const deviceDetails = - await this.deviceMessagesSubscriptionService.getDeviceMessagesSubscription( - userUuid, - deviceUuid, - ); - return { - statusCode: HttpStatus.OK, - message: 'User Device Subscription fetched Successfully', - data: deviceDetails, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, + const deviceDetails = + await this.deviceMessagesSubscriptionService.getDeviceMessagesSubscription( + userUuid, + deviceUuid, ); - } + return { + statusCode: HttpStatus.OK, + message: 'User Device Subscription fetched Successfully', + data: deviceDetails, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -80,19 +66,12 @@ export class DeviceMessagesSubscriptionController { async deleteDeviceMessagesSubscription( @Body() deviceMessagesAddDto: DeviceMessagesAddDto, ) { - try { - await this.deviceMessagesSubscriptionService.deleteDeviceMessagesSubscription( - deviceMessagesAddDto, - ); - return { - statusCode: HttpStatus.OK, - message: 'User subscription deleted Successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.deviceMessagesSubscriptionService.deleteDeviceMessagesSubscription( + deviceMessagesAddDto, + ); + return { + statusCode: HttpStatus.OK, + message: 'User subscription deleted Successfully', + }; } } diff --git a/src/device-messages/services/device-messages.service.ts b/src/device-messages/services/device-messages.service.ts index 4722b79..fea62d5 100644 --- a/src/device-messages/services/device-messages.service.ts +++ b/src/device-messages/services/device-messages.service.ts @@ -1,6 +1,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { DeviceMessagesAddDto } from '../dtos/device-messages.dto'; import { DeviceNotificationRepository } from '@app/common/modules/device/repositories'; +import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; @Injectable() export class DeviceMessagesSubscriptionService { @@ -21,7 +22,7 @@ export class DeviceMessagesSubscriptionService { }, }); } catch (error) { - if (error.code === '23505') { + if (error.code === CommonErrorCodes.DUPLICATE_ENTITY) { throw new HttpException( 'This User already belongs to this device', HttpStatus.BAD_REQUEST, diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index bc399b6..5f1a7ae 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -6,7 +6,6 @@ import { Post, Query, Param, - HttpException, HttpStatus, UseGuards, Req, @@ -18,17 +17,24 @@ import { GetDeviceByRoomUuidDto, GetDeviceLogsDto, } from '../dtos/get.device.dto'; -import { ControlDeviceDto } from '../dtos/control.device.dto'; +import { + ControlDeviceDto, + BatchControlDevicesDto, + BatchStatusDevicesDto, + BatchFactoryResetDevicesDto, +} from '../dtos/control.device.dto'; import { CheckRoomGuard } from 'src/guards/room.guard'; import { CheckUserHavePermission } from 'src/guards/user.device.permission.guard'; import { CheckUserHaveControllablePermission } from 'src/guards/user.device.controllable.permission.guard'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { CheckDeviceGuard } from 'src/guards/device.guard'; import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { SpaceType } from '@app/common/constants/space-type.enum'; @ApiTags('Device Module') @Controller({ - version: '1', + version: EnableDisableStatusEnum.ENABLED, path: 'device', }) export class DeviceController { @@ -37,67 +43,39 @@ export class DeviceController { @UseGuards(SuperAdminRoleGuard, CheckDeviceGuard) @Post() async addDeviceUser(@Body() addDeviceDto: AddDeviceDto) { - try { - const device = await this.deviceService.addDeviceUser(addDeviceDto); + const device = await this.deviceService.addDeviceUser(addDeviceDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'device added successfully', - data: device, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'device added successfully', + data: device, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('user/:userUuid') async getDevicesByUser(@Param('userUuid') userUuid: string) { - try { - return await this.deviceService.getDevicesByUser(userUuid); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.deviceService.getDevicesByUser(userUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard, CheckRoomGuard) - @Get('room') + @Get(SpaceType.ROOM) async getDevicesByRoomId( @Query() getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto, @Req() req: any, ) { - try { - const userUuid = req.user.uuid; - return await this.deviceService.getDevicesByRoomId( - getDeviceByRoomUuidDto, - userUuid, - ); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const userUuid = req.user.uuid; + return await this.deviceService.getDevicesByRoomId( + getDeviceByRoomUuidDto, + userUuid, + ); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('unit/:unitUuid') async getDevicesByUnitId(@Param('unitUuid') unitUuid: string) { - try { - return await this.deviceService.getDevicesByUnitId(unitUuid); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.deviceService.getDevicesByUnitId(unitUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard, CheckRoomGuard) @@ -105,23 +83,16 @@ export class DeviceController { async updateDeviceInRoom( @Body() updateDeviceInRoomDto: UpdateDeviceInRoomDto, ) { - try { - const device = await this.deviceService.updateDeviceInRoom( - updateDeviceInRoomDto, - ); + const device = await this.deviceService.updateDeviceInRoom( + updateDeviceInRoomDto, + ); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'device updated in room successfully', - data: device, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'device updated in room successfully', + data: device, + }; } @ApiBearerAuth() @@ -131,18 +102,11 @@ export class DeviceController { @Param('deviceUuid') deviceUuid: string, @Req() req: any, ) { - try { - const userUuid = req.user.uuid; - return await this.deviceService.getDeviceDetailsByDeviceId( - deviceUuid, - userUuid, - ); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const userUuid = req.user.uuid; + return await this.deviceService.getDeviceDetailsByDeviceId( + deviceUuid, + userUuid, + ); } @ApiBearerAuth() @UseGuards(JwtAuthGuard, CheckUserHavePermission) @@ -150,29 +114,13 @@ export class DeviceController { async getDeviceInstructionByDeviceId( @Param('deviceUuid') deviceUuid: string, ) { - try { - return await this.deviceService.getDeviceInstructionByDeviceId( - deviceUuid, - ); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.deviceService.getDeviceInstructionByDeviceId(deviceUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard, CheckUserHavePermission) @Get(':deviceUuid/functions/status') async getDevicesInstructionStatus(@Param('deviceUuid') deviceUuid: string) { - try { - return await this.deviceService.getDevicesInstructionStatus(deviceUuid); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.deviceService.getDevicesInstructionStatus(deviceUuid); } @ApiBearerAuth() @@ -182,17 +130,7 @@ export class DeviceController { @Body() controlDeviceDto: ControlDeviceDto, @Param('deviceUuid') deviceUuid: string, ) { - try { - return await this.deviceService.controlDevice( - controlDeviceDto, - deviceUuid, - ); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.deviceService.controlDevice(controlDeviceDto, deviceUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -201,43 +139,22 @@ export class DeviceController { @Param('deviceUuid') deviceUuid: string, @Param('firmwareVersion') firmwareVersion: number, ) { - try { - return await this.deviceService.updateDeviceFirmware( - deviceUuid, - firmwareVersion, - ); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.deviceService.updateDeviceFirmware( + deviceUuid, + firmwareVersion, + ); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('gateway/:gatewayUuid/devices') async getDevicesInGateway(@Param('gatewayUuid') gatewayUuid: string) { - try { - return await this.deviceService.getDevicesInGateway(gatewayUuid); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.deviceService.getDevicesInGateway(gatewayUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get() async getAllDevices() { - try { - return await this.deviceService.getAllDevices(); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.deviceService.getAllDevices(); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -246,13 +163,32 @@ export class DeviceController { @Param('deviceUuid') deviceUuid: string, @Query() query: GetDeviceLogsDto, ) { - try { - return await this.deviceService.getDeviceLogs(deviceUuid, query); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.deviceService.getDeviceLogs(deviceUuid, query); + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('control/batch') + async batchControlDevices( + @Body() batchControlDevicesDto: BatchControlDevicesDto, + ) { + return await this.deviceService.batchControlDevices(batchControlDevicesDto); + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('status/batch') + async batchStatusDevices( + @Query() batchStatusDevicesDto: BatchStatusDevicesDto, + ) { + return await this.deviceService.batchStatusDevices(batchStatusDevicesDto); + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('factory/reset/:deviceUuid') + async batchFactoryResetDevices( + @Body() batchFactoryResetDevicesDto: BatchFactoryResetDevicesDto, + ) { + return await this.deviceService.batchFactoryResetDevices( + batchFactoryResetDevicesDto, + ); } } diff --git a/src/device/dtos/control.device.dto.ts b/src/device/dtos/control.device.dto.ts index ab2125d..d917a96 100644 --- a/src/device/dtos/control.device.dto.ts +++ b/src/device/dtos/control.device.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsArray, IsNotEmpty, IsString } from 'class-validator'; export class ControlDeviceDto { @ApiProperty({ @@ -16,3 +16,41 @@ export class ControlDeviceDto { @IsNotEmpty() public value: any; } +export class BatchControlDevicesDto { + @ApiProperty({ + description: 'devicesUuid', + required: true, + }) + @IsArray() + @IsNotEmpty() + public devicesUuid: [string]; + @ApiProperty({ + description: 'code', + required: true, + }) + @IsString() + @IsNotEmpty() + public code: string; + @ApiProperty({ + description: 'value', + required: true, + }) + @IsNotEmpty() + public value: any; +} +export class BatchStatusDevicesDto { + @ApiProperty({ + example: 'uuid1,uuid2,uuid3', + description: 'Comma-separated list of device UUIDs', + }) + devicesUuid: string; +} +export class BatchFactoryResetDevicesDto { + @ApiProperty({ + description: 'devicesUuid', + required: true, + }) + @IsArray() + @IsNotEmpty() + public devicesUuid: [string]; +} diff --git a/src/device/dtos/get.device.dto.ts b/src/device/dtos/get.device.dto.ts index 99c0dad..13204de 100644 --- a/src/device/dtos/get.device.dto.ts +++ b/src/device/dtos/get.device.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class GetDeviceByRoomUuidDto { @ApiProperty({ @@ -18,4 +18,18 @@ export class GetDeviceLogsDto { @IsString() @IsNotEmpty() public code: string; + @ApiProperty({ + description: 'startTime', + required: false, + }) + @IsString() + @IsOptional() + public startTime: string; + @ApiProperty({ + description: 'endTime', + required: false, + }) + @IsString() + @IsOptional() + public endTime: string; } diff --git a/src/device/interfaces/get.device.interface.ts b/src/device/interfaces/get.device.interface.ts index 880a976..7422ed2 100644 --- a/src/device/interfaces/get.device.interface.ts +++ b/src/device/interfaces/get.device.interface.ts @@ -66,7 +66,7 @@ export interface updateDeviceFirmwareInterface { } export interface getDeviceLogsInterface { data: []; - startTime: number; - endTime: number; + startTime: string; + endTime: string; deviceUuid?: string; } diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 0641d6f..2b37df8 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -22,7 +22,12 @@ import { GetDeviceByRoomUuidDto, GetDeviceLogsDto, } from '../dtos/get.device.dto'; -import { ControlDeviceDto } from '../dtos/control.device.dto'; +import { + BatchControlDevicesDto, + BatchFactoryResetDevicesDto, + BatchStatusDevicesDto, + ControlDeviceDto, +} from '../dtos/control.device.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { PermissionType } from '@app/common/constants/permission-type.enum'; @@ -30,6 +35,9 @@ import { In } from 'typeorm'; import { ProductType } from '@app/common/constants/product-type.enum'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service'; +import { DeviceStatuses } from '@app/common/constants/device-status.enum'; +import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; +import { BatteryStatus } from '@app/common/constants/battery-status.enum'; @Injectable() export class DeviceService { @@ -101,7 +109,7 @@ export class DeviceService { } return deviceSaved; } catch (error) { - if (error.code === '23505') { + if (error.code === CommonErrorCodes.DUPLICATE_ENTITY) { throw new HttpException( 'Device already exists', HttpStatus.BAD_REQUEST, @@ -121,6 +129,7 @@ export class DeviceService { const devices = await this.deviceRepository.find({ where: { user: { uuid: userUuid }, + isActive: true, permission: { userUuid, permissionType: { @@ -167,6 +176,7 @@ export class DeviceService { const devices = await this.deviceRepository.find({ where: { spaceDevice: { uuid: getDeviceByRoomUuidDto.roomUuid }, + isActive: true, permission: { userUuid, permissionType: { @@ -284,6 +294,24 @@ export class DeviceService { ); } } + async factoryResetDeviceTuya( + deviceUuid: string, + ): Promise { + try { + const path = `/v2.0/cloud/thing/${deviceUuid}/reset`; + const response = await this.tuya.request({ + method: 'POST', + path, + }); + + return response as controlDeviceInterface; + } catch (error) { + throw new HttpException( + 'Error factory resetting device from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } async controlDeviceTuya( deviceUuid: string, controlDeviceDto: ControlDeviceDto, @@ -308,7 +336,164 @@ export class DeviceService { ); } } + async batchControlDevices(batchControlDevicesDto: BatchControlDevicesDto) { + const { devicesUuid } = batchControlDevicesDto; + try { + // Check if all devices have the same product UUID + await this.checkAllDevicesHaveSameProductUuid(devicesUuid); + + // Perform all operations concurrently + const results = await Promise.allSettled( + devicesUuid.map(async (deviceUuid) => { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + const result = await this.controlDeviceTuya( + deviceDetails.deviceTuyaUuid, + batchControlDevicesDto, + ); + return { deviceUuid, result }; + }), + ); + + // Separate successful and failed operations + const successResults = []; + const failedResults = []; + + for (const result of results) { + if (result.status === DeviceStatuses.FULLFILLED) { + const { deviceUuid, result: operationResult } = result.value; + + if (operationResult.success) { + // Add to success results if operationResult.success is true + successResults.push({ deviceUuid, result: operationResult }); + } else { + // Add to failed results if operationResult.success is false + failedResults.push({ deviceUuid, error: operationResult.msg }); + } + } else { + // Add to failed results if promise is rejected + failedResults.push({ + deviceUuid: devicesUuid[results.indexOf(result)], + error: result.reason.message, + }); + } + } + + return { successResults, failedResults }; + } catch (error) { + throw new HttpException( + error.message || 'Device Not Found', + error.status || HttpStatus.NOT_FOUND, + ); + } + } + async batchStatusDevices(batchStatusDevicesDto: BatchStatusDevicesDto) { + const { devicesUuid } = batchStatusDevicesDto; + const devicesUuidArray = devicesUuid.split(','); + + try { + await this.checkAllDevicesHaveSameProductUuid(devicesUuidArray); + const statuses = await Promise.all( + devicesUuidArray.map(async (deviceUuid) => { + const result = await this.getDevicesInstructionStatus(deviceUuid); + return { deviceUuid, result }; + }), + ); + return { + status: statuses[0].result, + devices: statuses, + }; + } catch (error) { + throw new HttpException( + error.message || 'Device Not Found', + error.status || HttpStatus.NOT_FOUND, + ); + } + } + async checkAllDevicesHaveSameProductUuid(deviceUuids: string[]) { + const firstDevice = await this.deviceRepository.findOne({ + where: { uuid: deviceUuids[0], isActive: true }, + relations: ['productDevice'], + }); + + if (!firstDevice) { + throw new BadRequestException('First device not found'); + } + + const firstProductType = firstDevice.productDevice.prodType; + + for (let i = 1; i < deviceUuids.length; i++) { + const device = await this.deviceRepository.findOne({ + where: { uuid: deviceUuids[i], isActive: true }, + relations: ['productDevice'], + }); + + if (!device) { + throw new BadRequestException(`Device ${deviceUuids[i]} not found`); + } + + if (device.productDevice.prodType !== firstProductType) { + throw new BadRequestException(`Devices have different product types`); + } + } + } + async batchFactoryResetDevices( + batchFactoryResetDevicesDto: BatchFactoryResetDevicesDto, + ) { + const { devicesUuid } = batchFactoryResetDevicesDto; + + try { + // Check if all devices have the same product UUID + await this.checkAllDevicesHaveSameProductUuid(devicesUuid); + + // Perform all operations concurrently + const results = await Promise.allSettled( + devicesUuid.map(async (deviceUuid) => { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + const result = await this.factoryResetDeviceTuya( + deviceDetails.deviceTuyaUuid, + ); + return { deviceUuid, result }; + }), + ); + + // Separate successful and failed operations + const successResults = []; + const failedResults = []; + + for (const result of results) { + if (result.status === DeviceStatuses.FULLFILLED) { + const { deviceUuid, result: operationResult } = result.value; + + if (operationResult.success) { + // Add to success results if operationResult.success is true + successResults.push({ deviceUuid, result: operationResult }); + // Update isActive to false in the repository for the successfully reset device + await this.deviceRepository.update( + { uuid: deviceUuid }, + { isActive: false }, + ); + } else { + // Add to failed results if operationResult.success is false + failedResults.push({ deviceUuid, error: operationResult.msg }); + } + } else { + // Add to failed results if promise is rejected + failedResults.push({ + deviceUuid: devicesUuid[results.indexOf(result)], + error: result.reason.message, + }); + } + } + + return { successResults, failedResults }; + } catch (error) { + throw new HttpException( + error.message || 'Device Not Found', + error.status || HttpStatus.NOT_FOUND, + ); + } + } async getDeviceDetailsByDeviceId(deviceUuid: string, userUuid: string) { try { const userDevicePermission = await this.getUserDevicePermission( @@ -359,7 +544,7 @@ export class DeviceService { }); // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { productName, productId, id, ...rest } = camelCaseResponse.result; + const { productId, id, ...rest } = camelCaseResponse.result; return { ...rest, @@ -492,19 +677,25 @@ export class DeviceService { const devices = await Promise.all( response.map(async (device: any) => { - const deviceDetails = await this.getDeviceByDeviceTuyaUuid(device.id); - if (deviceDetails.deviceTuyaUuid) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { id, ...rest } = device; - return { - ...rest, - tuyaUuid: deviceDetails.deviceTuyaUuid, - uuid: deviceDetails.uuid, - productUuid: deviceDetails.productDevice.uuid, - productType: deviceDetails.productDevice.prodType, - }; + try { + const deviceDetails = await this.getDeviceByDeviceTuyaUuid( + device.id, + ); + if (deviceDetails.deviceTuyaUuid) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { id, ...rest } = device; + return { + ...rest, + tuyaUuid: deviceDetails.deviceTuyaUuid, + uuid: deviceDetails.uuid, + productUuid: deviceDetails.productDevice.uuid, + productType: deviceDetails.productDevice.prodType, + }; + } + return null; + } catch (error) { + return null; } - return null; }), ); @@ -593,6 +784,9 @@ export class DeviceService { parent: { uuid: unitUuid, }, + devicesSpaceEntity: { + isActive: true, + }, }, relations: ['devicesSpaceEntity', 'devicesSpaceEntity.productDevice'], }); @@ -627,6 +821,7 @@ export class DeviceService { async getAllDevices(): Promise { try { const devices = await this.deviceRepository.find({ + where: { isActive: true }, relations: [ 'spaceDevice.parent', 'productDevice', @@ -644,14 +839,40 @@ export class DeviceService { await this.getDevicesInstructionStatus(device.uuid); const batteryStatus: any = doorLockInstructionsStatus.status.find( - (status: any) => status.code === 'residual_electricity', + (status: any) => + status.code === BatteryStatus.RESIDUAL_ELECTRICITY, ); if (batteryStatus) { battery = batteryStatus.value; } } + // Check if the device is a door sensor (DS) + if (device.productDevice.prodType === ProductType.DS) { + const doorSensorInstructionsStatus = + await this.getDevicesInstructionStatus(device.uuid); + const batteryStatus: any = doorSensorInstructionsStatus.status.find( + (status: any) => status.code === BatteryStatus.BATTERY_PERCENTAGE, + ); + + if (batteryStatus) { + battery = batteryStatus.value; + } + } + // Check if the device is a water leak sensor (WL) + if (device.productDevice.prodType === ProductType.WL) { + const doorSensorInstructionsStatus = + await this.getDevicesInstructionStatus(device.uuid); + + const batteryStatus: any = doorSensorInstructionsStatus.status.find( + (status: any) => status.code === BatteryStatus.BATTERY_PERCENTAGE, + ); + + if (batteryStatus) { + battery = batteryStatus.value; + } + } const spaceDevice = device?.spaceDevice; const parentDevice = spaceDevice?.parent; return { @@ -677,7 +898,7 @@ export class DeviceService { // Filter out rejected promises and extract the fulfilled values const fulfilledDevices = devicesData - .filter((result) => result.status === 'fulfilled') + .filter((result) => result.status === DeviceStatuses.FULLFILLED) .map( (result) => (result as PromiseFulfilledResult).value, @@ -702,6 +923,8 @@ export class DeviceService { const response = await this.getDeviceLogsTuya( deviceDetails.deviceTuyaUuid, query.code, + query.startTime, + query.endTime, ); return { @@ -718,11 +941,11 @@ export class DeviceService { async getDeviceLogsTuya( deviceId: string, code: string, + startTime: string = (Date.now() - 1 * 60 * 60 * 1000).toString(), + endTime: string = Date.now().toString(), ): Promise { try { - const now = Date.now(); - const oneHourAgo = now - 1 * 60 * 60 * 1000; - const path = `/v2.0/cloud/thing/${deviceId}/report-logs?start_time=${oneHourAgo}&end_time=${now}&codes=${code}&size=50`; + const path = `/v2.0/cloud/thing/${deviceId}/report-logs?start_time=${startTime}&end_time=${endTime}&codes=${code}&size=50`; const response = await this.tuya.request({ method: 'GET', path, @@ -731,8 +954,8 @@ export class DeviceService { const camelCaseResponse = convertKeysToCamelCase(response); const logs = camelCaseResponse.result.logs ?? []; return { - startTime: oneHourAgo, - endTime: now, + startTime, + endTime, data: logs, } as getDeviceLogsInterface; } catch (error) { diff --git a/src/door-lock/controllers/door.lock.controller.ts b/src/door-lock/controllers/door.lock.controller.ts index 56ba2b8..3e6f459 100644 --- a/src/door-lock/controllers/door.lock.controller.ts +++ b/src/door-lock/controllers/door.lock.controller.ts @@ -4,7 +4,6 @@ import { Controller, Post, Param, - HttpException, HttpStatus, Get, Delete, @@ -16,10 +15,11 @@ import { AddDoorLockOnlineDto } from '../dtos/add.online-temp.dto'; import { AddDoorLockOfflineTempMultipleTimeDto } from '../dtos/add.offline-temp.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { UpdateDoorLockOfflineTempDto } from '../dtos/update.offline-temp.dto'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @ApiTags('Door Lock Module') @Controller({ - version: '1', + version: EnableDisableStatusEnum.ENABLED, path: 'door-lock', }) export class DoorLockController { @@ -31,27 +31,20 @@ export class DoorLockController { @Body() addDoorLockDto: AddDoorLockOnlineDto, @Param('doorLockUuid') doorLockUuid: string, ) { - try { - const temporaryPassword = - await this.doorLockService.addOnlineTemporaryPassword( - addDoorLockDto, - doorLockUuid, - ); - - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'online temporary password added successfully', - data: { - id: temporaryPassword.id, - }, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, + const temporaryPassword = + await this.doorLockService.addOnlineTemporaryPassword( + addDoorLockDto, + doorLockUuid, ); - } + + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'online temporary password added successfully', + data: { + id: temporaryPassword.id, + }, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -59,24 +52,17 @@ export class DoorLockController { async addOfflineOneTimeTemporaryPassword( @Param('doorLockUuid') doorLockUuid: string, ) { - try { - const temporaryPassword = - await this.doorLockService.addOfflineOneTimeTemporaryPassword( - doorLockUuid, - ); - - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'offline temporary password added successfully', - data: temporaryPassword, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, + const temporaryPassword = + await this.doorLockService.addOfflineOneTimeTemporaryPassword( + doorLockUuid, ); - } + + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'offline temporary password added successfully', + data: temporaryPassword, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -86,25 +72,18 @@ export class DoorLockController { addDoorLockOfflineTempMultipleTimeDto: AddDoorLockOfflineTempMultipleTimeDto, @Param('doorLockUuid') doorLockUuid: string, ) { - try { - const temporaryPassword = - await this.doorLockService.addOfflineMultipleTimeTemporaryPassword( - addDoorLockOfflineTempMultipleTimeDto, - doorLockUuid, - ); - - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'offline temporary password added successfully', - data: temporaryPassword, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, + const temporaryPassword = + await this.doorLockService.addOfflineMultipleTimeTemporaryPassword( + addDoorLockOfflineTempMultipleTimeDto, + doorLockUuid, ); - } + + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'offline temporary password added successfully', + data: temporaryPassword, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -112,16 +91,9 @@ export class DoorLockController { async getOnlineTemporaryPasswords( @Param('doorLockUuid') doorLockUuid: string, ) { - try { - return await this.doorLockService.getOnlineTemporaryPasswordsMultiple( - doorLockUuid, - ); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.doorLockService.getOnlineTemporaryPasswordsMultiple( + doorLockUuid, + ); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -130,21 +102,11 @@ export class DoorLockController { @Param('doorLockUuid') doorLockUuid: string, @Param('passwordId') passwordId: string, ) { - try { - await this.doorLockService.deleteDoorLockPassword( - doorLockUuid, - passwordId, - ); - return { - statusCode: HttpStatus.OK, - message: 'Temporary Password deleted Successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.doorLockService.deleteDoorLockPassword(doorLockUuid, passwordId); + return { + statusCode: HttpStatus.OK, + message: 'Temporary Password deleted Successfully', + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -152,16 +114,9 @@ export class DoorLockController { async getOfflineOneTimeTemporaryPasswords( @Param('doorLockUuid') doorLockUuid: string, ) { - try { - return await this.doorLockService.getOfflineOneTimeTemporaryPasswords( - doorLockUuid, - ); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.doorLockService.getOfflineOneTimeTemporaryPasswords( + doorLockUuid, + ); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -169,16 +124,9 @@ export class DoorLockController { async getOfflineMultipleTimeTemporaryPasswords( @Param('doorLockUuid') doorLockUuid: string, ) { - try { - return await this.doorLockService.getOfflineMultipleTimeTemporaryPasswords( - doorLockUuid, - ); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.doorLockService.getOfflineMultipleTimeTemporaryPasswords( + doorLockUuid, + ); } @ApiBearerAuth() @@ -190,44 +138,30 @@ export class DoorLockController { @Param('doorLockUuid') doorLockUuid: string, @Param('passwordId') passwordId: string, ) { - try { - const temporaryPassword = - await this.doorLockService.updateOfflineTemporaryPassword( - updateDoorLockOfflineTempDto, - doorLockUuid, - passwordId, - ); - - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'offline temporary password updated successfully', - data: temporaryPassword, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, + const temporaryPassword = + await this.doorLockService.updateOfflineTemporaryPassword( + updateDoorLockOfflineTempDto, + doorLockUuid, + passwordId, ); - } + + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'offline temporary password updated successfully', + data: temporaryPassword, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('open/:doorLockUuid') async openDoorLock(@Param('doorLockUuid') doorLockUuid: string) { - try { - await this.doorLockService.openDoorLock(doorLockUuid); + await this.doorLockService.openDoorLock(doorLockUuid); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'door lock opened successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'door lock opened successfully', + }; } } diff --git a/src/door-lock/services/door.lock.service.ts b/src/door-lock/services/door.lock.service.ts index 5864c5d..cf7f9f4 100644 --- a/src/door-lock/services/door.lock.service.ts +++ b/src/door-lock/services/door.lock.service.ts @@ -24,6 +24,15 @@ import { UpdateDoorLockOfflineTempDto } from '../dtos/update.offline-temp.dto'; import { defaultDoorLockPass } from '@app/common/constants/default.door-lock-pass'; import { VisitorPasswordRepository } from '@app/common/modules/visitor-password/repositories'; import { DeviceService } from 'src/device/services'; +import { + DaysEnum, + EnableDisableStatusEnum, +} from '@app/common/constants/days.enum'; +import { PasswordType } from '@app/common/constants/password-type.enum'; +import { + CommonHourMinutes, + CommonHours, +} from '@app/common/constants/hours-minutes.enum'; @Injectable() export class DoorLockService { @@ -115,7 +124,7 @@ export class DoorLockService { ); const passwords = await this.getTemporaryOfflinePasswordsTuya( deviceDetails.deviceTuyaUuid, - 'multiple', + PasswordType.MULTIPLE, isExpired, ); if (!passwords.result.records.length && fromVisitor) { @@ -502,7 +511,7 @@ export class DoorLockService { } const createOnceOfflinePass = await this.addOfflineTemporaryPasswordTuya( deviceDetails.deviceTuyaUuid, - 'multiple', + PasswordType.MULTIPLE, addDoorLockOfflineTempMultipleTimeDto, ); if (!createOnceOfflinePass.success) { @@ -566,7 +575,7 @@ export class DoorLockService { method: 'POST', path, body: { - ...(type === 'multiple' && { + ...(type === PasswordType.MULTIPLE && { effective_time: addDoorLockOfflineTempMultipleTimeDto.effectiveTime, invalid_time: addDoorLockOfflineTempMultipleTimeDto.invalidTime, }), @@ -711,7 +720,7 @@ export class DoorLockService { schedule_list: scheduleList, }), - type: '0', + type: EnableDisableStatusEnum.DISABLED, }, }); @@ -725,7 +734,15 @@ export class DoorLockService { } getWorkingDayValue(days) { // Array representing the days of the week - const weekDays = ['Sat', 'Fri', 'Thu', 'Wed', 'Tue', 'Mon', 'Sun']; + const weekDays = [ + DaysEnum.SAT, + DaysEnum.FRI, + DaysEnum.THU, + DaysEnum.WED, + DaysEnum.TUE, + DaysEnum.MON, + DaysEnum.SUN, + ]; // Initialize a binary string with 7 bits let binaryString = '0000000'; @@ -734,10 +751,10 @@ export class DoorLockService { days.forEach((day) => { const index = weekDays.indexOf(day); if (index !== -1) { - // Set the corresponding bit to '1' + // Set the corresponding bit to EnableDisableStatusEnum.ENABLED binaryString = binaryString.substring(0, index) + - '1' + + EnableDisableStatusEnum.ENABLED + binaryString.substring(index + 1); } }); @@ -749,17 +766,27 @@ export class DoorLockService { } getDaysFromWorkingDayValue(workingDayValue) { // Array representing the days of the week - const weekDays = ['Sat', 'Fri', 'Thu', 'Wed', 'Tue', 'Mon', 'Sun']; + const weekDays = [ + DaysEnum.SAT, + DaysEnum.FRI, + DaysEnum.THU, + DaysEnum.WED, + DaysEnum.TUE, + DaysEnum.MON, + DaysEnum.SUN, + ]; // Convert the integer to a binary string and pad with leading zeros to ensure 7 bits - const binaryString = workingDayValue.toString(2).padStart(7, '0'); + const binaryString = workingDayValue + .toString(2) + .padStart(7, EnableDisableStatusEnum.DISABLED); // Initialize an array to hold the days of the week const days = []; // Iterate through the binary string and weekDays array for (let i = 0; i < binaryString.length; i++) { - if (binaryString[i] === '1') { + if (binaryString[i] === EnableDisableStatusEnum.ENABLED) { days.push(weekDays[i]); } } @@ -769,8 +796,8 @@ export class DoorLockService { timeToMinutes(timeStr) { try { // Special case for "24:00" - if (timeStr === '24:00') { - return 1440; + if (timeStr === CommonHours.TWENTY_FOUR) { + return CommonHourMinutes.TWENTY_FOUR; } // Regular expression to validate the 24-hour time format (HH:MM) @@ -798,20 +825,26 @@ export class DoorLockService { if ( typeof totalMinutes !== 'number' || totalMinutes < 0 || - totalMinutes > 1440 + totalMinutes > CommonHourMinutes.TWENTY_FOUR ) { throw new Error('Invalid minutes value'); } - if (totalMinutes === 1440) { - return '24:00'; + if (totalMinutes === CommonHourMinutes.TWENTY_FOUR) { + return CommonHours.TWENTY_FOUR; } const hours = Math.floor(totalMinutes / 60); const minutes = totalMinutes % 60; - const formattedHours = String(hours).padStart(2, '0'); - const formattedMinutes = String(minutes).padStart(2, '0'); + const formattedHours = String(hours).padStart( + 2, + EnableDisableStatusEnum.DISABLED, + ); + const formattedMinutes = String(minutes).padStart( + 2, + EnableDisableStatusEnum.DISABLED, + ); return `${formattedHours}:${formattedMinutes}`; } catch (error) { @@ -826,6 +859,7 @@ export class DoorLockService { return await this.deviceRepository.findOne({ where: { uuid: deviceUuid, + isActive: true, }, ...(withProductDevice && { relations: ['productDevice'] }), }); diff --git a/src/door-lock/services/encryption.services.ts b/src/door-lock/services/encryption.services.ts index 7e56afe..b87ef03 100644 --- a/src/door-lock/services/encryption.services.ts +++ b/src/door-lock/services/encryption.services.ts @@ -1,6 +1,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import * as CryptoJS from 'crypto-js'; import { ConfigService } from '@nestjs/config'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @Injectable() export class PasswordEncryptionService { @@ -43,7 +44,9 @@ export class PasswordEncryptionService { 'auth-config.SECRET_KEY', ); // The accessSecret must be 32 bytes, ensure it is properly padded or truncated - const paddedAccessSecret = accessSecret.padEnd(32, '0').slice(0, 32); + const paddedAccessSecret = accessSecret + .padEnd(32, EnableDisableStatusEnum.DISABLED) + .slice(0, 32); const plainTextTicketKey = this.decrypt(ticketKey, paddedAccessSecret); return this.encrypt(plainTextPassword, plainTextTicketKey); diff --git a/src/error-message/error-message.service.spec.ts b/src/error-message/error-message.service.spec.ts new file mode 100644 index 0000000..ae9c353 --- /dev/null +++ b/src/error-message/error-message.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ErrorMessageService } from './error-message.service'; + +describe('ErrorMessageService', () => { + let service: ErrorMessageService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ErrorMessageService], + }).compile(); + + service = module.get(ErrorMessageService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/error-message/error-message.service.ts b/src/error-message/error-message.service.ts new file mode 100644 index 0000000..83f7497 --- /dev/null +++ b/src/error-message/error-message.service.ts @@ -0,0 +1,40 @@ +// src/common/services/error-message.service.ts +import { Injectable } from '@nestjs/common'; +type ErrorMessageKey = keyof typeof ErrorMessageService.prototype.messages; + +@Injectable() +export class ErrorMessageService { + public readonly messages = { + NOT_FOUND: '{entity} not found', // Single key for "not found" errors + INVALID_MINUTES: 'Invalid minutes value', + INVALID_TIME_FORMAT: 'Invalid time format', + USER_NOT_FOUND: '{entity} not found', // Can reuse NOT_FOUND if desired + INTERNAL_SERVER_ERROR: 'Internal server error', + ERROR_ADDING_TEMP_PASSWORD: + 'Error adding {type} temporary password from Tuya', + INVALID_UUID: 'Invalid {entity} UUID', + USER_ALREADY_BELONGS: 'This user already belongs to this {entity}', + USER_HAS_NO_ENTITIES: 'This user has no {entity}', + DEVICE_OPERATION_FAILED: 'All device operations failed', + REQUEST_FAILED: 'Error processing {operation} request', + COOLDOWN_ERROR: + 'Please wait {time} more seconds before requesting a new OTP.', + }; + + getMessage( + key: ErrorMessageKey, + params?: Record, + ): string { + let message = this.messages[key] || 'Unknown error'; + + // Replace placeholders with provided params + if (params) { + Object.keys(params).forEach((param) => { + const regex = new RegExp(`{${param}}`, 'g'); + message = message.replace(regex, params[param].toString()); + }); + } + + return message; + } +} diff --git a/src/floor/controllers/floor.controller.ts b/src/floor/controllers/floor.controller.ts index b4940fe..9142db6 100644 --- a/src/floor/controllers/floor.controller.ts +++ b/src/floor/controllers/floor.controller.ts @@ -3,7 +3,6 @@ import { Body, Controller, Get, - HttpException, HttpStatus, Param, Post, @@ -20,11 +19,13 @@ import { CheckUserFloorGuard } from 'src/guards/user.floor.guard'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { AdminRoleGuard } from 'src/guards/admin.role.guard'; import { FloorPermissionGuard } from 'src/guards/floor.permission.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { SpaceType } from '@app/common/constants/space-type.enum'; @ApiTags('Floor Module') @Controller({ - version: '1', - path: 'floor', + version: EnableDisableStatusEnum.ENABLED, + path: SpaceType.FLOOR, }) export class FloorController { constructor(private readonly floorService: FloorService) {} @@ -33,35 +34,21 @@ export class FloorController { @UseGuards(JwtAuthGuard, CheckBuildingTypeGuard) @Post() async addFloor(@Body() addFloorDto: AddFloorDto) { - try { - const floor = await this.floorService.addFloor(addFloorDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Floor added successfully', - data: floor, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const floor = await this.floorService.addFloor(addFloorDto); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'Floor added successfully', + data: floor, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard, FloorPermissionGuard) @Get(':floorUuid') async getFloorByUuid(@Param('floorUuid') floorUuid: string) { - try { - const floor = await this.floorService.getFloorByUuid(floorUuid); - return floor; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const floor = await this.floorService.getFloorByUuid(floorUuid); + return floor; } @ApiBearerAuth() @@ -71,85 +58,47 @@ export class FloorController { @Param('floorUuid') floorUuid: string, @Query() query: GetFloorChildDto, ) { - try { - const floor = await this.floorService.getFloorChildByUuid( - floorUuid, - query, - ); - return floor; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const floor = await this.floorService.getFloorChildByUuid(floorUuid, query); + return floor; } @ApiBearerAuth() @UseGuards(JwtAuthGuard, FloorPermissionGuard) @Get('parent/:floorUuid') async getFloorParentByUuid(@Param('floorUuid') floorUuid: string) { - try { - const floor = await this.floorService.getFloorParentByUuid(floorUuid); - return floor; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const floor = await this.floorService.getFloorParentByUuid(floorUuid); + return floor; } @ApiBearerAuth() @UseGuards(AdminRoleGuard, CheckUserFloorGuard) @Post('user') async addUserFloor(@Body() addUserFloorDto: AddUserFloorDto) { - try { - await this.floorService.addUserFloor(addUserFloorDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'user floor added successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.floorService.addUserFloor(addUserFloorDto); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'user floor added successfully', + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('user/:userUuid') async getFloorsByUserId(@Param('userUuid') userUuid: string) { - try { - return await this.floorService.getFloorsByUserId(userUuid); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.floorService.getFloorsByUserId(userUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard, FloorPermissionGuard) - @Put('rename/:floorUuid') + @Put(':floorUuid') async renameFloorByUuid( @Param('floorUuid') floorUuid: string, @Body() updateFloorNameDto: UpdateFloorNameDto, ) { - try { - const floor = await this.floorService.renameFloorByUuid( - floorUuid, - updateFloorNameDto, - ); - return floor; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const floor = await this.floorService.renameFloorByUuid( + floorUuid, + updateFloorNameDto, + ); + return floor; } } diff --git a/src/floor/dtos/get.floor.dto.ts b/src/floor/dtos/get.floor.dto.ts index 23a8e56..957f81b 100644 --- a/src/floor/dtos/get.floor.dto.ts +++ b/src/floor/dtos/get.floor.dto.ts @@ -1,3 +1,4 @@ +import { BooleanValues } from '@app/common/constants/boolean-values.enum'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { @@ -45,7 +46,7 @@ export class GetFloorChildDto { @IsOptional() @IsBoolean() @Transform((value) => { - return value.obj.includeSubSpaces === 'true'; + return value.obj.includeSubSpaces === BooleanValues.TRUE; }) public includeSubSpaces: boolean = false; } diff --git a/src/floor/services/floor.service.ts b/src/floor/services/floor.service.ts index 5113206..1f7604f 100644 --- a/src/floor/services/floor.service.ts +++ b/src/floor/services/floor.service.ts @@ -18,6 +18,8 @@ import { import { SpaceEntity } from '@app/common/modules/space/entities'; import { UpdateFloorNameDto } from '../dtos/update.floor.dto'; import { UserSpaceRepository } from '@app/common/modules/user/repositories'; +import { SpaceType } from '@app/common/constants/space-type.enum'; +import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; @Injectable() export class FloorService { @@ -31,7 +33,7 @@ export class FloorService { try { const spaceType = await this.spaceTypeRepository.findOne({ where: { - type: 'floor', + type: SpaceType.FLOOR, }, }); @@ -52,12 +54,16 @@ export class FloorService { where: { uuid: floorUuid, spaceType: { - type: 'floor', + type: SpaceType.FLOOR, }, }, relations: ['spaceType'], }); - if (!floor || !floor.spaceType || floor.spaceType.type !== 'floor') { + if ( + !floor || + !floor.spaceType || + floor.spaceType.type !== SpaceType.FLOOR + ) { throw new BadRequestException('Invalid floor UUID'); } @@ -88,7 +94,11 @@ export class FloorService { relations: ['children', 'spaceType'], }); - if (!space || !space.spaceType || space.spaceType.type !== 'floor') { + if ( + !space || + !space.spaceType || + space.spaceType.type !== SpaceType.FLOOR + ) { throw new BadRequestException('Invalid floor UUID'); } const totalCount = await this.spaceRepository.count({ @@ -135,9 +145,9 @@ export class FloorService { return children .filter( (child) => - child.spaceType.type !== 'floor' && - child.spaceType.type !== 'building' && - child.spaceType.type !== 'community', + child.spaceType.type !== SpaceType.FLOOR && + child.spaceType.type !== SpaceType.BUILDING && + child.spaceType.type !== SpaceType.COMMUNITY, ) // Filter remaining floor and building and community types .map((child) => ({ uuid: child.uuid, @@ -150,9 +160,9 @@ export class FloorService { children .filter( (child) => - child.spaceType.type !== 'floor' && - child.spaceType.type !== 'building' && - child.spaceType.type !== 'community', + child.spaceType.type !== SpaceType.FLOOR && + child.spaceType.type !== SpaceType.BUILDING && + child.spaceType.type !== SpaceType.COMMUNITY, ) // Filter remaining floor and building and community types .map(async (child) => ({ uuid: child.uuid, @@ -171,12 +181,16 @@ export class FloorService { where: { uuid: floorUuid, spaceType: { - type: 'floor', + type: SpaceType.FLOOR, }, }, relations: ['spaceType', 'parent', 'parent.spaceType'], }); - if (!floor || !floor.spaceType || floor.spaceType.type !== 'floor') { + if ( + !floor || + !floor.spaceType || + floor.spaceType.type !== SpaceType.FLOOR + ) { throw new BadRequestException('Invalid floor UUID'); } @@ -207,7 +221,7 @@ export class FloorService { relations: ['space', 'space.spaceType'], where: { user: { uuid: userUuid }, - space: { spaceType: { type: 'floor' } }, + space: { spaceType: { type: SpaceType.FLOOR } }, }, }); @@ -239,7 +253,7 @@ export class FloorService { space: { uuid: addUserFloorDto.floorUuid }, }); } catch (err) { - if (err.code === '23505') { + if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) { throw new HttpException( 'User already belongs to this floor', HttpStatus.BAD_REQUEST, @@ -261,7 +275,11 @@ export class FloorService { relations: ['spaceType'], }); - if (!floor || !floor.spaceType || floor.spaceType.type !== 'floor') { + if ( + !floor || + !floor.spaceType || + floor.spaceType.type !== SpaceType.FLOOR + ) { throw new BadRequestException('Invalid floor UUID'); } diff --git a/src/group/controllers/group.controller.ts b/src/group/controllers/group.controller.ts index ce6fae8..85a4a69 100644 --- a/src/group/controllers/group.controller.ts +++ b/src/group/controllers/group.controller.ts @@ -1,20 +1,13 @@ import { GroupService } from '../services/group.service'; -import { - Controller, - Get, - UseGuards, - Param, - HttpException, - HttpStatus, - Req, -} from '@nestjs/common'; +import { Controller, Get, UseGuards, Param, Req } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { UnitPermissionGuard } from 'src/guards/unit.permission.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @ApiTags('Group Module') @Controller({ - version: '1', + version: EnableDisableStatusEnum.ENABLED, path: 'group', }) export class GroupController { @@ -24,14 +17,7 @@ export class GroupController { @UseGuards(JwtAuthGuard, UnitPermissionGuard) @Get(':unitUuid') async getGroupsBySpaceUuid(@Param('unitUuid') unitUuid: string) { - try { - return await this.groupService.getGroupsByUnitUuid(unitUuid); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.groupService.getGroupsByUnitUuid(unitUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard, UnitPermissionGuard) @@ -41,19 +27,12 @@ export class GroupController { @Param('groupName') groupName: string, @Req() req: any, ) { - try { - const userUuid = req.user.uuid; + const userUuid = req.user.uuid; - return await this.groupService.getUnitDevicesByGroupName( - unitUuid, - groupName, - userUuid, - ); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.groupService.getUnitDevicesByGroupName( + unitUuid, + groupName, + userUuid, + ); } } diff --git a/src/group/services/group.service.ts b/src/group/services/group.service.ts index 4f1a1f7..f549afc 100644 --- a/src/group/services/group.service.ts +++ b/src/group/services/group.service.ts @@ -7,6 +7,7 @@ import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { ProductRepository } from '@app/common/modules/product/repositories'; import { PermissionType } from '@app/common/constants/permission-type.enum'; import { In } from 'typeorm'; +import { ProductType } from '@app/common/constants/product-type.enum'; @Injectable() export class GroupService { @@ -44,8 +45,24 @@ export class GroupService { }); const uniqueGroupNames = [...new Set(groupNames)]; - - return uniqueGroupNames.map((groupName) => ({ groupName })); + const groups = uniqueGroupNames.map((groupName) => ({ + groupName: groupName as ProductType, + })); + const allowedProductTypes = [ + ProductType.ONE_1TG, + ProductType.TWO_2TG, + ProductType.THREE_3TG, + ProductType.THREE_G, + ProductType.TWO_G, + ProductType.ONE_G, + ProductType.GD, + ProductType.WH, + ProductType.AC, + ProductType.CUR, + ]; + return groups.filter((group) => + allowedProductTypes.includes(group.groupName), + ); } catch (error) { throw new HttpException( 'This unit does not have any groups', diff --git a/src/guards/building.permission.guard.ts b/src/guards/building.permission.guard.ts index d73f19b..5d496a9 100644 --- a/src/guards/building.permission.guard.ts +++ b/src/guards/building.permission.guard.ts @@ -1,3 +1,4 @@ +import { SpaceType } from '@app/common/constants/space-type.enum'; import { SpacePermissionService } from '@app/common/helper/services/space.permission.service'; import { BadRequestException, @@ -24,7 +25,7 @@ export class BuildingPermissionGuard implements CanActivate { await this.permissionService.checkUserPermission( buildingUuid, user.uuid, - 'building', + SpaceType.BUILDING, ); return true; diff --git a/src/guards/building.type.guard.ts b/src/guards/building.type.guard.ts index 1e36d9f..dd2870e 100644 --- a/src/guards/building.type.guard.ts +++ b/src/guards/building.type.guard.ts @@ -1,3 +1,4 @@ +import { SpaceType } from '@app/common/constants/space-type.enum'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { Injectable, @@ -42,7 +43,7 @@ export class CheckBuildingTypeGuard implements CanActivate { if ( !buildingData || !buildingData.spaceType || - buildingData.spaceType.type !== 'building' + buildingData.spaceType.type !== SpaceType.BUILDING ) { throw new BadRequestException('Invalid building UUID'); } diff --git a/src/guards/community.permission.guard.ts b/src/guards/community.permission.guard.ts index 2e44cd1..a078b70 100644 --- a/src/guards/community.permission.guard.ts +++ b/src/guards/community.permission.guard.ts @@ -1,3 +1,4 @@ +import { SpaceType } from '@app/common/constants/space-type.enum'; import { SpacePermissionService } from '@app/common/helper/services/space.permission.service'; import { BadRequestException, @@ -24,7 +25,7 @@ export class CommunityPermissionGuard implements CanActivate { await this.permissionService.checkUserPermission( communityUuid, user.uuid, - 'community', + SpaceType.COMMUNITY, ); return true; diff --git a/src/guards/community.type.guard.ts b/src/guards/community.type.guard.ts index e8212f4..9dc5d01 100644 --- a/src/guards/community.type.guard.ts +++ b/src/guards/community.type.guard.ts @@ -6,6 +6,7 @@ import { } from '@nestjs/common'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException } from '@nestjs/common'; +import { SpaceType } from '@app/common/constants/space-type.enum'; @Injectable() export class CheckCommunityTypeGuard implements CanActivate { @@ -43,7 +44,7 @@ export class CheckCommunityTypeGuard implements CanActivate { if ( !communityData || !communityData.spaceType || - communityData.spaceType.type !== 'community' + communityData.spaceType.type !== SpaceType.COMMUNITY ) { throw new BadRequestException('Invalid community UUID'); } diff --git a/src/guards/device.product.guard.ts b/src/guards/device.product.guard.ts index 108307a..6bf84cc 100644 --- a/src/guards/device.product.guard.ts +++ b/src/guards/device.product.guard.ts @@ -28,7 +28,7 @@ export class CheckProductUuidForAllDevicesGuard implements CanActivate { async checkAllDevicesHaveSameProductUuid(deviceUuids: string[]) { const firstDevice = await this.deviceRepository.findOne({ - where: { uuid: deviceUuids[0] }, + where: { uuid: deviceUuids[0], isActive: true }, relations: ['productDevice'], }); @@ -40,7 +40,7 @@ export class CheckProductUuidForAllDevicesGuard implements CanActivate { for (let i = 1; i < deviceUuids.length; i++) { const device = await this.deviceRepository.findOne({ - where: { uuid: deviceUuids[i] }, + where: { uuid: deviceUuids[i], isActive: true }, relations: ['productDevice'], }); diff --git a/src/guards/floor.permission.guard.ts b/src/guards/floor.permission.guard.ts index 5e5c6ce..7092264 100644 --- a/src/guards/floor.permission.guard.ts +++ b/src/guards/floor.permission.guard.ts @@ -1,3 +1,4 @@ +import { SpaceType } from '@app/common/constants/space-type.enum'; import { SpacePermissionService } from '@app/common/helper/services/space.permission.service'; import { BadRequestException, @@ -24,7 +25,7 @@ export class FloorPermissionGuard implements CanActivate { await this.permissionService.checkUserPermission( floorUuid, user.uuid, - 'floor', + SpaceType.FLOOR, ); return true; diff --git a/src/guards/floor.type.guard.ts b/src/guards/floor.type.guard.ts index 8064cb2..3e6b875 100644 --- a/src/guards/floor.type.guard.ts +++ b/src/guards/floor.type.guard.ts @@ -1,3 +1,4 @@ +import { SpaceType } from '@app/common/constants/space-type.enum'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { Injectable, @@ -42,7 +43,7 @@ export class CheckFloorTypeGuard implements CanActivate { if ( !floorData || !floorData.spaceType || - floorData.spaceType.type !== 'floor' + floorData.spaceType.type !== SpaceType.FLOOR ) { throw new BadRequestException('Invalid floor UUID'); } diff --git a/src/guards/room.guard.ts b/src/guards/room.guard.ts index c5ed514..bd63520 100644 --- a/src/guards/room.guard.ts +++ b/src/guards/room.guard.ts @@ -8,6 +8,7 @@ import { import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException, NotFoundException } from '@nestjs/common'; import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { SpaceType } from '@app/common/constants/space-type.enum'; @Injectable() export class CheckRoomGuard implements CanActivate { @@ -43,7 +44,7 @@ export class CheckRoomGuard implements CanActivate { where: { uuid: roomUuid, spaceType: { - type: 'room', + type: SpaceType.ROOM, }, }, }); @@ -55,6 +56,7 @@ export class CheckRoomGuard implements CanActivate { const response = await this.deviceRepository.findOne({ where: { uuid: deviceUuid, + isActive: true, }, }); diff --git a/src/guards/room.permission.guard.ts b/src/guards/room.permission.guard.ts index 721a92f..d1e7042 100644 --- a/src/guards/room.permission.guard.ts +++ b/src/guards/room.permission.guard.ts @@ -1,3 +1,4 @@ +import { SpaceType } from '@app/common/constants/space-type.enum'; import { SpacePermissionService } from '@app/common/helper/services/space.permission.service'; import { BadRequestException, @@ -24,7 +25,7 @@ export class RoomPermissionGuard implements CanActivate { await this.permissionService.checkUserPermission( roomUuid, user.uuid, - 'room', + SpaceType.ROOM, ); return true; diff --git a/src/guards/unit.permission.guard.ts b/src/guards/unit.permission.guard.ts index e0d4958..fe2b969 100644 --- a/src/guards/unit.permission.guard.ts +++ b/src/guards/unit.permission.guard.ts @@ -1,3 +1,4 @@ +import { SpaceType } from '@app/common/constants/space-type.enum'; import { SpacePermissionService } from '@app/common/helper/services/space.permission.service'; import { BadRequestException, @@ -24,7 +25,7 @@ export class UnitPermissionGuard implements CanActivate { await this.permissionService.checkUserPermission( unitUuid, user.uuid, - 'unit', + SpaceType.UNIT, ); return true; diff --git a/src/guards/unit.type.guard.ts b/src/guards/unit.type.guard.ts index f1e292f..a753756 100644 --- a/src/guards/unit.type.guard.ts +++ b/src/guards/unit.type.guard.ts @@ -1,3 +1,4 @@ +import { SpaceType } from '@app/common/constants/space-type.enum'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { Injectable, @@ -42,7 +43,7 @@ export class CheckUnitTypeGuard implements CanActivate { if ( !unitData || !unitData.spaceType || - unitData.spaceType.type !== 'unit' + unitData.spaceType.type !== SpaceType.UNIT ) { throw new BadRequestException('Invalid unit UUID'); } diff --git a/src/guards/user.building.guard.ts b/src/guards/user.building.guard.ts index b47124d..3f74500 100644 --- a/src/guards/user.building.guard.ts +++ b/src/guards/user.building.guard.ts @@ -7,6 +7,7 @@ import { import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException, NotFoundException } from '@nestjs/common'; import { UserRepository } from '@app/common/modules/user/repositories'; +import { SpaceType } from '@app/common/constants/space-type.enum'; @Injectable() export class CheckUserBuildingGuard implements CanActivate { @@ -43,7 +44,7 @@ export class CheckUserBuildingGuard implements CanActivate { private async checkBuildingIsFound(spaceUuid: string) { const spaceData = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid, spaceType: { type: 'building' } }, + where: { uuid: spaceUuid, spaceType: { type: SpaceType.BUILDING } }, relations: ['spaceType'], }); if (!spaceData) { diff --git a/src/guards/user.community.guard.ts b/src/guards/user.community.guard.ts index 04e08b4..e8dea71 100644 --- a/src/guards/user.community.guard.ts +++ b/src/guards/user.community.guard.ts @@ -7,6 +7,7 @@ import { import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException, NotFoundException } from '@nestjs/common'; import { UserRepository } from '@app/common/modules/user/repositories'; +import { SpaceType } from '@app/common/constants/space-type.enum'; @Injectable() export class CheckUserCommunityGuard implements CanActivate { @@ -43,7 +44,7 @@ export class CheckUserCommunityGuard implements CanActivate { private async checkCommunityIsFound(spaceUuid: string) { const spaceData = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid, spaceType: { type: 'community' } }, + where: { uuid: spaceUuid, spaceType: { type: SpaceType.COMMUNITY } }, relations: ['spaceType'], }); if (!spaceData) { diff --git a/src/guards/user.device.controllable.permission.guard.ts b/src/guards/user.device.controllable.permission.guard.ts index 2684c8d..3736368 100644 --- a/src/guards/user.device.controllable.permission.guard.ts +++ b/src/guards/user.device.controllable.permission.guard.ts @@ -58,7 +58,11 @@ export class CheckUserHaveControllablePermission implements CanActivate { deviceUuid: string, ): Promise { const device = await this.deviceRepository.findOne({ - where: { uuid: deviceUuid, permission: { userUuid: userUuid } }, + where: { + uuid: deviceUuid, + isActive: true, + permission: { userUuid: userUuid }, + }, relations: ['permission', 'permission.permissionType'], }); diff --git a/src/guards/user.device.permission.guard.ts b/src/guards/user.device.permission.guard.ts index e63a08e..adf78e4 100644 --- a/src/guards/user.device.permission.guard.ts +++ b/src/guards/user.device.permission.guard.ts @@ -59,7 +59,11 @@ export class CheckUserHavePermission implements CanActivate { deviceUuid: string, ): Promise { const device = await this.deviceRepository.findOne({ - where: { uuid: deviceUuid, permission: { userUuid: userUuid } }, + where: { + uuid: deviceUuid, + permission: { userUuid: userUuid }, + isActive: true, + }, relations: ['permission', 'permission.permissionType'], }); diff --git a/src/guards/user.floor.guard.ts b/src/guards/user.floor.guard.ts index 9235fb8..6faa520 100644 --- a/src/guards/user.floor.guard.ts +++ b/src/guards/user.floor.guard.ts @@ -7,6 +7,7 @@ import { import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException, NotFoundException } from '@nestjs/common'; import { UserRepository } from '@app/common/modules/user/repositories'; +import { SpaceType } from '@app/common/constants/space-type.enum'; @Injectable() export class CheckUserFloorGuard implements CanActivate { @@ -43,7 +44,7 @@ export class CheckUserFloorGuard implements CanActivate { private async checkFloorIsFound(spaceUuid: string) { const spaceData = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid, spaceType: { type: 'floor' } }, + where: { uuid: spaceUuid, spaceType: { type: SpaceType.FLOOR } }, relations: ['spaceType'], }); if (!spaceData) { diff --git a/src/guards/user.room.guard.ts b/src/guards/user.room.guard.ts index 0ecf817..49c77b8 100644 --- a/src/guards/user.room.guard.ts +++ b/src/guards/user.room.guard.ts @@ -7,6 +7,7 @@ import { import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException, NotFoundException } from '@nestjs/common'; import { UserRepository } from '@app/common/modules/user/repositories'; +import { SpaceType } from '@app/common/constants/space-type.enum'; @Injectable() export class CheckUserRoomGuard implements CanActivate { @@ -43,7 +44,7 @@ export class CheckUserRoomGuard implements CanActivate { private async checkRoomIsFound(spaceUuid: string) { const spaceData = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid, spaceType: { type: 'room' } }, + where: { uuid: spaceUuid, spaceType: { type: SpaceType.ROOM } }, relations: ['spaceType'], }); if (!spaceData) { diff --git a/src/guards/user.unit.guard.ts b/src/guards/user.unit.guard.ts index 4d7b1ab..eb60a27 100644 --- a/src/guards/user.unit.guard.ts +++ b/src/guards/user.unit.guard.ts @@ -7,6 +7,7 @@ import { import { SpaceRepository } from '@app/common/modules/space/repositories'; import { BadRequestException, NotFoundException } from '@nestjs/common'; import { UserRepository } from '@app/common/modules/user/repositories'; +import { SpaceType } from '@app/common/constants/space-type.enum'; @Injectable() export class CheckUserUnitGuard implements CanActivate { @@ -43,7 +44,7 @@ export class CheckUserUnitGuard implements CanActivate { private async checkUnitIsFound(spaceUuid: string) { const spaceData = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid, spaceType: { type: 'unit' } }, + where: { uuid: spaceUuid, spaceType: { type: SpaceType.UNIT } }, relations: ['spaceType'], }); if (!spaceData) { diff --git a/src/main.ts b/src/main.ts index 894ecd1..6dbfc45 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,7 @@ import { setupSwaggerAuthentication } from '../libs/common/src/util/user-auth.sw import { ValidationPipe } from '@nestjs/common'; import { json, urlencoded } from 'body-parser'; import { SeederService } from '@app/common/seed/services/seeder.service'; +import { HttpExceptionFilter } from './common/filters/http-exception/http-exception.filter'; async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -15,6 +16,7 @@ async function bootstrap() { // Set the body parser limit to 1 MB app.use(json({ limit: '1mb' })); app.use(urlencoded({ limit: '1mb', extended: true })); + app.useGlobalFilters(new HttpExceptionFilter()); app.use( rateLimit({ diff --git a/src/region/controllers/region.controller.ts b/src/region/controllers/region.controller.ts index d914642..c1b82da 100644 --- a/src/region/controllers/region.controller.ts +++ b/src/region/controllers/region.controller.ts @@ -1,24 +1,23 @@ -import { Controller, Get, HttpException, HttpStatus } from '@nestjs/common'; +import { Controller, Get } from '@nestjs/common'; import { RegionService } from '../services/region.service'; -import { ApiTags } from '@nestjs/swagger'; +import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @ApiTags('Region Module') @Controller({ - version: '1', - path: 'region', + version: EnableDisableStatusEnum.ENABLED, + path: ControllerRoute.REGION.ROUTE, }) export class RegionController { constructor(private readonly regionService: RegionService) {} @Get() + @ApiOperation({ + summary: ControllerRoute.REGION.ACTIONS.GET_REGIONS_SUMMARY, + description: ControllerRoute.REGION.ACTIONS.GET_REGIONS_DESCRIPTION, + }) async getAllRegions() { - try { - return await this.regionService.getAllRegions(); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.regionService.getAllRegions(); } } diff --git a/src/region/region.module.ts b/src/region/region.module.ts index c0da5ab..22eaa2d 100644 --- a/src/region/region.module.ts +++ b/src/region/region.module.ts @@ -3,9 +3,10 @@ import { RegionService } from './services/region.service'; import { RegionController } from './controllers/region.controller'; import { ConfigModule } from '@nestjs/config'; import { RegionRepository } from '@app/common/modules/region/repositories'; +import { CommonModule } from '@app/common'; @Module({ - imports: [ConfigModule], + imports: [ConfigModule, CommonModule], controllers: [RegionController], providers: [RegionService, RegionRepository], exports: [RegionService], diff --git a/src/region/services/region.service.ts b/src/region/services/region.service.ts index 580f285..9061e0a 100644 --- a/src/region/services/region.service.ts +++ b/src/region/services/region.service.ts @@ -2,23 +2,33 @@ import { BadRequestException, HttpException, HttpStatus, + Inject, Injectable, } from '@nestjs/common'; import { RegionRepository } from '@app/common/modules/region/repositories'; +import { ErrorMessageService } from 'src/error-message/error-message.service'; @Injectable() export class RegionService { - constructor(private readonly regionRepository: RegionRepository) {} + constructor( + private readonly regionRepository: RegionRepository, + @Inject(ErrorMessageService) + private readonly errorMessageService: ErrorMessageService, + ) {} async getAllRegions() { try { const regions = await this.regionRepository.find(); - return regions; } catch (err) { if (err instanceof BadRequestException) { throw err; // Re-throw BadRequestException } else { - throw new HttpException('Regions found', HttpStatus.NOT_FOUND); + throw new HttpException( + this.errorMessageService.getMessage('NOT_FOUND', { + entity: 'Regions', + }), + HttpStatus.NOT_FOUND, + ); } } } diff --git a/src/role/controllers/role.controller.ts b/src/role/controllers/role.controller.ts index cb2038d..f08adb4 100644 --- a/src/role/controllers/role.controller.ts +++ b/src/role/controllers/role.controller.ts @@ -2,7 +2,6 @@ import { Body, Controller, Get, - HttpException, HttpStatus, Post, UseGuards, @@ -11,10 +10,11 @@ import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { RoleService } from '../services/role.service'; import { AddUserRoleDto } from '../dtos'; import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @ApiTags('Role Module') @Controller({ - version: '1', + version: EnableDisableStatusEnum.ENABLED, path: 'role', }) export class RoleController { @@ -23,32 +23,21 @@ export class RoleController { @UseGuards(SuperAdminRoleGuard) @Get('types') async fetchRoleTypes() { - try { - const roleTypes = await this.roleService.fetchRoleTypes(); - return { - statusCode: HttpStatus.OK, - message: 'Role Types fetched Successfully', - data: roleTypes, - }; - } catch (err) { - throw new Error(err); - } + const roleTypes = await this.roleService.fetchRoleTypes(); + return { + statusCode: HttpStatus.OK, + message: 'Role Types fetched Successfully', + data: roleTypes, + }; } @ApiBearerAuth() @UseGuards(SuperAdminRoleGuard) @Post() async addUserRoleType(@Body() addUserRoleDto: AddUserRoleDto) { - try { - await this.roleService.addUserRoleType(addUserRoleDto); - return { - statusCode: HttpStatus.OK, - message: 'User Role Added Successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.roleService.addUserRoleType(addUserRoleDto); + return { + statusCode: HttpStatus.OK, + message: 'User Role Added Successfully', + }; } } diff --git a/src/role/services/role.service.ts b/src/role/services/role.service.ts index 42076d4..ba10f44 100644 --- a/src/role/services/role.service.ts +++ b/src/role/services/role.service.ts @@ -3,6 +3,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { AddUserRoleDto } from '../dtos/role.add.dto'; import { UserRoleRepository } from '@app/common/modules/user/repositories'; import { QueryFailedError } from 'typeorm'; +import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; @Injectable() export class RoleService { @@ -24,7 +25,7 @@ export class RoleService { } catch (error) { if ( error instanceof QueryFailedError && - error.driverError.code === '23505' + error.driverError.code === CommonErrorCodes.DUPLICATE_ENTITY ) { // Postgres unique constraint violation error code throw new HttpException( diff --git a/src/room/controllers/room.controller.ts b/src/room/controllers/room.controller.ts index 0a92e57..e3eb687 100644 --- a/src/room/controllers/room.controller.ts +++ b/src/room/controllers/room.controller.ts @@ -3,7 +3,6 @@ import { Body, Controller, Get, - HttpException, HttpStatus, Param, Post, @@ -18,11 +17,13 @@ import { CheckUserRoomGuard } from 'src/guards/user.room.guard'; import { AdminRoleGuard } from 'src/guards/admin.role.guard'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { RoomPermissionGuard } from 'src/guards/room.permission.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { SpaceType } from '@app/common/constants/space-type.enum'; @ApiTags('Room Module') @Controller({ - version: '1', - path: 'room', + version: EnableDisableStatusEnum.ENABLED, + path: SpaceType.ROOM, }) export class RoomController { constructor(private readonly roomService: RoomService) {} @@ -31,101 +32,59 @@ export class RoomController { @UseGuards(JwtAuthGuard, CheckUnitTypeGuard) @Post() async addRoom(@Body() addRoomDto: AddRoomDto) { - try { - const room = await this.roomService.addRoom(addRoomDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Room added successfully', - data: room, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const room = await this.roomService.addRoom(addRoomDto); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'Room added successfully', + data: room, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard, RoomPermissionGuard) @Get(':roomUuid') async getRoomByUuid(@Param('roomUuid') roomUuid: string) { - try { - const room = await this.roomService.getRoomByUuid(roomUuid); - return room; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const room = await this.roomService.getRoomByUuid(roomUuid); + return room; } @ApiBearerAuth() @UseGuards(JwtAuthGuard, RoomPermissionGuard) @Get('parent/:roomUuid') async getRoomParentByUuid(@Param('roomUuid') roomUuid: string) { - try { - const room = await this.roomService.getRoomParentByUuid(roomUuid); - return room; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const room = await this.roomService.getRoomParentByUuid(roomUuid); + return room; } @ApiBearerAuth() @UseGuards(AdminRoleGuard, CheckUserRoomGuard) @Post('user') async addUserRoom(@Body() addUserRoomDto: AddUserRoomDto) { - try { - await this.roomService.addUserRoom(addUserRoomDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'user room added successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.roomService.addUserRoom(addUserRoomDto); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'user room added successfully', + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('user/:userUuid') async getRoomsByUserId(@Param('userUuid') userUuid: string) { - try { - return await this.roomService.getRoomsByUserId(userUuid); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.roomService.getRoomsByUserId(userUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard, RoomPermissionGuard) - @Put('rename/:roomUuid') + @Put(':roomUuid') async renameRoomByUuid( @Param('roomUuid') roomUuid: string, @Body() updateRoomNameDto: UpdateRoomNameDto, ) { - try { - const room = await this.roomService.renameRoomByUuid( - roomUuid, - updateRoomNameDto, - ); - return room; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const room = await this.roomService.renameRoomByUuid( + roomUuid, + updateRoomNameDto, + ); + return room; } } diff --git a/src/room/services/room.service.ts b/src/room/services/room.service.ts index 7babd6b..340df0f 100644 --- a/src/room/services/room.service.ts +++ b/src/room/services/room.service.ts @@ -15,6 +15,8 @@ import { } from '../interface/room.interface'; import { UpdateRoomNameDto } from '../dtos/update.room.dto'; import { UserSpaceRepository } from '@app/common/modules/user/repositories'; +import { SpaceType } from '@app/common/constants/space-type.enum'; +import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; @Injectable() export class RoomService { @@ -28,7 +30,7 @@ export class RoomService { try { const spaceType = await this.spaceTypeRepository.findOne({ where: { - type: 'room', + type: SpaceType.ROOM, }, }); @@ -49,12 +51,12 @@ export class RoomService { where: { uuid: roomUuid, spaceType: { - type: 'room', + type: SpaceType.ROOM, }, }, relations: ['spaceType'], }); - if (!room || !room.spaceType || room.spaceType.type !== 'room') { + if (!room || !room.spaceType || room.spaceType.type !== SpaceType.ROOM) { throw new BadRequestException('Invalid room UUID'); } @@ -80,12 +82,12 @@ export class RoomService { where: { uuid: roomUuid, spaceType: { - type: 'room', + type: SpaceType.ROOM, }, }, relations: ['spaceType', 'parent', 'parent.spaceType'], }); - if (!room || !room.spaceType || room.spaceType.type !== 'room') { + if (!room || !room.spaceType || room.spaceType.type !== SpaceType.ROOM) { throw new BadRequestException('Invalid room UUID'); } @@ -116,7 +118,7 @@ export class RoomService { relations: ['space', 'space.spaceType'], where: { user: { uuid: userUuid }, - space: { spaceType: { type: 'room' } }, + space: { spaceType: { type: SpaceType.ROOM } }, }, }); @@ -145,7 +147,7 @@ export class RoomService { space: { uuid: addUserRoomDto.roomUuid }, }); } catch (err) { - if (err.code === '23505') { + if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) { throw new HttpException( 'User already belongs to this room', HttpStatus.BAD_REQUEST, @@ -167,7 +169,7 @@ export class RoomService { relations: ['spaceType'], }); - if (!room || !room.spaceType || room.spaceType.type !== 'room') { + if (!room || !room.spaceType || room.spaceType.type !== SpaceType.ROOM) { throw new BadRequestException('Invalid room UUID'); } diff --git a/src/scene/controllers/scene.controller.ts b/src/scene/controllers/scene.controller.ts index 40af2a8..1186433 100644 --- a/src/scene/controllers/scene.controller.ts +++ b/src/scene/controllers/scene.controller.ts @@ -4,7 +4,6 @@ import { Controller, Delete, Get, - HttpException, HttpStatus, Param, Post, @@ -14,10 +13,11 @@ import { import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { AddSceneTapToRunDto, UpdateSceneTapToRunDto } from '../dtos/scene.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @ApiTags('Scene Module') @Controller({ - version: '1', + version: EnableDisableStatusEnum.ENABLED, path: 'scene', }) export class SceneController { @@ -27,36 +27,22 @@ export class SceneController { @UseGuards(JwtAuthGuard) @Post('tap-to-run') async addTapToRunScene(@Body() addSceneTapToRunDto: AddSceneTapToRunDto) { - try { - const tapToRunScene = - await this.sceneService.addTapToRunScene(addSceneTapToRunDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Scene added successfully', - data: tapToRunScene, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const tapToRunScene = + await this.sceneService.addTapToRunScene(addSceneTapToRunDto); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'Scene added successfully', + data: tapToRunScene, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('tap-to-run/:unitUuid') async getTapToRunSceneByUnit(@Param('unitUuid') unitUuid: string) { - try { - const tapToRunScenes = - await this.sceneService.getTapToRunSceneByUnit(unitUuid); - return tapToRunScenes; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const tapToRunScenes = + await this.sceneService.getTapToRunSceneByUnit(unitUuid); + return tapToRunScenes; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -65,53 +51,31 @@ export class SceneController { @Param('unitUuid') unitUuid: string, @Param('sceneId') sceneId: string, ) { - try { - await this.sceneService.deleteTapToRunScene(unitUuid, sceneId); - return { - statusCode: HttpStatus.OK, - message: 'Scene Deleted Successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.sceneService.deleteTapToRunScene(unitUuid, sceneId); + return { + statusCode: HttpStatus.OK, + message: 'Scene Deleted Successfully', + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Post('tap-to-run/trigger/:sceneId') async triggerTapToRunScene(@Param('sceneId') sceneId: string) { - try { - await this.sceneService.triggerTapToRunScene(sceneId); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Scene trigger successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.sceneService.triggerTapToRunScene(sceneId); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'Scene trigger successfully', + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('tap-to-run/details/:sceneId') async getTapToRunSceneDetails(@Param('sceneId') sceneId: string) { - try { - const tapToRunScenes = - await this.sceneService.getTapToRunSceneDetails(sceneId); - return tapToRunScenes; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - ``; - } + const tapToRunScenes = + await this.sceneService.getTapToRunSceneDetails(sceneId); + return tapToRunScenes; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -120,22 +84,15 @@ export class SceneController { @Body() updateSceneTapToRunDto: UpdateSceneTapToRunDto, @Param('sceneId') sceneId: string, ) { - try { - const tapToRunScene = await this.sceneService.updateTapToRunScene( - updateSceneTapToRunDto, - sceneId, - ); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Scene updated successfully', - data: tapToRunScene, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const tapToRunScene = await this.sceneService.updateTapToRunScene( + updateSceneTapToRunDto, + sceneId, + ); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'Scene updated successfully', + data: tapToRunScene, + }; } } diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 66bb769..30e53aa 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -18,6 +18,8 @@ import { SceneDetailsResult, } from '../interface/scene.interface'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; +import { SpaceType } from '@app/common/constants/space-type.enum'; +import { ActionExecutorEnum } from '@app/common/constants/automation.enum'; @Injectable() export class SceneService { @@ -63,7 +65,7 @@ export class SceneService { const convertedData = convertKeysToSnakeCase(actions); for (const action of convertedData) { - if (action.action_executor === 'device_issue') { + if (action.action_executor === ActionExecutorEnum.DEVICE_ISSUE) { const device = await this.deviceService.getDeviceByDeviceUuid( action.entity_id, false, @@ -108,12 +110,12 @@ export class SceneService { where: { uuid: unitUuid, spaceType: { - type: 'unit', + type: SpaceType.UNIT, }, }, relations: ['spaceType'], }); - if (!unit || !unit.spaceType || unit.spaceType.type !== 'unit') { + if (!unit || !unit.spaceType || unit.spaceType.type !== SpaceType.UNIT) { throw new BadRequestException('Invalid unit UUID'); } return { @@ -249,7 +251,7 @@ export class SceneService { }); for (const action of actions) { - if (action.actionExecutor === 'device_issue') { + if (action.actionExecutor === ActionExecutorEnum.DEVICE_ISSUE) { const device = await this.deviceService.getDeviceByDeviceTuyaUuid( action.entityId, ); @@ -258,8 +260,8 @@ export class SceneService { action.entityId = device.uuid; } } else if ( - action.actionExecutor !== 'device_issue' && - action.actionExecutor !== 'delay' + action.actionExecutor !== ActionExecutorEnum.DEVICE_ISSUE && + action.actionExecutor !== ActionExecutorEnum.DELAY ) { const sceneDetails = await this.getTapToRunSceneDetailsTuya( action.entityId, diff --git a/src/schedule/controllers/index.ts b/src/schedule/controllers/index.ts new file mode 100644 index 0000000..edd3a1d --- /dev/null +++ b/src/schedule/controllers/index.ts @@ -0,0 +1 @@ +export * from './schedule.controller'; diff --git a/src/schedule/controllers/schedule.controller.ts b/src/schedule/controllers/schedule.controller.ts new file mode 100644 index 0000000..82676bf --- /dev/null +++ b/src/schedule/controllers/schedule.controller.ts @@ -0,0 +1,113 @@ +import { ScheduleService } from '../services/schedule.service'; +import { + Body, + Controller, + Get, + Post, + Param, + HttpStatus, + UseGuards, + Put, + Delete, + Query, +} from '@nestjs/common'; +import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { + AddScheduleDto, + EnableScheduleDto, + GetScheduleDeviceDto, + UpdateScheduleDto, +} from '../dtos/schedule.dto'; + +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; + +@ApiTags('Schedule Module') +@Controller({ + version: EnableDisableStatusEnum.ENABLED, + path: 'schedule', +}) +export class ScheduleController { + constructor(private readonly scheduleService: ScheduleService) {} + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post(':deviceUuid') + async addDeviceSchedule( + @Param('deviceUuid') deviceUuid: string, + @Body() addScheduleDto: AddScheduleDto, + ) { + const schedule = await this.scheduleService.addDeviceSchedule( + deviceUuid, + addScheduleDto, + ); + + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'schedule added successfully', + data: schedule, + }; + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get(':deviceUuid') + async getDeviceScheduleByCategory( + @Param('deviceUuid') deviceUuid: string, + @Query() query: GetScheduleDeviceDto, + ) { + return await this.scheduleService.getDeviceScheduleByCategory( + deviceUuid, + query.category, + ); + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Delete(':deviceUuid/:scheduleId') + async deleteDeviceSchedule( + @Param('deviceUuid') deviceUuid: string, + @Param('scheduleId') scheduleId: string, + ) { + await this.scheduleService.deleteDeviceSchedule(deviceUuid, scheduleId); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'schedule deleted successfully', + }; + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Put('enable/:deviceUuid') + async enableDeviceSchedule( + @Param('deviceUuid') deviceUuid: string, + @Body() enableScheduleDto: EnableScheduleDto, + ) { + await this.scheduleService.enableDeviceSchedule( + deviceUuid, + enableScheduleDto, + ); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'schedule updated successfully', + }; + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Put(':deviceUuid') + async updateDeviceSchedule( + @Param('deviceUuid') deviceUuid: string, + @Body() updateScheduleDto: UpdateScheduleDto, + ) { + const schedule = await this.scheduleService.updateDeviceSchedule( + deviceUuid, + updateScheduleDto, + ); + + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'schedule updated successfully', + data: schedule, + }; + } +} diff --git a/src/schedule/dtos/index.ts b/src/schedule/dtos/index.ts new file mode 100644 index 0000000..8398723 --- /dev/null +++ b/src/schedule/dtos/index.ts @@ -0,0 +1 @@ +export * from './schedule.dto'; diff --git a/src/schedule/dtos/schedule.dto.ts b/src/schedule/dtos/schedule.dto.ts new file mode 100644 index 0000000..09b2156 --- /dev/null +++ b/src/schedule/dtos/schedule.dto.ts @@ -0,0 +1,138 @@ +import { WorkingDays } from '@app/common/constants/working-days'; +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsArray, + IsBoolean, + IsEnum, + IsNotEmpty, + IsString, + ValidateNested, +} from 'class-validator'; +export class FunctionDto { + @ApiProperty({ + description: 'code', + required: true, + }) + @IsString() + @IsNotEmpty() + public code: string; + + @ApiProperty({ + description: 'value', + required: true, + }) + @IsNotEmpty() + public value: any; +} + +// Update the main DTO class +export class AddScheduleDto { + @ApiProperty({ + description: 'category', + required: true, + }) + @IsString() + @IsNotEmpty() + public category: string; + + @ApiProperty({ + description: 'time', + required: true, + }) + @IsString() + @IsNotEmpty() + public time: string; + + @ApiProperty({ + description: 'function', + required: true, + type: FunctionDto, + }) + @ValidateNested() + @Type(() => FunctionDto) + public function: FunctionDto; + + @ApiProperty({ + description: 'days', + enum: WorkingDays, + isArray: true, + required: true, + }) + @IsArray() + @IsEnum(WorkingDays, { each: true }) + @IsNotEmpty() + public days: WorkingDays[]; +} + +export class EnableScheduleDto { + @ApiProperty({ + description: 'scheduleId', + required: true, + }) + @IsString() + @IsNotEmpty() + public scheduleId: string; + + @ApiProperty({ + description: 'enable', + required: true, + }) + @IsBoolean() + @IsNotEmpty() + public enable: boolean; +} +export class GetScheduleDeviceDto { + @ApiProperty({ + description: 'category', + required: true, + }) + @IsString() + @IsNotEmpty() + public category: string; +} + +export class UpdateScheduleDto { + @ApiProperty({ + description: 'scheduleId', + required: true, + }) + @IsString() + @IsNotEmpty() + public scheduleId: string; + @ApiProperty({ + description: 'category', + required: true, + }) + @IsString() + @IsNotEmpty() + public category: string; + + @ApiProperty({ + description: 'time', + required: true, + }) + @IsString() + @IsNotEmpty() + public time: string; + + @ApiProperty({ + description: 'function', + required: true, + type: FunctionDto, + }) + @ValidateNested() + @Type(() => FunctionDto) + public function: FunctionDto; + + @ApiProperty({ + description: 'days', + enum: WorkingDays, + isArray: true, + required: true, + }) + @IsArray() + @IsEnum(WorkingDays, { each: true }) + @IsNotEmpty() + public days: WorkingDays[]; +} diff --git a/src/schedule/interfaces/get.schedule.interface.ts b/src/schedule/interfaces/get.schedule.interface.ts new file mode 100644 index 0000000..40c2011 --- /dev/null +++ b/src/schedule/interfaces/get.schedule.interface.ts @@ -0,0 +1,10 @@ +export interface getDeviceScheduleInterface { + success: boolean; + result: []; + msg: string; +} +export interface addScheduleDeviceInterface { + success: boolean; + result: boolean; + msg: string; +} diff --git a/src/schedule/schedule.module.ts b/src/schedule/schedule.module.ts new file mode 100644 index 0000000..f0f0a64 --- /dev/null +++ b/src/schedule/schedule.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { ScheduleService } from './services/schedule.service'; +import { ScheduleController } from './controllers/schedule.controller'; +import { ConfigModule } from '@nestjs/config'; +import { DeviceRepositoryModule } from '@app/common/modules/device'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +@Module({ + imports: [ConfigModule, DeviceRepositoryModule], + controllers: [ScheduleController], + providers: [ScheduleService, DeviceRepository], + exports: [ScheduleService], +}) +export class ScheduleModule {} diff --git a/src/schedule/services/index.ts b/src/schedule/services/index.ts new file mode 100644 index 0000000..ad5aed0 --- /dev/null +++ b/src/schedule/services/index.ts @@ -0,0 +1 @@ +export * from './schedule.service'; diff --git a/src/schedule/services/schedule.service.ts b/src/schedule/services/schedule.service.ts new file mode 100644 index 0000000..ee029cf --- /dev/null +++ b/src/schedule/services/schedule.service.ts @@ -0,0 +1,379 @@ +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { TuyaContext } from '@tuya/tuya-connector-nodejs'; +import { ConfigService } from '@nestjs/config'; +import { + AddScheduleDto, + EnableScheduleDto, + UpdateScheduleDto, +} from '../dtos/schedule.dto'; +import { + addScheduleDeviceInterface, + getDeviceScheduleInterface, +} from '../interfaces/get.schedule.interface'; + +import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { ProductType } from '@app/common/constants/product-type.enum'; +import { convertTimestampToDubaiTime } from '@app/common/helper/convertTimestampToDubaiTime'; +import { + getEnabledDays, + getScheduleStatus, +} from '@app/common/helper/getScheduleStatus'; + +@Injectable() +export class ScheduleService { + private tuya: TuyaContext; + constructor( + private readonly configService: ConfigService, + 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 enableDeviceSchedule( + deviceUuid: string, + enableScheduleDto: EnableScheduleDto, + ) { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + + if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } + + // Corrected condition for supported device types + if ( + deviceDetails.productDevice.prodType !== ProductType.THREE_G && + deviceDetails.productDevice.prodType !== ProductType.ONE_G && + deviceDetails.productDevice.prodType !== ProductType.TWO_G && + deviceDetails.productDevice.prodType !== ProductType.WH && + deviceDetails.productDevice.prodType !== ProductType.ONE_1TG && + deviceDetails.productDevice.prodType !== ProductType.TWO_2TG && + deviceDetails.productDevice.prodType !== ProductType.THREE_3TG && + deviceDetails.productDevice.prodType !== ProductType.GD + ) { + throw new HttpException( + 'This device is not supported for schedule', + HttpStatus.BAD_REQUEST, + ); + } + return await this.enableScheduleDeviceInTuya( + deviceDetails.deviceTuyaUuid, + enableScheduleDto, + ); + } catch (error) { + throw new HttpException( + error.message || 'Error While Updating Schedule', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async enableScheduleDeviceInTuya( + deviceId: string, + enableScheduleDto: EnableScheduleDto, + ): Promise { + try { + const path = `/v2.0/cloud/timer/device/${deviceId}/state`; + const response = await this.tuya.request({ + method: 'PUT', + path, + body: { + enable: enableScheduleDto.enable, + timer_id: enableScheduleDto.scheduleId, + }, + }); + + return response as addScheduleDeviceInterface; + } catch (error) { + throw new HttpException( + 'Error while updating schedule from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async deleteDeviceSchedule(deviceUuid: string, scheduleId: string) { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + + if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } + + // Corrected condition for supported device types + if ( + deviceDetails.productDevice.prodType !== ProductType.THREE_G && + deviceDetails.productDevice.prodType !== ProductType.ONE_G && + deviceDetails.productDevice.prodType !== ProductType.TWO_G && + deviceDetails.productDevice.prodType !== ProductType.WH && + deviceDetails.productDevice.prodType !== ProductType.ONE_1TG && + deviceDetails.productDevice.prodType !== ProductType.TWO_2TG && + deviceDetails.productDevice.prodType !== ProductType.THREE_3TG && + deviceDetails.productDevice.prodType !== ProductType.GD + ) { + throw new HttpException( + 'This device is not supported for schedule', + HttpStatus.BAD_REQUEST, + ); + } + return await this.deleteScheduleDeviceInTuya( + deviceDetails.deviceTuyaUuid, + scheduleId, + ); + } catch (error) { + throw new HttpException( + error.message || 'Error While Deleting Schedule', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async deleteScheduleDeviceInTuya( + deviceId: string, + scheduleId: string, + ): Promise { + try { + const path = `/v2.0/cloud/timer/device/${deviceId}/batch?timer_ids=${scheduleId}`; + const response = await this.tuya.request({ + method: 'DELETE', + path, + }); + + return response as addScheduleDeviceInterface; + } catch (error) { + throw new HttpException( + 'Error while deleting schedule from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async addDeviceSchedule(deviceUuid: string, addScheduleDto: AddScheduleDto) { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + + if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } + + // Corrected condition for supported device types + if ( + deviceDetails.productDevice.prodType !== ProductType.THREE_G && + deviceDetails.productDevice.prodType !== ProductType.ONE_G && + deviceDetails.productDevice.prodType !== ProductType.TWO_G && + deviceDetails.productDevice.prodType !== ProductType.WH && + deviceDetails.productDevice.prodType !== ProductType.ONE_1TG && + deviceDetails.productDevice.prodType !== ProductType.TWO_2TG && + deviceDetails.productDevice.prodType !== ProductType.THREE_3TG && + deviceDetails.productDevice.prodType !== ProductType.GD + ) { + throw new HttpException( + 'This device is not supported for schedule', + HttpStatus.BAD_REQUEST, + ); + } + await this.addScheduleDeviceInTuya( + deviceDetails.deviceTuyaUuid, + addScheduleDto, + ); + } catch (error) { + throw new HttpException( + error.message || 'Error While Adding Schedule', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async addScheduleDeviceInTuya( + deviceId: string, + addScheduleDto: AddScheduleDto, + ): Promise { + try { + const convertedTime = convertTimestampToDubaiTime(addScheduleDto.time); + const loops = getScheduleStatus(addScheduleDto.days); + + const path = `/v2.0/cloud/timer/device/${deviceId}`; + const response = await this.tuya.request({ + method: 'POST', + path, + body: { + time: convertedTime.time, + timezone_id: 'Asia/Dubai', + loops: `${loops}`, + functions: [ + { + code: addScheduleDto.function.code, + value: addScheduleDto.function.value, + }, + ], + category: `category_${addScheduleDto.category}`, + }, + }); + + return response as addScheduleDeviceInterface; + } catch (error) { + throw new HttpException( + 'Error adding schedule from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getDeviceScheduleByCategory(deviceUuid: string, category: string) { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + + if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } + // Corrected condition for supported device types + if ( + deviceDetails.productDevice.prodType !== ProductType.THREE_G && + deviceDetails.productDevice.prodType !== ProductType.ONE_G && + deviceDetails.productDevice.prodType !== ProductType.TWO_G && + deviceDetails.productDevice.prodType !== ProductType.WH && + deviceDetails.productDevice.prodType !== ProductType.ONE_1TG && + deviceDetails.productDevice.prodType !== ProductType.TWO_2TG && + deviceDetails.productDevice.prodType !== ProductType.THREE_3TG && + deviceDetails.productDevice.prodType !== ProductType.GD + ) { + throw new HttpException( + 'This device is not supported for schedule', + HttpStatus.BAD_REQUEST, + ); + } + const schedules = await this.getScheduleDeviceInTuya( + deviceDetails.deviceTuyaUuid, + category, + ); + const result = schedules.result.map((schedule: any) => { + return { + category: schedule.category.replace('category_', ''), + enable: schedule.enable, + function: { + code: schedule.functions[0].code, + value: schedule.functions[0].value, + }, + time: schedule.time, + schedule_id: schedule.timer_id, + timezone_id: schedule.timezone_id, + days: getEnabledDays(schedule.loops), + }; + }); + return convertKeysToCamelCase(result); + } catch (error) { + throw new HttpException( + error.message || 'Error While Adding Schedule', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getScheduleDeviceInTuya( + deviceId: string, + category: string, + ): Promise { + try { + const path = `/v2.0/cloud/timer/device/${deviceId}?category=category_${category}`; + const response = await this.tuya.request({ + method: 'GET', + path, + }); + + return response as getDeviceScheduleInterface; + } catch (error) { + console.error('Error fetching device schedule from Tuya:', error); + + throw new HttpException( + 'Error fetching device schedule from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getDeviceByDeviceUuid( + deviceUuid: string, + withProductDevice: boolean = true, + ) { + return await this.deviceRepository.findOne({ + where: { + uuid: deviceUuid, + isActive: true, + }, + ...(withProductDevice && { relations: ['productDevice'] }), + }); + } + async updateDeviceSchedule( + deviceUuid: string, + updateScheduleDto: UpdateScheduleDto, + ) { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + + if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } + + // Corrected condition for supported device types + if ( + deviceDetails.productDevice.prodType !== ProductType.THREE_G && + deviceDetails.productDevice.prodType !== ProductType.ONE_G && + deviceDetails.productDevice.prodType !== ProductType.TWO_G && + deviceDetails.productDevice.prodType !== ProductType.WH && + deviceDetails.productDevice.prodType !== ProductType.ONE_1TG && + deviceDetails.productDevice.prodType !== ProductType.TWO_2TG && + deviceDetails.productDevice.prodType !== ProductType.THREE_3TG && + deviceDetails.productDevice.prodType !== ProductType.GD + ) { + throw new HttpException( + 'This device is not supported for schedule', + HttpStatus.BAD_REQUEST, + ); + } + await this.updateScheduleDeviceInTuya( + deviceDetails.deviceTuyaUuid, + updateScheduleDto, + ); + } catch (error) { + throw new HttpException( + error.message || 'Error While Updating Schedule', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async updateScheduleDeviceInTuya( + deviceId: string, + updateScheduleDto: UpdateScheduleDto, + ): Promise { + try { + const convertedTime = convertTimestampToDubaiTime(updateScheduleDto.time); + const loops = getScheduleStatus(updateScheduleDto.days); + + const path = `/v2.0/cloud/timer/device/${deviceId}`; + const response = await this.tuya.request({ + method: 'PUT', + path, + body: { + timer_id: updateScheduleDto.scheduleId, + time: convertedTime.time, + timezone_id: 'Asia/Dubai', + loops: `${loops}`, + functions: [ + { + code: updateScheduleDto.function.code, + value: updateScheduleDto.function.value, + }, + ], + category: `category_${updateScheduleDto.category}`, + }, + }); + + return response as addScheduleDeviceInterface; + } catch (error) { + throw new HttpException( + 'Error updating schedule from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/timezone/controllers/timezone.controller.ts b/src/timezone/controllers/timezone.controller.ts index fc4dd8a..bc46381 100644 --- a/src/timezone/controllers/timezone.controller.ts +++ b/src/timezone/controllers/timezone.controller.ts @@ -1,17 +1,12 @@ -import { - Controller, - Get, - HttpException, - HttpStatus, - UseGuards, -} from '@nestjs/common'; +import { Controller, Get, UseGuards } from '@nestjs/common'; import { TimeZoneService } from '../services/timezone.service'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @ApiTags('TimeZone Module') @Controller({ - version: '1', + version: EnableDisableStatusEnum.ENABLED, path: 'timezone', }) export class TimeZoneController { @@ -21,13 +16,6 @@ export class TimeZoneController { @UseGuards(JwtAuthGuard) @Get() async getAllTimeZones() { - try { - return await this.timeZoneService.getAllTimeZones(); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.timeZoneService.getAllTimeZones(); } } diff --git a/src/unit/controllers/unit.controller.ts b/src/unit/controllers/unit.controller.ts index 1d5cbd3..8dbede7 100644 --- a/src/unit/controllers/unit.controller.ts +++ b/src/unit/controllers/unit.controller.ts @@ -3,7 +3,6 @@ import { Body, Controller, Get, - HttpException, HttpStatus, Param, Post, @@ -23,11 +22,13 @@ import { CheckFloorTypeGuard } from 'src/guards/floor.type.guard'; import { CheckUserUnitGuard } from 'src/guards/user.unit.guard'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { UnitPermissionGuard } from 'src/guards/unit.permission.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { SpaceType } from '@app/common/constants/space-type.enum'; @ApiTags('Unit Module') @Controller({ - version: '1', - path: 'unit', + version: EnableDisableStatusEnum.ENABLED, + path: SpaceType.UNIT, }) export class UnitController { constructor(private readonly unitService: UnitService) {} @@ -36,35 +37,21 @@ export class UnitController { @UseGuards(JwtAuthGuard, CheckFloorTypeGuard) @Post() async addUnit(@Body() addUnitDto: AddUnitDto) { - try { - const unit = await this.unitService.addUnit(addUnitDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'Unit added successfully', - data: unit, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const unit = await this.unitService.addUnit(addUnitDto); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'Unit added successfully', + data: unit, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard, UnitPermissionGuard) @Get(':unitUuid') async getUnitByUuid(@Param('unitUuid') unitUuid: string) { - try { - const unit = await this.unitService.getUnitByUuid(unitUuid); - return unit; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const unit = await this.unitService.getUnitByUuid(unitUuid); + return unit; } @ApiBearerAuth() @@ -74,95 +61,53 @@ export class UnitController { @Param('unitUuid') unitUuid: string, @Query() query: GetUnitChildDto, ) { - try { - const unit = await this.unitService.getUnitChildByUuid(unitUuid, query); - return unit; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const unit = await this.unitService.getUnitChildByUuid(unitUuid, query); + return unit; } @ApiBearerAuth() @UseGuards(JwtAuthGuard, UnitPermissionGuard) @Get('parent/:unitUuid') async getUnitParentByUuid(@Param('unitUuid') unitUuid: string) { - try { - const unit = await this.unitService.getUnitParentByUuid(unitUuid); - return unit; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const unit = await this.unitService.getUnitParentByUuid(unitUuid); + return unit; } @ApiBearerAuth() @UseGuards(JwtAuthGuard, CheckUserUnitGuard) @Post('user') async addUserUnit(@Body() addUserUnitDto: AddUserUnitDto) { - try { - await this.unitService.addUserUnit(addUserUnitDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'user unit added successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.unitService.addUserUnit(addUserUnitDto); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'user unit added successfully', + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('user/:userUuid') async getUnitsByUserId(@Param('userUuid') userUuid: string) { - try { - return await this.unitService.getUnitsByUserId(userUuid); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.unitService.getUnitsByUserId(userUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard, UnitPermissionGuard) - @Put('rename/:unitUuid') + @Put(':unitUuid') async renameUnitByUuid( @Param('unitUuid') unitUuid: string, @Body() updateUnitNameDto: UpdateUnitNameDto, ) { - try { - const unit = await this.unitService.renameUnitByUuid( - unitUuid, - updateUnitNameDto, - ); - return unit; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const unit = await this.unitService.renameUnitByUuid( + unitUuid, + updateUnitNameDto, + ); + return unit; } @ApiBearerAuth() @UseGuards(JwtAuthGuard, UnitPermissionGuard) @Get(':unitUuid/invitation-code') async getUnitInvitationCode(@Param('unitUuid') unitUuid: string) { - try { - const unit = await this.unitService.getUnitInvitationCode(unitUuid); - return unit; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const unit = await this.unitService.getUnitInvitationCode(unitUuid); + return unit; } @ApiBearerAuth() @@ -171,18 +116,11 @@ export class UnitController { async verifyCodeAndAddUserUnit( @Body() addUserUnitUsingCodeDto: AddUserUnitUsingCodeDto, ) { - try { - await this.unitService.verifyCodeAndAddUserUnit(addUserUnitUsingCodeDto); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'user unit added successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.unitService.verifyCodeAndAddUserUnit(addUserUnitUsingCodeDto); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'user unit added successfully', + }; } } diff --git a/src/unit/services/unit.service.ts b/src/unit/services/unit.service.ts index b5dfb8f..826c044 100644 --- a/src/unit/services/unit.service.ts +++ b/src/unit/services/unit.service.ts @@ -24,6 +24,8 @@ import { UserDevicePermissionService } from 'src/user-device-permission/services import { PermissionType } from '@app/common/constants/permission-type.enum'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { ConfigService } from '@nestjs/config'; +import { SpaceType } from '@app/common/constants/space-type.enum'; +import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; @Injectable() export class UnitService { @@ -50,7 +52,7 @@ export class UnitService { try { const spaceType = await this.spaceTypeRepository.findOne({ where: { - type: 'unit', + type: SpaceType.UNIT, }, }); const tuyaUnit = await this.addUnitTuya(addUnitDto.unitName); @@ -95,12 +97,12 @@ export class UnitService { where: { uuid: unitUuid, spaceType: { - type: 'unit', + type: SpaceType.UNIT, }, }, relations: ['spaceType'], }); - if (!unit || !unit.spaceType || unit.spaceType.type !== 'unit') { + if (!unit || !unit.spaceType || unit.spaceType.type !== SpaceType.UNIT) { throw new BadRequestException('Invalid unit UUID'); } return { @@ -131,7 +133,11 @@ export class UnitService { relations: ['children', 'spaceType'], }); - if (!space || !space.spaceType || space.spaceType.type !== 'unit') { + if ( + !space || + !space.spaceType || + space.spaceType.type !== SpaceType.UNIT + ) { throw new BadRequestException('Invalid unit UUID'); } @@ -174,10 +180,10 @@ export class UnitService { return children .filter( (child) => - child.spaceType.type !== 'unit' && - child.spaceType.type !== 'floor' && - child.spaceType.type !== 'community' && - child.spaceType.type !== 'unit', + child.spaceType.type !== SpaceType.UNIT && + child.spaceType.type !== SpaceType.FLOOR && + child.spaceType.type !== SpaceType.COMMUNITY && + child.spaceType.type !== SpaceType.UNIT, ) // Filter remaining unit and floor and community and unit types .map((child) => ({ uuid: child.uuid, @@ -190,10 +196,10 @@ export class UnitService { children .filter( (child) => - child.spaceType.type !== 'unit' && - child.spaceType.type !== 'floor' && - child.spaceType.type !== 'community' && - child.spaceType.type !== 'unit', + child.spaceType.type !== SpaceType.UNIT && + child.spaceType.type !== SpaceType.FLOOR && + child.spaceType.type !== SpaceType.COMMUNITY && + child.spaceType.type !== SpaceType.UNIT, ) // Filter remaining unit and floor and community and unit types .map(async (child) => ({ uuid: child.uuid, @@ -212,12 +218,12 @@ export class UnitService { where: { uuid: unitUuid, spaceType: { - type: 'unit', + type: SpaceType.UNIT, }, }, relations: ['spaceType', 'parent', 'parent.spaceType'], }); - if (!unit || !unit.spaceType || unit.spaceType.type !== 'unit') { + if (!unit || !unit.spaceType || unit.spaceType.type !== SpaceType.UNIT) { throw new BadRequestException('Invalid unit UUID'); } return { @@ -246,7 +252,7 @@ export class UnitService { relations: ['space', 'space.spaceType'], where: { user: { uuid: userUuid }, - space: { spaceType: { type: 'unit' } }, + space: { spaceType: { type: SpaceType.UNIT } }, }, }); @@ -276,7 +282,7 @@ export class UnitService { space: { uuid: addUserUnitDto.unitUuid }, }); } catch (err) { - if (err.code === '23505') { + if (err.code === CommonErrorCodes.DUPLICATE_ENTITY) { throw new HttpException( 'User already belongs to this unit', HttpStatus.BAD_REQUEST, @@ -298,7 +304,7 @@ export class UnitService { relations: ['spaceType'], }); - if (!unit || !unit.spaceType || unit.spaceType.type !== 'unit') { + if (!unit || !unit.spaceType || unit.spaceType.type !== SpaceType.UNIT) { throw new BadRequestException('Invalid unit UUID'); } @@ -383,7 +389,7 @@ export class UnitService { const unit = await this.spaceRepository.findOneOrFail({ where: { invitationCode: inviteCode, - spaceType: { type: 'unit' }, + spaceType: { type: SpaceType.UNIT }, }, relations: ['spaceType'], }); diff --git a/src/user-device-permission/controllers/user-device-permission.controller.ts b/src/user-device-permission/controllers/user-device-permission.controller.ts index 2f68708..5ff5284 100644 --- a/src/user-device-permission/controllers/user-device-permission.controller.ts +++ b/src/user-device-permission/controllers/user-device-permission.controller.ts @@ -3,7 +3,6 @@ import { Controller, Delete, Get, - HttpException, HttpStatus, Param, Post, @@ -15,10 +14,11 @@ import { UserDevicePermissionService } from '../services/user-device-permission. import { UserDevicePermissionAddDto } from '../dtos/user-device-permission.add.dto'; import { UserDevicePermissionEditDto } from '../dtos/user-device-permission.edit.dto'; import { AdminRoleGuard } from 'src/guards/admin.role.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @ApiTags('Device Permission Module') @Controller({ - version: '1', + version: EnableDisableStatusEnum.ENABLED, path: 'device-permission', }) export class UserDevicePermissionController { @@ -28,67 +28,48 @@ export class UserDevicePermissionController { @ApiBearerAuth() @UseGuards(AdminRoleGuard) - @Post('add') + @Post() async addDevicePermission( @Body() userDevicePermissionDto: UserDevicePermissionAddDto, ) { - try { - const addDetails = - await this.userDevicePermissionService.addUserPermission( - userDevicePermissionDto, - ); - return { - statusCode: HttpStatus.CREATED, - message: 'User Permission for Devices Added Successfully', - data: addDetails, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const addDetails = await this.userDevicePermissionService.addUserPermission( + userDevicePermissionDto, + ); + return { + statusCode: HttpStatus.CREATED, + message: 'User Permission for Devices Added Successfully', + data: addDetails, + }; } @ApiBearerAuth() @UseGuards(AdminRoleGuard) - @Put('edit/:devicePermissionUuid') + @Put(':devicePermissionUuid') async editDevicePermission( @Param('devicePermissionUuid') devicePermissionUuid: string, @Body() userDevicePermissionEditDto: UserDevicePermissionEditDto, ) { - try { - await this.userDevicePermissionService.editUserPermission( - devicePermissionUuid, - userDevicePermissionEditDto, - ); - return { - statusCode: HttpStatus.OK, - message: 'User Permission for Devices Updated Successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.userDevicePermissionService.editUserPermission( + devicePermissionUuid, + userDevicePermissionEditDto, + ); + return { + statusCode: HttpStatus.OK, + message: 'User Permission for Devices Updated Successfully', + }; } @ApiBearerAuth() @UseGuards(AdminRoleGuard) - @Get(':deviceUuid/list') + @Get(':deviceUuid') async fetchDevicePermission(@Param('deviceUuid') deviceUuid: string) { - try { - const deviceDetails = - await this.userDevicePermissionService.fetchUserPermission(deviceUuid); - return { - statusCode: HttpStatus.OK, - message: 'Device Details fetched Successfully', - data: deviceDetails, - }; - } catch (err) { - throw new Error(err); - } + const deviceDetails = + await this.userDevicePermissionService.fetchUserPermission(deviceUuid); + return { + statusCode: HttpStatus.OK, + message: 'Device Details fetched Successfully', + data: deviceDetails, + }; } @ApiBearerAuth() @UseGuards(AdminRoleGuard) @@ -96,19 +77,12 @@ export class UserDevicePermissionController { async deleteDevicePermission( @Param('devicePermissionUuid') devicePermissionUuid: string, ) { - try { - await this.userDevicePermissionService.deleteDevicePermission( - devicePermissionUuid, - ); - return { - statusCode: HttpStatus.OK, - message: 'User Permission for Devices Deleted Successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.userDevicePermissionService.deleteDevicePermission( + devicePermissionUuid, + ); + return { + statusCode: HttpStatus.OK, + message: 'User Permission for Devices Deleted Successfully', + }; } } diff --git a/src/user-device-permission/services/user-device-permission.service.ts b/src/user-device-permission/services/user-device-permission.service.ts index 9ec93e6..aa2df6d 100644 --- a/src/user-device-permission/services/user-device-permission.service.ts +++ b/src/user-device-permission/services/user-device-permission.service.ts @@ -3,6 +3,7 @@ import { UserDevicePermissionAddDto } from '../dtos/user-device-permission.add.d import { UserDevicePermissionEditDto } from '../dtos/user-device-permission.edit.dto'; import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; +import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; @Injectable() export class UserDevicePermissionService { @@ -24,7 +25,7 @@ export class UserDevicePermissionService { }, }); } catch (error) { - if (error.code === '23505') { + if (error.code === CommonErrorCodes.DUPLICATE_ENTITY) { throw new HttpException( 'This User already belongs to this device', HttpStatus.BAD_REQUEST, @@ -54,7 +55,7 @@ export class UserDevicePermissionService { }, ); } catch (error) { - if (error.code === '23505') { + if (error.code === CommonErrorCodes.DUPLICATE_ENTITY) { throw new HttpException( 'This User already belongs to this device', HttpStatus.BAD_REQUEST, diff --git a/src/user-notification/controllers/user-notification.controller.ts b/src/user-notification/controllers/user-notification.controller.ts index 83fd215..8fe8586 100644 --- a/src/user-notification/controllers/user-notification.controller.ts +++ b/src/user-notification/controllers/user-notification.controller.ts @@ -2,7 +2,6 @@ import { Body, Controller, Get, - HttpException, HttpStatus, Param, Post, @@ -17,10 +16,11 @@ import { } from '../dtos/user-notification.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @ApiTags('User Notification Module') @Controller({ - version: '1', + version: EnableDisableStatusEnum.ENABLED, path: 'user-notification/subscription', }) export class UserNotificationController { @@ -34,41 +34,27 @@ export class UserNotificationController { async addUserSubscription( @Body() userNotificationAddDto: UserNotificationAddDto, ) { - try { - const addDetails = await this.userNotificationService.addUserSubscription( - userNotificationAddDto, - ); - return { - statusCode: HttpStatus.CREATED, - message: 'User Notification Added Successfully', - data: addDetails, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const addDetails = await this.userNotificationService.addUserSubscription( + userNotificationAddDto, + ); + return { + statusCode: HttpStatus.CREATED, + message: 'User Notification Added Successfully', + data: addDetails, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get(':userUuid') async fetchUserSubscriptions(@Param('userUuid') userUuid: string) { - try { - const userDetails = - await this.userNotificationService.fetchUserSubscriptions(userUuid); - return { - statusCode: HttpStatus.OK, - message: 'User Notification fetched Successfully', - data: { ...userDetails }, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const userDetails = + await this.userNotificationService.fetchUserSubscriptions(userUuid); + return { + statusCode: HttpStatus.OK, + message: 'User Notification fetched Successfully', + data: { ...userDetails }, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -76,19 +62,12 @@ export class UserNotificationController { async updateUserSubscription( @Body() userNotificationUpdateDto: UserNotificationUpdateDto, ) { - try { - await this.userNotificationService.updateUserSubscription( - userNotificationUpdateDto, - ); - return { - statusCode: HttpStatus.OK, - message: 'User subscription updated Successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + await this.userNotificationService.updateUserSubscription( + userNotificationUpdateDto, + ); + return { + statusCode: HttpStatus.OK, + message: 'User subscription updated Successfully', + }; } } diff --git a/src/user-notification/services/user-notification.service.ts b/src/user-notification/services/user-notification.service.ts index 8c9616b..b2208bb 100644 --- a/src/user-notification/services/user-notification.service.ts +++ b/src/user-notification/services/user-notification.service.ts @@ -4,6 +4,7 @@ import { UserNotificationUpdateDto, } from '../dtos/user-notification.dto'; import { UserNotificationRepository } from '@app/common/modules/user/repositories'; +import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; @Injectable() export class UserNotificationService { @@ -20,7 +21,7 @@ export class UserNotificationService { subscriptionUuid: userNotificationAddDto.subscriptionUuid, }); } catch (error) { - if (error.code === '23505') { + if (error.code === CommonErrorCodes.DUPLICATE_ENTITY) { throw new HttpException( 'This User already has this subscription uuid', HttpStatus.BAD_REQUEST, diff --git a/src/users/controllers/user.controller.ts b/src/users/controllers/user.controller.ts index b088b93..503f47c 100644 --- a/src/users/controllers/user.controller.ts +++ b/src/users/controllers/user.controller.ts @@ -1,8 +1,8 @@ import { Body, Controller, + Delete, Get, - HttpException, HttpStatus, Param, Put, @@ -18,10 +18,12 @@ import { UpdateTimezoneDataDto, } from '../dtos'; import { CheckProfilePictureGuard } from 'src/guards/profile.picture.guard'; +import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @ApiTags('User Module') @Controller({ - version: '1', + version: EnableDisableStatusEnum.ENABLED, path: 'user', }) export class UserController { @@ -31,14 +33,7 @@ export class UserController { @UseGuards(JwtAuthGuard) @Get(':userUuid') async getUserDetailsByUserUuid(@Param('userUuid') userUuid: string) { - try { - return await this.userService.getUserDetailsByUserUuid(userUuid); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.userService.getUserDetailsByUserUuid(userUuid); } @ApiBearerAuth() @UseGuards(JwtAuthGuard, CheckProfilePictureGuard) @@ -47,23 +42,16 @@ export class UserController { @Param('userUuid') userUuid: string, @Body() updateProfilePictureDataDto: UpdateProfilePictureDataDto, ) { - try { - const userData = await this.userService.updateProfilePictureByUserUuid( - userUuid, - updateProfilePictureDataDto, - ); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'profile picture updated successfully', - data: userData, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const userData = await this.userService.updateProfilePictureByUserUuid( + userUuid, + updateProfilePictureDataDto, + ); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'profile picture updated successfully', + data: userData, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -72,23 +60,16 @@ export class UserController { @Param('userUuid') userUuid: string, @Body() updateRegionDataDto: UpdateRegionDataDto, ) { - try { - const userData = await this.userService.updateRegionByUserUuid( - userUuid, - updateRegionDataDto, - ); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'region updated successfully', - data: userData, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const userData = await this.userService.updateRegionByUserUuid( + userUuid, + updateRegionDataDto, + ); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'region updated successfully', + data: userData, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -97,23 +78,16 @@ export class UserController { @Param('userUuid') userUuid: string, @Body() updateTimezoneDataDto: UpdateTimezoneDataDto, ) { - try { - const userData = await this.userService.updateTimezoneByUserUuid( - userUuid, - updateTimezoneDataDto, - ); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'timezone updated successfully', - data: userData, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + const userData = await this.userService.updateTimezoneByUserUuid( + userUuid, + updateTimezoneDataDto, + ); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'timezone updated successfully', + data: userData, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -122,22 +96,28 @@ export class UserController { @Param('userUuid') userUuid: string, @Body() updateNameDto: UpdateNameDto, ) { - try { - const userData = await this.userService.updateNameByUserUuid( + const userData = await this.userService.updateNameByUserUuid( + userUuid, + updateNameDto, + ); + return { + statusCode: HttpStatus.CREATED, + success: true, + message: 'name updated successfully', + data: userData, + }; + } + @ApiBearerAuth() + @UseGuards(SuperAdminRoleGuard) + @Delete('/:userUuid') + async userDelete(@Param('userUuid') userUuid: string) { + await this.userService.deleteUser(userUuid); + return { + statusCode: HttpStatus.OK, + data: { userUuid, - updateNameDto, - ); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'name updated successfully', - data: userData, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + }, + message: 'User Deleted Successfully', + }; } } diff --git a/src/users/services/user.service.ts b/src/users/services/user.service.ts index b035c05..80dad24 100644 --- a/src/users/services/user.service.ts +++ b/src/users/services/user.service.ts @@ -14,6 +14,7 @@ import { UserRepository } from '@app/common/modules/user/repositories'; import { RegionRepository } from '@app/common/modules/region/repositories'; import { TimeZoneRepository } from '@app/common/modules/timezone/repositories'; import { removeBase64Prefix } from '@app/common/helper/removeBase64Prefix'; +import { UserEntity } from '@app/common/modules/user/entities'; @Injectable() export class UserService { @@ -237,4 +238,14 @@ export class UserService { ); } } + async findOneById(id: string): Promise { + return await this.userRepository.findOne({ where: { uuid: id } }); + } + async deleteUser(uuid: string) { + const user = await this.findOneById(uuid); + if (!user) { + throw new BadRequestException('User not found'); + } + return await this.userRepository.update({ uuid }, { isActive: false }); + } } diff --git a/src/vistor-password/controllers/visitor-password.controller.ts b/src/vistor-password/controllers/visitor-password.controller.ts index bdd4324..894b759 100644 --- a/src/vistor-password/controllers/visitor-password.controller.ts +++ b/src/vistor-password/controllers/visitor-password.controller.ts @@ -3,7 +3,6 @@ import { Body, Controller, Post, - HttpException, HttpStatus, UseGuards, Get, @@ -17,10 +16,11 @@ import { AddDoorLockOnlineOneTimeDto, } from '../dtos/temp-pass.dto'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; @ApiTags('Visitor Password Module') @Controller({ - version: '1', + version: EnableDisableStatusEnum.ENABLED, path: 'visitor-password', }) export class VisitorPasswordController { @@ -34,24 +34,17 @@ export class VisitorPasswordController { @Body() addDoorLockOnlineMultipleDto: AddDoorLockOnlineMultipleDto, @Req() req: any, ) { - try { - const userUuid = req.user.uuid; - const temporaryPasswords = - await this.visitorPasswordService.addOnlineTemporaryPasswordMultipleTime( - addDoorLockOnlineMultipleDto, - userUuid, - ); - - return { - statusCode: HttpStatus.CREATED, - data: temporaryPasswords, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, + const userUuid = req.user.uuid; + const temporaryPasswords = + await this.visitorPasswordService.addOnlineTemporaryPasswordMultipleTime( + addDoorLockOnlineMultipleDto, + userUuid, ); - } + + return { + statusCode: HttpStatus.CREATED, + data: temporaryPasswords, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -60,24 +53,17 @@ export class VisitorPasswordController { @Body() addDoorLockOnlineOneTimeDto: AddDoorLockOnlineOneTimeDto, @Req() req: any, ) { - try { - const userUuid = req.user.uuid; - const temporaryPasswords = - await this.visitorPasswordService.addOnlineTemporaryPasswordOneTime( - addDoorLockOnlineOneTimeDto, - userUuid, - ); - - return { - statusCode: HttpStatus.CREATED, - data: temporaryPasswords, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, + const userUuid = req.user.uuid; + const temporaryPasswords = + await this.visitorPasswordService.addOnlineTemporaryPasswordOneTime( + addDoorLockOnlineOneTimeDto, + userUuid, ); - } + + return { + statusCode: HttpStatus.CREATED, + data: temporaryPasswords, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -86,24 +72,17 @@ export class VisitorPasswordController { @Body() addDoorLockOfflineOneTimeDto: AddDoorLockOfflineOneTimeDto, @Req() req: any, ) { - try { - const userUuid = req.user.uuid; - const temporaryPassword = - await this.visitorPasswordService.addOfflineOneTimeTemporaryPassword( - addDoorLockOfflineOneTimeDto, - userUuid, - ); - - return { - statusCode: HttpStatus.CREATED, - data: temporaryPassword, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, + const userUuid = req.user.uuid; + const temporaryPassword = + await this.visitorPasswordService.addOfflineOneTimeTemporaryPassword( + addDoorLockOfflineOneTimeDto, + userUuid, ); - } + + return { + statusCode: HttpStatus.CREATED, + data: temporaryPassword, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @@ -113,49 +92,28 @@ export class VisitorPasswordController { addDoorLockOfflineMultipleDto: AddDoorLockOfflineMultipleDto, @Req() req: any, ) { - try { - const userUuid = req.user.uuid; - const temporaryPassword = - await this.visitorPasswordService.addOfflineMultipleTimeTemporaryPassword( - addDoorLockOfflineMultipleDto, - userUuid, - ); - - return { - statusCode: HttpStatus.CREATED, - data: temporaryPassword, - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, + const userUuid = req.user.uuid; + const temporaryPassword = + await this.visitorPasswordService.addOfflineMultipleTimeTemporaryPassword( + addDoorLockOfflineMultipleDto, + userUuid, ); - } + + return { + statusCode: HttpStatus.CREATED, + data: temporaryPassword, + }; } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get() async GetVisitorPassword() { - try { - return await this.visitorPasswordService.getPasswords(); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.visitorPasswordService.getPasswords(); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Get('/devices') async GetVisitorDevices() { - try { - return await this.visitorPasswordService.getAllPassDevices(); - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return await this.visitorPasswordService.getAllPassDevices(); } } diff --git a/src/vistor-password/services/visitor-password.service.ts b/src/vistor-password/services/visitor-password.service.ts index f15b8e4..81f2a62 100644 --- a/src/vistor-password/services/visitor-password.service.ts +++ b/src/vistor-password/services/visitor-password.service.ts @@ -20,6 +20,16 @@ import { PasswordEncryptionService } from 'src/door-lock/services/encryption.ser import { DoorLockService } from 'src/door-lock/services'; import { GetDeviceDetailsInterface } from 'src/device/interfaces/get.device.interface'; import { DeviceService } from 'src/device/services'; +import { DeviceStatuses } from '@app/common/constants/device-status.enum'; +import { + DaysEnum, + EnableDisableStatusEnum, +} from '@app/common/constants/days.enum'; +import { PasswordType } from '@app/common/constants/password-type.enum'; +import { + CommonHourMinutes, + CommonHours, +} from '@app/common/constants/hours-minutes.enum'; @Injectable() export class VisitorPasswordService { @@ -67,7 +77,7 @@ export class VisitorPasswordService { const createMultipleOfflinePass = await this.addOfflineTemporaryPasswordTuya( deviceDetails.deviceTuyaUuid, - 'multiple', + PasswordType.MULTIPLE, addDoorLockOfflineMultipleDto, addDoorLockOfflineMultipleDto.passwordName, ); @@ -117,7 +127,7 @@ export class VisitorPasswordService { const successfulResults = deviceResults .filter( (result) => - result.status === 'fulfilled' && + result.status === DeviceStatuses.FULLFILLED && (result as PromiseFulfilledResult).value.success, ) .map((result) => (result as PromiseFulfilledResult).value); @@ -125,11 +135,11 @@ export class VisitorPasswordService { const failedResults = deviceResults .filter( (result) => - result.status === 'rejected' || + result.status === DeviceStatuses.REJECTED || !(result as PromiseFulfilledResult).value.success, ) .map((result) => - result.status === 'rejected' + result.status === DeviceStatuses.REJECTED ? { success: false, error: @@ -238,7 +248,7 @@ export class VisitorPasswordService { const successfulResults = deviceResults .filter( (result) => - result.status === 'fulfilled' && + result.status === DeviceStatuses.FULLFILLED && (result as PromiseFulfilledResult).value.success, ) .map((result) => (result as PromiseFulfilledResult).value); @@ -246,11 +256,11 @@ export class VisitorPasswordService { const failedResults = deviceResults .filter( (result) => - result.status === 'rejected' || + result.status === DeviceStatuses.REJECTED || !(result as PromiseFulfilledResult).value.success, ) .map((result) => - result.status === 'rejected' + result.status === DeviceStatuses.REJECTED ? { success: false, error: @@ -298,7 +308,7 @@ export class VisitorPasswordService { method: 'POST', path, body: { - ...(type === 'multiple' && { + ...(type === PasswordType.MULTIPLE && { effective_time: addDoorLockOfflineMultipleDto.effectiveTime, invalid_time: addDoorLockOfflineMultipleDto.invalidTime, }), @@ -389,7 +399,7 @@ export class VisitorPasswordService { const successfulResults = deviceResults .filter( (result) => - result.status === 'fulfilled' && + result.status === DeviceStatuses.FULLFILLED && (result as PromiseFulfilledResult).value.success, ) .map((result) => (result as PromiseFulfilledResult).value); @@ -397,11 +407,11 @@ export class VisitorPasswordService { const failedResults = deviceResults .filter( (result) => - result.status === 'rejected' || + result.status === DeviceStatuses.REJECTED || !(result as PromiseFulfilledResult).value.success, ) .map((result) => - result.status === 'rejected' + result.status === DeviceStatuses.REJECTED ? { success: false, error: @@ -440,6 +450,7 @@ export class VisitorPasswordService { productDevice: { prodType: ProductType.DL, }, + isActive: true, }, }); const data = []; @@ -482,6 +493,7 @@ export class VisitorPasswordService { productDevice: { prodType: ProductType.DL, }, + isActive: true, }, relations: ['productDevice'], }); @@ -573,7 +585,7 @@ export class VisitorPasswordService { const successfulResults = deviceResults .filter( (result) => - result.status === 'fulfilled' && + result.status === DeviceStatuses.FULLFILLED && (result as PromiseFulfilledResult).value.success, ) .map((result) => (result as PromiseFulfilledResult).value); @@ -581,11 +593,11 @@ export class VisitorPasswordService { const failedResults = deviceResults .filter( (result) => - result.status === 'rejected' || + result.status === DeviceStatuses.REJECTED || !(result as PromiseFulfilledResult).value.success, ) .map((result) => - result.status === 'rejected' + result.status === DeviceStatuses.REJECTED ? { success: false, error: @@ -707,7 +719,7 @@ export class VisitorPasswordService { schedule_list: scheduleList, }), - type: '0', + type: EnableDisableStatusEnum.DISABLED, }, }); @@ -722,7 +734,15 @@ export class VisitorPasswordService { getWorkingDayValue(days) { // Array representing the days of the week - const weekDays = ['Sat', 'Fri', 'Thu', 'Wed', 'Tue', 'Mon', 'Sun']; + const weekDays = [ + DaysEnum.SAT, + DaysEnum.FRI, + DaysEnum.THU, + DaysEnum.WED, + DaysEnum.TUE, + DaysEnum.MON, + DaysEnum.SUN, + ]; // Initialize a binary string with 7 bits let binaryString = '0000000'; @@ -731,10 +751,10 @@ export class VisitorPasswordService { days.forEach((day) => { const index = weekDays.indexOf(day); if (index !== -1) { - // Set the corresponding bit to '1' + // Set the corresponding bit to EnableDisableStatusEnum.ENABLED binaryString = binaryString.substring(0, index) + - '1' + + EnableDisableStatusEnum.ENABLED + binaryString.substring(index + 1); } }); @@ -746,17 +766,27 @@ export class VisitorPasswordService { } getDaysFromWorkingDayValue(workingDayValue) { // Array representing the days of the week - const weekDays = ['Sat', 'Fri', 'Thu', 'Wed', 'Tue', 'Mon', 'Sun']; + const weekDays = [ + DaysEnum.SAT, + DaysEnum.FRI, + DaysEnum.THU, + DaysEnum.WED, + DaysEnum.TUE, + DaysEnum.MON, + DaysEnum.SUN, + ]; // Convert the integer to a binary string and pad with leading zeros to ensure 7 bits - const binaryString = workingDayValue.toString(2).padStart(7, '0'); + const binaryString = workingDayValue + .toString(2) + .padStart(7, EnableDisableStatusEnum.DISABLED); // Initialize an array to hold the days of the week const days = []; // Iterate through the binary string and weekDays array for (let i = 0; i < binaryString.length; i++) { - if (binaryString[i] === '1') { + if (binaryString[i] === EnableDisableStatusEnum.ENABLED) { days.push(weekDays[i]); } } @@ -766,8 +796,8 @@ export class VisitorPasswordService { timeToMinutes(timeStr) { try { // Special case for "24:00" - if (timeStr === '24:00') { - return 1440; + if (timeStr === CommonHours.TWENTY_FOUR) { + return CommonHourMinutes.TWENTY_FOUR; } // Regular expression to validate the 24-hour time format (HH:MM) @@ -795,20 +825,26 @@ export class VisitorPasswordService { if ( typeof totalMinutes !== 'number' || totalMinutes < 0 || - totalMinutes > 1440 + totalMinutes > CommonHourMinutes.TWENTY_FOUR ) { throw new Error('Invalid minutes value'); } - if (totalMinutes === 1440) { - return '24:00'; + if (totalMinutes === CommonHourMinutes.TWENTY_FOUR) { + return CommonHours.TWENTY_FOUR; } const hours = Math.floor(totalMinutes / 60); const minutes = totalMinutes % 60; - const formattedHours = String(hours).padStart(2, '0'); - const formattedMinutes = String(minutes).padStart(2, '0'); + const formattedHours = String(hours).padStart( + 2, + EnableDisableStatusEnum.DISABLED, + ); + const formattedMinutes = String(minutes).padStart( + 2, + EnableDisableStatusEnum.DISABLED, + ); return `${formattedHours}:${formattedMinutes}`; } catch (error) { @@ -823,6 +859,7 @@ export class VisitorPasswordService { return await this.deviceRepository.findOne({ where: { uuid: deviceUuid, + isActive: true, }, ...(withProductDevice && { relations: ['productDevice'] }), }); @@ -846,7 +883,7 @@ export class VisitorPasswordService { invalid_time: addDeviceObj.invalidTime, password_type: 'ticket', ticket_id: addDeviceObj.ticketId, - type: '1', + type: EnableDisableStatusEnum.ENABLED, }, });