Merge branch 'dev'

This commit is contained in:
faris Aljohari
2024-10-08 23:40:30 -05:00
107 changed files with 2554 additions and 1825 deletions

View File

@ -90,3 +90,6 @@ FIREBASE_DATABASE_URL=
OTP_LIMITER=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

View File

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

View File

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

View File

@ -1,11 +1,12 @@
import { registerAs } from '@nestjs/config';
import { BooleanValues } from '../constants/boolean-values.enum';
export default registerAs(
'email-config',
(): Record<string, any> => ({
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,

View File

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

View File

@ -0,0 +1,9 @@
// automation.enum.ts
export enum ActionExecutorEnum {
DEVICE_ISSUE = 'device_issue',
DELAY = 'delay',
}
export enum EntityTypeEnum {
DEVICE_REPORT = 'device_report',
}

View File

@ -0,0 +1,4 @@
export enum BatteryStatus {
RESIDUAL_ELECTRICITY = 'residual_electricity',
BATTERY_PERCENTAGE = 'battery_percentage',
}

View File

@ -0,0 +1,4 @@
export enum BooleanValues {
TRUE = 'true',
FALSE = 'false',
}

View File

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

View File

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

View File

@ -0,0 +1,4 @@
export enum DeviceStatuses {
REJECTED = 'rejected',
FULLFILLED = 'fulfilled',
}

View File

@ -0,0 +1,3 @@
export enum CommonErrorCodes {
DUPLICATE_ENTITY = '23505',
}

View File

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

View File

@ -0,0 +1,4 @@
export enum PasswordType {
SINGLE = 'single',
MULTIPLE = 'multiple',
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,8 +18,9 @@ export class DeviceEntity extends AbstractEntity<DeviceDto> {
@Column({
nullable: true,
default: true,
type: 'boolean',
})
isActive: true;
isActive: boolean;
@ManyToOne(() => UserEntity, (user) => user.userSpaces, { nullable: false })
user: UserEntity;

346
package-lock.json generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,2 +1 @@
export * from './authentication.controller';
export * from './user-auth.controller';

View File

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

View File

@ -41,5 +41,5 @@ export class UserSignUpDto {
@IsString()
@IsOptional()
public regionUuid: string;
public regionUuid?: string;
}

View File

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

View File

@ -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<string>('auth-config.DEVICE_ID')),
(this.accessKey = this.configService.get<string>(
'auth-config.ACCESS_KEY',
)),
(this.secretKey = this.configService.get<string>(
'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<string> {
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,
};
}
}

View File

@ -1,2 +1 @@
export * from './authentication.service';
export * from './user-auth.service';

View File

@ -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<UserEntity> {
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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
import { HttpExceptionFilter } from './http-exception.filter';
describe('HttpExceptionFilter', () => {
it('should be defined', () => {
expect(new HttpExceptionFilter()).toBeDefined();
});
});

View File

@ -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<Response>();
const request = ctx.getRequest<Request>();
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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -66,7 +66,7 @@ export interface updateDeviceFirmwareInterface {
}
export interface getDeviceLogsInterface {
data: [];
startTime: number;
endTime: number;
startTime: string;
endTime: string;
deviceUuid?: string;
}

View File

@ -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<controlDeviceInterface> {
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<GetDeviceDetailsInterface[]> {
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<GetDeviceDetailsInterface>).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<getDeviceLogsInterface> {
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) {

View File

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

View File

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

View File

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

View File

@ -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>(ErrorMessageService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -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, string | number>,
): 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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -58,7 +58,11 @@ export class CheckUserHaveControllablePermission implements CanActivate {
deviceUuid: string,
): Promise<string> {
const device = await this.deviceRepository.findOne({
where: { uuid: deviceUuid, permission: { userUuid: userUuid } },
where: {
uuid: deviceUuid,
isActive: true,
permission: { userUuid: userUuid },
},
relations: ['permission', 'permission.permissionType'],
});

View File

@ -59,7 +59,11 @@ export class CheckUserHavePermission implements CanActivate {
deviceUuid: string,
): Promise<string> {
const device = await this.deviceRepository.findOne({
where: { uuid: deviceUuid, permission: { userUuid: userUuid } },
where: {
uuid: deviceUuid,
permission: { userUuid: userUuid },
isActive: true,
},
relations: ['permission', 'permission.permissionType'],
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
export * from './schedule.controller';

View File

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

View File

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

View File

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

View File

@ -0,0 +1,10 @@
export interface getDeviceScheduleInterface {
success: boolean;
result: [];
msg: string;
}
export interface addScheduleDeviceInterface {
success: boolean;
result: boolean;
msg: string;
}

View File

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

View File

@ -0,0 +1 @@
export * from './schedule.service';

View File

@ -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<string>('auth-config.ACCESS_KEY');
const secretKey = this.configService.get<string>('auth-config.SECRET_KEY');
const tuyaEuUrl = this.configService.get<string>('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<addScheduleDeviceInterface> {
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<addScheduleDeviceInterface> {
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<addScheduleDeviceInterface> {
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<getDeviceScheduleInterface> {
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<addScheduleDeviceInterface> {
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,
);
}
}
}

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More