diff --git a/.env.example b/.env.example index 54819e3..54773c4 100644 --- a/.env.example +++ b/.env.example @@ -1,75 +1,92 @@ ACCESS_KEY= - + AZURE_POSTGRESQL_DATABASE= - + AZURE_POSTGRESQL_HOST= - + AZURE_POSTGRESQL_PASSWORD= - + AZURE_POSTGRESQL_PORT= - + AZURE_POSTGRESQL_SSL= - + AZURE_POSTGRESQL_SYNC= - + AZURE_POSTGRESQL_USER= - + AZURE_REDIS_CONNECTIONSTRING= - + BASE_URL= - + DB_SYNC= - + DOCKER_REGISTRY_SERVER_PASSWORD= - + DOCKER_REGISTRY_SERVER_URL= - + DOCKER_REGISTRY_SERVER_USERNAME= - + DOPPLER_CONFIG= - + DOPPLER_ENVIRONMENT= - + DOPPLER_PROJECT= - + JWT_EXPIRE_TIME= - + JWT_EXPIRE_TIME_REFRESH= - + JWT_SECRET= - + JWT_SECRET_REFRESH= - + SECRET_KEY= - + SMTP_HOST= - + SMTP_PASSWORD= - + SMTP_PORT= - + SMTP_SECURE= - + SMTP_USER= - + WEBSITES_ENABLE_APP_SERVICE_STORAGE= - + PORT= - + SUPER_ADMIN_EMAIL= - + SUPER_ADMIN_PASSWORD= - + TUYA_ACCESS_ID= - + TUYA_ACCESS_KEY= - + ONESIGNAL_APP_ID= - + ONESIGNAL_API_KEY= - + TRUN_ON_TUYA_SOCKET= - + TUYA_EU_URL= - -MONGODB_URI= \ No newline at end of file + +FIREBASE_API_KEY= + +FIREBASE_AUTH_DOMAIN= + +FIREBASE_PROJECT_ID= + +FIREBASE_STORAGE_BUCKET= + +FIREBASE_MESSAGING_SENDER_ID= + +FIREBASE_APP_ID=d + +FIREBASE_MEASUREMENT_ID= + +FIREBASE_DATABASE_URL= + +OTP_LIMITER= + diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index f1c1c0d..af9d047 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -17,10 +17,19 @@ export class AuthService { private readonly configService: ConfigService, ) {} - async validateUser(email: string, pass: string): Promise { + async validateUser( + email: string, + pass: string, + regionUuid?: string, + ): Promise { const user = await this.userRepository.findOne({ where: { email, + region: regionUuid + ? { + uuid: regionUuid, + } + : undefined, }, relations: ['roles.roleType'], }); diff --git a/libs/common/src/constants/default.door-lock-pass.ts b/libs/common/src/constants/default.door-lock-pass.ts new file mode 100644 index 0000000..3267ab8 --- /dev/null +++ b/libs/common/src/constants/default.door-lock-pass.ts @@ -0,0 +1 @@ +export const defaultDoorLockPass = '1233214'; diff --git a/libs/common/src/constants/regions.ts b/libs/common/src/constants/regions.ts index 3d2eb1a..4ab2375 100644 --- a/libs/common/src/constants/regions.ts +++ b/libs/common/src/constants/regions.ts @@ -129,7 +129,7 @@ export const allCountries = [ 'Oman', 'Pakistan', 'Palau', - 'Palestine State', + 'Palestine', 'Panama', 'Papua New Guinea', 'Paraguay', diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index e6ddb6c..ae01e55 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -4,20 +4,21 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { SnakeNamingStrategy } from './strategies'; import { UserEntity } from '../modules/user/entities/user.entity'; import { UserSessionEntity } from '../modules/session/entities/session.entity'; -import { UserOtpEntity } from '../modules/user-otp/entities'; +import { UserOtpEntity } from '../modules/user/entities'; import { ProductEntity } from '../modules/product/entities'; import { DeviceEntity } from '../modules/device/entities'; import { PermissionTypeEntity } from '../modules/permission/entities'; import { SpaceEntity } from '../modules/space/entities'; -import { SpaceTypeEntity } from '../modules/space-type/entities'; -import { UserSpaceEntity } from '../modules/user-space/entities'; -import { DeviceUserPermissionEntity } from '../modules/device-user-permission/entities'; -import { UserRoleEntity } from '../modules/user-role/entities'; +import { SpaceTypeEntity } from '../modules/space/entities'; +import { UserSpaceEntity } from '../modules/user/entities'; +import { DeviceUserPermissionEntity } from '../modules/device/entities'; +import { UserRoleEntity } from '../modules/user/entities'; import { RoleTypeEntity } from '../modules/role-type/entities'; -import { UserNotificationEntity } from '../modules/user-notification/entities'; -import { DeviceNotificationEntity } from '../modules/device-notification/entities'; +import { UserNotificationEntity } from '../modules/user/entities'; +import { DeviceNotificationEntity } from '../modules/device/entities'; import { RegionEntity } from '../modules/region/entities'; import { TimeZoneEntity } from '../modules/timezone/entities'; +import { VisitorPasswordEntity } from '../modules/visitor-password/entities'; @Module({ imports: [ @@ -50,6 +51,7 @@ import { TimeZoneEntity } from '../modules/timezone/entities'; DeviceNotificationEntity, RegionEntity, TimeZoneEntity, + VisitorPasswordEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), diff --git a/libs/common/src/firebase/devices-status/controllers/devices-status.controller.ts b/libs/common/src/firebase/devices-status/controllers/devices-status.controller.ts new file mode 100644 index 0000000..70506c2 --- /dev/null +++ b/libs/common/src/firebase/devices-status/controllers/devices-status.controller.ts @@ -0,0 +1,25 @@ +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'; + +@ApiTags('Device Status Firebase Module') +@Controller({ + version: '1', + path: 'device-status-firebase', +}) +export class DeviceStatusFirebaseController { + constructor( + private readonly deviceStatusFirebaseService: DeviceStatusFirebaseService, + ) {} + + @ApiBearerAuth() + @Post(':deviceTuyaUuid') + async addDeviceStatus( + @Param('deviceTuyaUuid') deviceTuyaUuid: string, + ): Promise { + return this.deviceStatusFirebaseService.addDeviceStatusByDeviceUuid( + deviceTuyaUuid, + ); + } +} diff --git a/libs/common/src/firebase/devices-status/devices-status.module.ts b/libs/common/src/firebase/devices-status/devices-status.module.ts new file mode 100644 index 0000000..ad13b29 --- /dev/null +++ b/libs/common/src/firebase/devices-status/devices-status.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { DeviceStatusFirebaseController } from './controllers/devices-status.controller'; +import { DeviceStatusFirebaseService } from './services/devices-status.service'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; + +@Module({ + providers: [DeviceStatusFirebaseService, DeviceRepository], + controllers: [DeviceStatusFirebaseController], + exports: [DeviceStatusFirebaseService], +}) +export class DeviceStatusFirebaseModule {} diff --git a/libs/common/src/firebase/devices-status/dtos/add.devices-status.dto.ts b/libs/common/src/firebase/devices-status/dtos/add.devices-status.dto.ts new file mode 100644 index 0000000..9ad7a11 --- /dev/null +++ b/libs/common/src/firebase/devices-status/dtos/add.devices-status.dto.ts @@ -0,0 +1,38 @@ +import { + IsString, + IsArray, + ValidateNested, + IsNotEmpty, + IsOptional, +} from 'class-validator'; +import { Type } from 'class-transformer'; + +class StatusDto { + @IsString() + code: string; + + @IsNotEmpty() + value: any; +} + +export class AddDeviceStatusDto { + @IsString() + @IsOptional() + deviceUuid?: string; + + @IsString() + deviceTuyaUuid: string; + + @IsString() + @IsOptional() + productUuid?: string; + + @IsString() + @IsOptional() + productType?: string; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => StatusDto) + status: StatusDto[]; +} diff --git a/libs/common/src/firebase/devices-status/services/devices-status.service.ts b/libs/common/src/firebase/devices-status/services/devices-status.service.ts new file mode 100644 index 0000000..267d6b6 --- /dev/null +++ b/libs/common/src/firebase/devices-status/services/devices-status.service.ts @@ -0,0 +1,192 @@ +import { + HttpException, + HttpStatus, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { AddDeviceStatusDto } from '../dtos/add.devices-status.dto'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { GetDeviceDetailsFunctionsStatusInterface } from 'src/device/interfaces/get.device.interface'; +import { TuyaContext } from '@tuya/tuya-connector-nodejs'; +import { ConfigService } from '@nestjs/config'; +import { firebaseDataBase } from '../../firebase.config'; +import { Database, DataSnapshot, get, ref, set } from 'firebase/database'; +@Injectable() +export class DeviceStatusFirebaseService { + private tuya: TuyaContext; + private firebaseDb: Database; + constructor( + private readonly configService: ConfigService, + private readonly deviceRepository: DeviceRepository, + ) { + const accessKey = this.configService.get('auth-config.ACCESS_KEY'); + const secretKey = this.configService.get('auth-config.SECRET_KEY'); + const tuyaEuUrl = this.configService.get('tuya-config.TUYA_EU_URL'); + this.tuya = new TuyaContext({ + baseUrl: tuyaEuUrl, + accessKey, + secretKey, + }); + + // Initialize firebaseDb using firebaseDataBase function + this.firebaseDb = firebaseDataBase(this.configService); + } + async addDeviceStatusByDeviceUuid( + deviceTuyaUuid: string, + ): Promise { + try { + const device = await this.getDeviceByDeviceTuyaUuid(deviceTuyaUuid); + if (device.uuid) { + const deviceStatus = await this.getDevicesInstructionStatus( + device.uuid, + ); + if (deviceStatus.productUuid) { + const deviceStatusSaved = await this.createDeviceStatusFirebase({ + deviceUuid: device.uuid, + deviceTuyaUuid: deviceTuyaUuid, + status: deviceStatus.status, + productUuid: deviceStatus.productUuid, + productType: deviceStatus.productType, + }); + + return deviceStatusSaved; + } + } + } catch (error) { + throw new HttpException( + 'Device Tuya UUID not found', + error.status || HttpStatus.BAD_REQUEST, + ); + } + } + async addDeviceStatusToFirebase( + addDeviceStatusDto: AddDeviceStatusDto, + ): Promise { + try { + const device = await this.getDeviceByDeviceTuyaUuid( + addDeviceStatusDto.deviceTuyaUuid, + ); + + if (device?.uuid) { + return await this.createDeviceStatusFirebase({ + deviceUuid: device.uuid, + ...addDeviceStatusDto, + }); + } + // Return null if device not found or no UUID + return null; + } catch (error) { + // Handle the error silently, perhaps log it internally or ignore it + return null; + } + } + + async getDeviceByDeviceTuyaUuid(deviceTuyaUuid: string) { + return await this.deviceRepository.findOne({ + where: { + deviceTuyaUuid, + }, + relations: ['productDevice'], + }); + } + async getDevicesInstructionStatus(deviceUuid: string) { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + + if (!deviceDetails) { + throw new NotFoundException('Device Not Found'); + } + const deviceStatus = await this.getDevicesInstructionStatusTuya( + deviceDetails.deviceTuyaUuid, + ); + + return { + productUuid: deviceDetails.productDevice.uuid, + productType: deviceDetails.productDevice.prodType, + status: deviceStatus.result[0].status, + }; + } catch (error) { + throw new HttpException( + 'Error fetching device functions status', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getDevicesInstructionStatusTuya( + deviceUuid: string, + ): Promise { + try { + const path = `/v1.0/iot-03/devices/status`; + const response = await this.tuya.request({ + method: 'GET', + path, + query: { + device_ids: deviceUuid, + }, + }); + return response as GetDeviceDetailsFunctionsStatusInterface; + } catch (error) { + throw new HttpException( + 'Error fetching device functions status from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getDeviceByDeviceUuid( + deviceUuid: string, + withProductDevice: boolean = true, + ) { + return await this.deviceRepository.findOne({ + where: { + uuid: deviceUuid, + }, + ...(withProductDevice && { relations: ['productDevice'] }), + }); + } + async createDeviceStatusFirebase( + addDeviceStatusDto: AddDeviceStatusDto, + ): Promise { + const dataRef = ref( + this.firebaseDb, + `device-status/${addDeviceStatusDto.deviceUuid}`, + ); + const snapshot: DataSnapshot = await get(dataRef); + const existingData = snapshot.val() || {}; + + // Assign default values if fields are not present + if (!existingData.deviceTuyaUuid) { + existingData.deviceTuyaUuid = addDeviceStatusDto.deviceTuyaUuid; + } + if (!existingData.productUuid) { + existingData.productUuid = addDeviceStatusDto.productUuid; + } + if (!existingData.productType) { + existingData.productType = addDeviceStatusDto.productType; + } + if (!existingData.status) { + existingData.status = []; + } + + // Create a map to track existing status codes + const statusMap = new Map( + existingData.status.map((item) => [item.code, item.value]), + ); + + // Update or add status codes + for (const statusItem of addDeviceStatusDto.status) { + statusMap.set(statusItem.code, statusItem.value); + } + + // Convert the map back to an array format + existingData.status = Array.from(statusMap, ([code, value]) => ({ + code, + value, + })); + + // Save the updated data to Firebase + await set(dataRef, existingData); + + // Return the updated data + return existingData; + } +} diff --git a/libs/common/src/firebase/firebase.config.ts b/libs/common/src/firebase/firebase.config.ts new file mode 100644 index 0000000..e34b0e3 --- /dev/null +++ b/libs/common/src/firebase/firebase.config.ts @@ -0,0 +1,24 @@ +import { initializeApp } from 'firebase/app'; +import { getDatabase } from 'firebase/database'; +import { ConfigService } from '@nestjs/config'; + +export const initializeFirebaseApp = (configService: ConfigService) => { + const firebaseConfig = { + apiKey: configService.get('FIREBASE_API_KEY'), + authDomain: configService.get('FIREBASE_AUTH_DOMAIN'), + projectId: configService.get('FIREBASE_PROJECT_ID'), + storageBucket: configService.get('FIREBASE_STORAGE_BUCKET'), + messagingSenderId: configService.get( + 'FIREBASE_MESSAGING_SENDER_ID', + ), + appId: configService.get('FIREBASE_APP_ID'), + measurementId: configService.get('FIREBASE_MEASUREMENT_ID'), + databaseURL: configService.get('FIREBASE_DATABASE_URL'), + }; + + const app = initializeApp(firebaseConfig); + return getDatabase(app); +}; + +export const firebaseDataBase = (configService: ConfigService) => + initializeFirebaseApp(configService); diff --git a/libs/common/src/firebase/firebase.shared.module.ts b/libs/common/src/firebase/firebase.shared.module.ts new file mode 100644 index 0000000..df8f6bd --- /dev/null +++ b/libs/common/src/firebase/firebase.shared.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { DeviceStatusFirebaseModule } from './devices-status/devices-status.module'; + +@Module({ + imports: [DeviceStatusFirebaseModule], + providers: [], + exports: [], +}) +export class FirebaseSharedModule {} diff --git a/libs/common/src/helper/differenceInSeconds.ts b/libs/common/src/helper/differenceInSeconds.ts new file mode 100644 index 0000000..9f05d67 --- /dev/null +++ b/libs/common/src/helper/differenceInSeconds.ts @@ -0,0 +1,4 @@ +export function differenceInSeconds(date1: Date, date2: Date): number { + const diffInMilliseconds = date1.getTime() - date2.getTime(); + return Math.floor(diffInMilliseconds / 1000); +} diff --git a/libs/common/src/helper/helper.module.ts b/libs/common/src/helper/helper.module.ts index 3378051..34a51c2 100644 --- a/libs/common/src/helper/helper.module.ts +++ b/libs/common/src/helper/helper.module.ts @@ -7,8 +7,9 @@ import { TuyaWebSocketService } from './services/tuya.web.socket.service'; import { OneSignalService } from './services/onesignal.service'; import { DeviceMessagesService } from './services/device.messages.service'; -import { DeviceNotificationRepositoryModule } from '../modules/device-notification/device.notification.module'; -import { DeviceNotificationRepository } from '../modules/device-notification/repositories'; +import { DeviceRepositoryModule } from '../modules/device/device.repository.module'; +import { DeviceNotificationRepository } from '../modules/device/repositories'; +import { DeviceStatusFirebaseModule } from '../firebase/devices-status/devices-status.module'; @Global() @Module({ @@ -23,6 +24,10 @@ import { DeviceNotificationRepository } from '../modules/device-notification/rep ], exports: [HelperHashService, SpacePermissionService], controllers: [], - imports: [SpaceRepositoryModule, DeviceNotificationRepositoryModule], + imports: [ + SpaceRepositoryModule, + DeviceRepositoryModule, + DeviceStatusFirebaseModule, + ], }) export class HelperModule {} diff --git a/libs/common/src/helper/services/device.messages.service.ts b/libs/common/src/helper/services/device.messages.service.ts index c51f6f4..c4e7024 100644 --- a/libs/common/src/helper/services/device.messages.service.ts +++ b/libs/common/src/helper/services/device.messages.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { DeviceNotificationRepository } from '@app/common/modules/device-notification/repositories'; +import { DeviceNotificationRepository } from '@app/common/modules/device/repositories'; import { OneSignalService } from './onesignal.service'; @Injectable() diff --git a/libs/common/src/helper/services/tuya.web.socket.service.ts b/libs/common/src/helper/services/tuya.web.socket.service.ts index cca7fc6..5b8d89a 100644 --- a/libs/common/src/helper/services/tuya.web.socket.service.ts +++ b/libs/common/src/helper/services/tuya.web.socket.service.ts @@ -1,8 +1,7 @@ import { Injectable } from '@nestjs/common'; import TuyaWebsocket from '../../config/tuya-web-socket-config'; import { ConfigService } from '@nestjs/config'; -import { OneSignalService } from './onesignal.service'; -import { DeviceMessagesService } from './device.messages.service'; +import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service'; @Injectable() export class TuyaWebSocketService { @@ -10,8 +9,7 @@ export class TuyaWebSocketService { constructor( private readonly configService: ConfigService, - private readonly oneSignalService: OneSignalService, - private readonly deviceMessagesService: DeviceMessagesService, + private readonly deviceStatusFirebaseService: DeviceStatusFirebaseService, ) { // Initialize the TuyaWebsocket client this.client = new TuyaWebsocket({ @@ -39,10 +37,11 @@ export class TuyaWebSocketService { this.client.message(async (ws: WebSocket, message: any) => { try { - await this.deviceMessagesService.getDevicesUserNotifications( - message.payload.data.bizData.devId, - message.payload.data.bizData, - ); + await this.deviceStatusFirebaseService.addDeviceStatusToFirebase({ + deviceTuyaUuid: message.payload.data.bizData.devId, + status: message.payload.data.bizData.properties, + }); + this.client.ackMessage(message.messageId); } catch (error) { console.error('Error processing message:', error); diff --git a/libs/common/src/modules/device-notification/device.notification.module.ts b/libs/common/src/modules/device-notification/device.notification.module.ts deleted file mode 100644 index 73acce3..0000000 --- a/libs/common/src/modules/device-notification/device.notification.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { DeviceNotificationEntity } from './entities'; -@Module({ - providers: [], - exports: [], - controllers: [], - imports: [TypeOrmModule.forFeature([DeviceNotificationEntity])], -}) -export class DeviceNotificationRepositoryModule {} diff --git a/libs/common/src/modules/device-notification/dtos/index.ts b/libs/common/src/modules/device-notification/dtos/index.ts deleted file mode 100644 index d205031..0000000 --- a/libs/common/src/modules/device-notification/dtos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './device.notification.dto'; diff --git a/libs/common/src/modules/device-notification/entities/device.notification.entity.ts b/libs/common/src/modules/device-notification/entities/device.notification.entity.ts deleted file mode 100644 index 84e592a..0000000 --- a/libs/common/src/modules/device-notification/entities/device.notification.entity.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Column, Entity, ManyToOne, Unique } from 'typeorm'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { DeviceNotificationDto } from '../dtos'; -import { DeviceEntity } from '../../device/entities'; -import { UserEntity } from '../../user/entities'; - -@Entity({ name: 'device-notification' }) -@Unique(['userUuid', 'deviceUuid']) -export class DeviceNotificationEntity extends AbstractEntity { - @Column({ - nullable: false, - }) - public userUuid: string; - - @Column({ - nullable: false, - }) - deviceUuid: string; - - @ManyToOne(() => DeviceEntity, (device) => device.permission, { - nullable: false, - }) - device: DeviceEntity; - - @ManyToOne(() => UserEntity, (user) => user.userPermission, { - nullable: false, - }) - user: UserEntity; - constructor(partial: Partial) { - super(); - Object.assign(this, partial); - } -} diff --git a/libs/common/src/modules/device-notification/entities/index.ts b/libs/common/src/modules/device-notification/entities/index.ts deleted file mode 100644 index fedfc80..0000000 --- a/libs/common/src/modules/device-notification/entities/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './device.notification.entity'; diff --git a/libs/common/src/modules/device-notification/repositories/device.notification.repository.ts b/libs/common/src/modules/device-notification/repositories/device.notification.repository.ts deleted file mode 100644 index b226791..0000000 --- a/libs/common/src/modules/device-notification/repositories/device.notification.repository.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { DataSource, Repository } from 'typeorm'; -import { Injectable } from '@nestjs/common'; -import { DeviceNotificationEntity } from '../entities'; - -@Injectable() -export class DeviceNotificationRepository extends Repository { - constructor(private dataSource: DataSource) { - super(DeviceNotificationEntity, dataSource.createEntityManager()); - } -} diff --git a/libs/common/src/modules/device-notification/repositories/index.ts b/libs/common/src/modules/device-notification/repositories/index.ts deleted file mode 100644 index 1f3da6f..0000000 --- a/libs/common/src/modules/device-notification/repositories/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './device.notification.repository'; diff --git a/libs/common/src/modules/device-user-permission/device.user.permission.repository.module.ts b/libs/common/src/modules/device-user-permission/device.user.permission.repository.module.ts deleted file mode 100644 index d950fd8..0000000 --- a/libs/common/src/modules/device-user-permission/device.user.permission.repository.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { DeviceUserPermissionEntity } from './entities'; -@Module({ - providers: [], - exports: [], - controllers: [], - imports: [TypeOrmModule.forFeature([DeviceUserPermissionEntity])], -}) -export class DeviceUserPermissionRepositoryModule {} diff --git a/libs/common/src/modules/device-user-permission/dtos/device.user.permission.dto.ts b/libs/common/src/modules/device-user-permission/dtos/device.user.permission.dto.ts deleted file mode 100644 index e78ea3c..0000000 --- a/libs/common/src/modules/device-user-permission/dtos/device.user.permission.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { IsNotEmpty, IsString } from 'class-validator'; - -export class DeviceUserPermissionDto { - @IsString() - @IsNotEmpty() - public uuid: string; - - @IsString() - @IsNotEmpty() - public userUuid: string; - - @IsString() - @IsNotEmpty() - public deviceUuid: string; - - @IsString() - @IsNotEmpty() - public permissionTypeUuid: string; -} diff --git a/libs/common/src/modules/device-user-permission/dtos/index.ts b/libs/common/src/modules/device-user-permission/dtos/index.ts deleted file mode 100644 index b95b695..0000000 --- a/libs/common/src/modules/device-user-permission/dtos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './device.user.permission.dto'; diff --git a/libs/common/src/modules/device-user-permission/entities/device.user.permission.entity.ts b/libs/common/src/modules/device-user-permission/entities/device.user.permission.entity.ts deleted file mode 100644 index d136df7..0000000 --- a/libs/common/src/modules/device-user-permission/entities/device.user.permission.entity.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Column, Entity, ManyToOne, Unique } from 'typeorm'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { DeviceUserPermissionDto } from '../dtos'; -import { PermissionTypeEntity } from '../../permission/entities'; -import { DeviceEntity } from '../../device/entities'; -import { UserEntity } from '../../user/entities'; - -@Entity({ name: 'device-user-permission' }) -@Unique(['userUuid', 'deviceUuid']) -export class DeviceUserPermissionEntity extends AbstractEntity { - @Column({ - nullable: false, - }) - public userUuid: string; - - @Column({ - nullable: false, - }) - deviceUuid: string; - - @ManyToOne(() => DeviceEntity, (device) => device.permission, { - nullable: false, - }) - device: DeviceEntity; - - @ManyToOne( - () => PermissionTypeEntity, - (permissionType) => permissionType.permission, - { - nullable: false, - }, - ) - permissionType: PermissionTypeEntity; - - @ManyToOne(() => UserEntity, (user) => user.userPermission, { - nullable: false, - }) - user: UserEntity; - constructor(partial: Partial) { - super(); - Object.assign(this, partial); - } -} diff --git a/libs/common/src/modules/device-user-permission/entities/index.ts b/libs/common/src/modules/device-user-permission/entities/index.ts deleted file mode 100644 index 9e95d82..0000000 --- a/libs/common/src/modules/device-user-permission/entities/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './device.user.permission.entity'; diff --git a/libs/common/src/modules/device-user-permission/repositories/device.user.permission.repository.ts b/libs/common/src/modules/device-user-permission/repositories/device.user.permission.repository.ts deleted file mode 100644 index a1776b3..0000000 --- a/libs/common/src/modules/device-user-permission/repositories/device.user.permission.repository.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { DataSource, Repository } from 'typeorm'; -import { Injectable } from '@nestjs/common'; -import { DeviceUserPermissionEntity } from '../entities'; - -@Injectable() -export class DeviceUserPermissionRepository extends Repository { - constructor(private dataSource: DataSource) { - super(DeviceUserPermissionEntity, dataSource.createEntityManager()); - } -} diff --git a/libs/common/src/modules/device-user-permission/repositories/index.ts b/libs/common/src/modules/device-user-permission/repositories/index.ts deleted file mode 100644 index 6957209..0000000 --- a/libs/common/src/modules/device-user-permission/repositories/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './device.user.permission.repository'; diff --git a/libs/common/src/modules/device/device.repository.module.ts b/libs/common/src/modules/device/device.repository.module.ts index 438e268..4b914fa 100644 --- a/libs/common/src/modules/device/device.repository.module.ts +++ b/libs/common/src/modules/device/device.repository.module.ts @@ -1,11 +1,21 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { DeviceEntity } from './entities'; +import { + DeviceEntity, + DeviceNotificationEntity, + DeviceUserPermissionEntity, +} from './entities'; @Module({ providers: [], exports: [], controllers: [], - imports: [TypeOrmModule.forFeature([DeviceEntity])], + imports: [ + TypeOrmModule.forFeature([ + DeviceEntity, + DeviceNotificationEntity, + DeviceUserPermissionEntity, + ]), + ], }) export class DeviceRepositoryModule {} diff --git a/libs/common/src/modules/device/dtos/device.dto.ts b/libs/common/src/modules/device/dtos/device.dto.ts index 7a6ba0c..41a0476 100644 --- a/libs/common/src/modules/device/dtos/device.dto.ts +++ b/libs/common/src/modules/device/dtos/device.dto.ts @@ -21,3 +21,35 @@ export class DeviceDto { @IsNotEmpty() productUuid: string; } + +export class DeviceUserPermissionDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public userUuid: string; + + @IsString() + @IsNotEmpty() + public deviceUuid: string; + + @IsString() + @IsNotEmpty() + public permissionTypeUuid: string; +} + +export class DeviceNotificationDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public userUuid: string; + + @IsString() + @IsNotEmpty() + public deviceUuid: string; +} diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index f782b4a..c02f8a1 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -1,11 +1,11 @@ import { Column, Entity, ManyToOne, OneToMany, Unique, Index } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { DeviceDto } from '../dtos/device.dto'; +import { DeviceDto, DeviceUserPermissionDto } from '../dtos/device.dto'; import { SpaceEntity } from '../../space/entities'; import { ProductEntity } from '../../product/entities'; -import { DeviceUserPermissionEntity } from '../../device-user-permission/entities'; -import { DeviceNotificationEntity } from '../../device-notification/entities'; import { UserEntity } from '../../user/entities'; +import { DeviceNotificationDto } from '../dtos'; +import { PermissionTypeEntity } from '../../permission/entities'; @Entity({ name: 'device' }) @Unique(['deviceTuyaUuid']) @@ -60,3 +60,68 @@ export class DeviceEntity extends AbstractEntity { Object.assign(this, partial); } } + +@Entity({ name: 'device-notification' }) +@Unique(['userUuid', 'deviceUuid']) +export class DeviceNotificationEntity extends AbstractEntity { + @Column({ + nullable: false, + }) + public userUuid: string; + + @Column({ + nullable: false, + }) + deviceUuid: string; + + @ManyToOne(() => DeviceEntity, (device) => device.permission, { + nullable: false, + }) + device: DeviceEntity; + + @ManyToOne(() => UserEntity, (user) => user.userPermission, { + nullable: false, + }) + user: UserEntity; + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} + +@Entity({ name: 'device-user-permission' }) +@Unique(['userUuid', 'deviceUuid']) +export class DeviceUserPermissionEntity extends AbstractEntity { + @Column({ + nullable: false, + }) + public userUuid: string; + + @Column({ + nullable: false, + }) + deviceUuid: string; + + @ManyToOne(() => DeviceEntity, (device) => device.permission, { + nullable: false, + }) + device: DeviceEntity; + + @ManyToOne( + () => PermissionTypeEntity, + (permissionType) => permissionType.permission, + { + nullable: false, + }, + ) + permissionType: PermissionTypeEntity; + + @ManyToOne(() => UserEntity, (user) => user.userPermission, { + nullable: false, + }) + user: UserEntity; + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/device/repositories/device.repository.ts b/libs/common/src/modules/device/repositories/device.repository.ts index 267c06c..dad195b 100644 --- a/libs/common/src/modules/device/repositories/device.repository.ts +++ b/libs/common/src/modules/device/repositories/device.repository.ts @@ -1,6 +1,10 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; -import { DeviceEntity } from '../entities'; +import { + DeviceEntity, + DeviceNotificationEntity, + DeviceUserPermissionEntity, +} from '../entities'; @Injectable() export class DeviceRepository extends Repository { @@ -8,3 +12,15 @@ export class DeviceRepository extends Repository { super(DeviceEntity, dataSource.createEntityManager()); } } +@Injectable() +export class DeviceNotificationRepository extends Repository { + constructor(private dataSource: DataSource) { + super(DeviceNotificationEntity, dataSource.createEntityManager()); + } +} +@Injectable() +export class DeviceUserPermissionRepository extends Repository { + constructor(private dataSource: DataSource) { + super(DeviceUserPermissionEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/permission/entities/permission.entity.ts b/libs/common/src/modules/permission/entities/permission.entity.ts index d15d936..ffb6c9f 100644 --- a/libs/common/src/modules/permission/entities/permission.entity.ts +++ b/libs/common/src/modules/permission/entities/permission.entity.ts @@ -2,7 +2,7 @@ import { Column, Entity, OneToMany } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { PermissionType } from '@app/common/constants/permission-type.enum'; import { PermissionTypeDto } from '../dtos/permission.dto'; -import { DeviceUserPermissionEntity } from '../../device-user-permission/entities'; +import { DeviceUserPermissionEntity } from '../../device/entities'; @Entity({ name: 'permission-type' }) export class PermissionTypeEntity extends AbstractEntity { diff --git a/libs/common/src/modules/role-type/entities/role.type.entity.ts b/libs/common/src/modules/role-type/entities/role.type.entity.ts index fd332fb..10f30bd 100644 --- a/libs/common/src/modules/role-type/entities/role.type.entity.ts +++ b/libs/common/src/modules/role-type/entities/role.type.entity.ts @@ -2,7 +2,7 @@ import { Column, Entity, OneToMany, Unique } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { RoleTypeDto } from '../dtos/role.type.dto'; import { RoleType } from '@app/common/constants/role.type.enum'; -import { UserRoleEntity } from '../../user-role/entities'; +import { UserRoleEntity } from '../../user/entities'; @Entity({ name: 'role-type' }) @Unique(['type']) diff --git a/libs/common/src/modules/space-type/dtos/index.ts b/libs/common/src/modules/space-type/dtos/index.ts deleted file mode 100644 index e9824e7..0000000 --- a/libs/common/src/modules/space-type/dtos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './space.type.dto'; diff --git a/libs/common/src/modules/space-type/dtos/space.type.dto.ts b/libs/common/src/modules/space-type/dtos/space.type.dto.ts deleted file mode 100644 index 2f3d807..0000000 --- a/libs/common/src/modules/space-type/dtos/space.type.dto.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { IsNotEmpty, IsString } from 'class-validator'; - -export class SpaceTypeDto { - @IsString() - @IsNotEmpty() - public uuid: string; - - @IsString() - @IsNotEmpty() - public type: string; -} diff --git a/libs/common/src/modules/space-type/entities/index.ts b/libs/common/src/modules/space-type/entities/index.ts deleted file mode 100644 index 71944ff..0000000 --- a/libs/common/src/modules/space-type/entities/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './space.type.entity'; diff --git a/libs/common/src/modules/space-type/entities/space.type.entity.ts b/libs/common/src/modules/space-type/entities/space.type.entity.ts deleted file mode 100644 index f66ee86..0000000 --- a/libs/common/src/modules/space-type/entities/space.type.entity.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Column, Entity, OneToMany } from 'typeorm'; -import { SpaceTypeDto } from '../dtos'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { SpaceEntity } from '../../space/entities'; - -@Entity({ name: 'space-type' }) -export class SpaceTypeEntity extends AbstractEntity { - @Column({ - type: 'uuid', - default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value - nullable: false, - }) - public uuid: string; - - @Column({ - nullable: false, - }) - type: string; - - @OneToMany(() => SpaceEntity, (space) => space.spaceType) - spaces: SpaceEntity[]; - constructor(partial: Partial) { - super(); - Object.assign(this, partial); - } -} diff --git a/libs/common/src/modules/space-type/repositories/index.ts b/libs/common/src/modules/space-type/repositories/index.ts deleted file mode 100644 index 26f69ae..0000000 --- a/libs/common/src/modules/space-type/repositories/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './space.type.repository'; diff --git a/libs/common/src/modules/space-type/repositories/space.type.repository.ts b/libs/common/src/modules/space-type/repositories/space.type.repository.ts deleted file mode 100644 index 0ea5b13..0000000 --- a/libs/common/src/modules/space-type/repositories/space.type.repository.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { DataSource, Repository } from 'typeorm'; -import { Injectable } from '@nestjs/common'; -import { SpaceTypeEntity } from '../entities/space.type.entity'; - -@Injectable() -export class SpaceTypeRepository extends Repository { - constructor(private dataSource: DataSource) { - super(SpaceTypeEntity, dataSource.createEntityManager()); - } -} diff --git a/libs/common/src/modules/space-type/space.type.repository.module.ts b/libs/common/src/modules/space-type/space.type.repository.module.ts deleted file mode 100644 index 6787b67..0000000 --- a/libs/common/src/modules/space-type/space.type.repository.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { SpaceTypeEntity } from './entities/space.type.entity'; - -@Module({ - providers: [], - exports: [], - controllers: [], - imports: [TypeOrmModule.forFeature([SpaceTypeEntity])], -}) -export class SpaceTypeRepositoryModule {} diff --git a/libs/common/src/modules/space/dtos/space.dto.ts b/libs/common/src/modules/space/dtos/space.dto.ts index 98706d0..04072fd 100644 --- a/libs/common/src/modules/space/dtos/space.dto.ts +++ b/libs/common/src/modules/space/dtos/space.dto.ts @@ -21,3 +21,13 @@ export class SpaceDto { @IsNotEmpty() public invitationCode: string; } + +export class SpaceTypeDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public type: string; +} diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 030db57..10e78a8 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -1,16 +1,37 @@ import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; -import { SpaceDto } from '../dtos'; +import { SpaceDto, SpaceTypeDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { SpaceTypeEntity } from '../../space-type/entities'; -import { UserSpaceEntity } from '../../user-space/entities'; +import { UserSpaceEntity } from '../../user/entities'; import { DeviceEntity } from '../../device/entities'; +@Entity({ name: 'space-type' }) +export class SpaceTypeEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', + nullable: false, + }) + public uuid: string; + + @Column({ + nullable: false, + }) + type: string; + + @OneToMany(() => SpaceEntity, (space) => space.spaceType) + spaces: SpaceEntity[]; + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} + @Entity({ name: 'space' }) @Unique(['invitationCode']) export class SpaceEntity extends AbstractEntity { @Column({ type: 'uuid', - default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value + default: () => 'gen_random_uuid()', nullable: false, }) public uuid: string; diff --git a/libs/common/src/modules/space/repositories/space.repository.ts b/libs/common/src/modules/space/repositories/space.repository.ts index c939761..ea11a6e 100644 --- a/libs/common/src/modules/space/repositories/space.repository.ts +++ b/libs/common/src/modules/space/repositories/space.repository.ts @@ -1,6 +1,6 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; -import { SpaceEntity } from '../entities/space.entity'; +import { SpaceEntity, SpaceTypeEntity } from '../entities'; @Injectable() export class SpaceRepository extends Repository { @@ -8,3 +8,10 @@ export class SpaceRepository extends Repository { super(SpaceEntity, dataSource.createEntityManager()); } } + +@Injectable() +export class SpaceTypeRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SpaceTypeEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/space/space.repository.module.ts b/libs/common/src/modules/space/space.repository.module.ts index 58a8ad0..9906708 100644 --- a/libs/common/src/modules/space/space.repository.module.ts +++ b/libs/common/src/modules/space/space.repository.module.ts @@ -1,11 +1,11 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { SpaceEntity } from './entities/space.entity'; +import { SpaceEntity, SpaceTypeEntity } from './entities'; @Module({ providers: [], exports: [], controllers: [], - imports: [TypeOrmModule.forFeature([SpaceEntity])], + imports: [TypeOrmModule.forFeature([SpaceEntity, SpaceTypeEntity])], }) export class SpaceRepositoryModule {} diff --git a/libs/common/src/modules/user-notification/dtos/index.ts b/libs/common/src/modules/user-notification/dtos/index.ts deleted file mode 100644 index 307e6f3..0000000 --- a/libs/common/src/modules/user-notification/dtos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './user.notification.dto'; diff --git a/libs/common/src/modules/user-notification/dtos/user.notification.dto.ts b/libs/common/src/modules/user-notification/dtos/user.notification.dto.ts deleted file mode 100644 index 4e9f72f..0000000 --- a/libs/common/src/modules/user-notification/dtos/user.notification.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { IsBoolean, IsNotEmpty, IsString } from 'class-validator'; - -export class UserNotificationDto { - @IsString() - @IsNotEmpty() - public uuid: string; - - @IsString() - @IsNotEmpty() - public userUuid: string; - - @IsString() - @IsNotEmpty() - public subscriptionUuid: string; - - @IsBoolean() - @IsNotEmpty() - public active: boolean; -} diff --git a/libs/common/src/modules/user-notification/entities/index.ts b/libs/common/src/modules/user-notification/entities/index.ts deleted file mode 100644 index 1acf5c0..0000000 --- a/libs/common/src/modules/user-notification/entities/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './user.notification.entity'; diff --git a/libs/common/src/modules/user-notification/entities/user.notification.entity.ts b/libs/common/src/modules/user-notification/entities/user.notification.entity.ts deleted file mode 100644 index aa6ec6b..0000000 --- a/libs/common/src/modules/user-notification/entities/user.notification.entity.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Column, Entity, ManyToOne, Unique } from 'typeorm'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { UserNotificationDto } from '../dtos'; -import { UserEntity } from '../../user/entities'; - -@Entity({ name: 'user-notification' }) -@Unique(['user', 'subscriptionUuid']) -export class UserNotificationEntity extends AbstractEntity { - @ManyToOne(() => UserEntity, (user) => user.roles, { - nullable: false, - }) - user: UserEntity; - @Column({ - nullable: false, - }) - subscriptionUuid: string; - - @Column({ - nullable: false, - default: true, - }) - active: boolean; - constructor(partial: Partial) { - super(); - Object.assign(this, partial); - } -} diff --git a/libs/common/src/modules/user-notification/repositories/index.ts b/libs/common/src/modules/user-notification/repositories/index.ts deleted file mode 100644 index 76d3ac2..0000000 --- a/libs/common/src/modules/user-notification/repositories/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './user.notification.repository'; diff --git a/libs/common/src/modules/user-notification/repositories/user.notification.repository.ts b/libs/common/src/modules/user-notification/repositories/user.notification.repository.ts deleted file mode 100644 index 6862e88..0000000 --- a/libs/common/src/modules/user-notification/repositories/user.notification.repository.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { DataSource, Repository } from 'typeorm'; -import { Injectable } from '@nestjs/common'; -import { UserNotificationEntity } from '../entities'; - -@Injectable() -export class UserNotificationRepository extends Repository { - constructor(private dataSource: DataSource) { - super(UserNotificationEntity, dataSource.createEntityManager()); - } -} diff --git a/libs/common/src/modules/user-notification/user.notification.repository.module.ts b/libs/common/src/modules/user-notification/user.notification.repository.module.ts deleted file mode 100644 index 73997be..0000000 --- a/libs/common/src/modules/user-notification/user.notification.repository.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { UserNotificationEntity } from './entities'; -@Module({ - providers: [], - exports: [], - controllers: [], - imports: [TypeOrmModule.forFeature([UserNotificationEntity])], -}) -export class UserNotificationRepositoryModule {} diff --git a/libs/common/src/modules/user-otp/dtos/index.ts b/libs/common/src/modules/user-otp/dtos/index.ts deleted file mode 100644 index 114762e..0000000 --- a/libs/common/src/modules/user-otp/dtos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './user-otp.dto'; diff --git a/libs/common/src/modules/user-otp/dtos/user-otp.dto.ts b/libs/common/src/modules/user-otp/dtos/user-otp.dto.ts deleted file mode 100644 index febdacb..0000000 --- a/libs/common/src/modules/user-otp/dtos/user-otp.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { IsNotEmpty, IsString } from 'class-validator'; - -export class UserOtpDto { - @IsString() - @IsNotEmpty() - public uuid: string; - - @IsString() - @IsNotEmpty() - public email: string; - - @IsString() - @IsNotEmpty() - public otpCode: string; - - @IsString() - @IsNotEmpty() - public expiryTime: string; -} diff --git a/libs/common/src/modules/user-otp/entities/index.ts b/libs/common/src/modules/user-otp/entities/index.ts deleted file mode 100644 index d09957f..0000000 --- a/libs/common/src/modules/user-otp/entities/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './user-otp.entity'; diff --git a/libs/common/src/modules/user-otp/entities/user-otp.entity.ts b/libs/common/src/modules/user-otp/entities/user-otp.entity.ts deleted file mode 100644 index 454a3b1..0000000 --- a/libs/common/src/modules/user-otp/entities/user-otp.entity.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Column, Entity } from 'typeorm'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { UserOtpDto } from '../dtos'; -import { OtpType } from '../../../../src/constants/otp-type.enum'; - -@Entity({ name: 'user-otp' }) -export class UserOtpEntity extends AbstractEntity { - @Column({ - type: 'uuid', - default: () => 'gen_random_uuid()', - nullable: false, - }) - public uuid: string; - - @Column({ nullable: false }) - email: string; - - @Column({ nullable: false }) - otpCode: string; - - @Column({ nullable: false }) - expiryTime: Date; - - @Column({ - type: 'enum', - enum: Object.values(OtpType), - }) - type: OtpType; - - constructor(partial: Partial) { - super(); - Object.assign(this, partial); - } -} diff --git a/libs/common/src/modules/user-otp/repositories/index.ts b/libs/common/src/modules/user-otp/repositories/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/libs/common/src/modules/user-otp/repositories/user-otp.repository.ts b/libs/common/src/modules/user-otp/repositories/user-otp.repository.ts deleted file mode 100644 index 75cff43..0000000 --- a/libs/common/src/modules/user-otp/repositories/user-otp.repository.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { DataSource, Repository } from 'typeorm'; -import { Injectable } from '@nestjs/common'; -import { UserOtpEntity } from '../entities'; - -@Injectable() -export class UserOtpRepository extends Repository { - constructor(private dataSource: DataSource) { - super(UserOtpEntity, dataSource.createEntityManager()); - } -} diff --git a/libs/common/src/modules/user-otp/user-otp.repository.module.ts b/libs/common/src/modules/user-otp/user-otp.repository.module.ts deleted file mode 100644 index 9286d8b..0000000 --- a/libs/common/src/modules/user-otp/user-otp.repository.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { UserOtpEntity } from './entities'; - -@Module({ - providers: [], - exports: [], - controllers: [], - imports: [TypeOrmModule.forFeature([UserOtpEntity])], -}) -export class UserOtpRepositoryModule {} diff --git a/libs/common/src/modules/user-role/dtos/index.ts b/libs/common/src/modules/user-role/dtos/index.ts deleted file mode 100644 index 7879674..0000000 --- a/libs/common/src/modules/user-role/dtos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './user.role.dto'; diff --git a/libs/common/src/modules/user-role/dtos/user.role.dto.ts b/libs/common/src/modules/user-role/dtos/user.role.dto.ts deleted file mode 100644 index 3ff4dab..0000000 --- a/libs/common/src/modules/user-role/dtos/user.role.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { IsNotEmpty, IsString } from 'class-validator'; - -export class UserRoleDto { - @IsString() - @IsNotEmpty() - public uuid: string; - - @IsString() - @IsNotEmpty() - public userUuid: string; - - @IsString() - @IsNotEmpty() - public roleTypeUuid: string; -} diff --git a/libs/common/src/modules/user-role/entities/index.ts b/libs/common/src/modules/user-role/entities/index.ts deleted file mode 100644 index b6f3bd9..0000000 --- a/libs/common/src/modules/user-role/entities/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './user.role.entity'; diff --git a/libs/common/src/modules/user-role/entities/user.role.entity.ts b/libs/common/src/modules/user-role/entities/user.role.entity.ts deleted file mode 100644 index c733594..0000000 --- a/libs/common/src/modules/user-role/entities/user.role.entity.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Entity, ManyToOne, Unique } from 'typeorm'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { UserRoleDto } from '../dtos'; -import { UserEntity } from '../../user/entities'; -import { RoleTypeEntity } from '../../role-type/entities'; - -@Entity({ name: 'user-role' }) -@Unique(['user', 'roleType']) -export class UserRoleEntity extends AbstractEntity { - @ManyToOne(() => UserEntity, (user) => user.roles, { - nullable: false, - }) - user: UserEntity; - - @ManyToOne(() => RoleTypeEntity, (roleType) => roleType.roles, { - nullable: false, - }) - roleType: RoleTypeEntity; - - constructor(partial: Partial) { - super(); - Object.assign(this, partial); - } -} diff --git a/libs/common/src/modules/user-role/repositories/index.ts b/libs/common/src/modules/user-role/repositories/index.ts deleted file mode 100644 index abf7247..0000000 --- a/libs/common/src/modules/user-role/repositories/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './user.role.repository'; diff --git a/libs/common/src/modules/user-role/repositories/user.role.repository.ts b/libs/common/src/modules/user-role/repositories/user.role.repository.ts deleted file mode 100644 index 9bc9a24..0000000 --- a/libs/common/src/modules/user-role/repositories/user.role.repository.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { DataSource, Repository } from 'typeorm'; -import { Injectable } from '@nestjs/common'; -import { UserRoleEntity } from '../entities'; - -@Injectable() -export class UserRoleRepository extends Repository { - constructor(private dataSource: DataSource) { - super(UserRoleEntity, dataSource.createEntityManager()); - } -} diff --git a/libs/common/src/modules/user-role/user.role.repository.module.ts b/libs/common/src/modules/user-role/user.role.repository.module.ts deleted file mode 100644 index 540787e..0000000 --- a/libs/common/src/modules/user-role/user.role.repository.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { UserRoleEntity } from './entities'; -@Module({ - providers: [], - exports: [], - controllers: [], - imports: [TypeOrmModule.forFeature([UserRoleEntity])], -}) -export class UserRoleRepositoryModule {} diff --git a/libs/common/src/modules/user-space/dtos/index.ts b/libs/common/src/modules/user-space/dtos/index.ts deleted file mode 100644 index 41572f5..0000000 --- a/libs/common/src/modules/user-space/dtos/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './user.space.dto'; diff --git a/libs/common/src/modules/user-space/dtos/user.space.dto.ts b/libs/common/src/modules/user-space/dtos/user.space.dto.ts deleted file mode 100644 index b9ef4d0..0000000 --- a/libs/common/src/modules/user-space/dtos/user.space.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { IsNotEmpty, IsString } from 'class-validator'; - -export class UserSpaceDto { - @IsString() - @IsNotEmpty() - public uuid: string; - - @IsString() - @IsNotEmpty() - public spaceUuid: string; - - @IsString() - @IsNotEmpty() - public userUuid: string; -} diff --git a/libs/common/src/modules/user-space/entities/index.ts b/libs/common/src/modules/user-space/entities/index.ts deleted file mode 100644 index ef6849a..0000000 --- a/libs/common/src/modules/user-space/entities/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './user.space.entity'; diff --git a/libs/common/src/modules/user-space/entities/user.space.entity.ts b/libs/common/src/modules/user-space/entities/user.space.entity.ts deleted file mode 100644 index a6caaa5..0000000 --- a/libs/common/src/modules/user-space/entities/user.space.entity.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Column, Entity, ManyToOne, Unique } from 'typeorm'; -import { UserSpaceDto } from '../dtos'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { SpaceEntity } from '../../space/entities'; -import { UserEntity } from '../../user/entities'; - -@Entity({ name: 'user-space' }) -@Unique(['user', 'space']) -export class UserSpaceEntity extends AbstractEntity { - @Column({ - type: 'uuid', - default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value - nullable: false, - }) - public uuid: string; - - @ManyToOne(() => UserEntity, (user) => user.userSpaces, { nullable: false }) - user: UserEntity; - - @ManyToOne(() => SpaceEntity, (space) => space.userSpaces, { - nullable: false, - }) - space: SpaceEntity; - - constructor(partial: Partial) { - super(); - Object.assign(this, partial); - } -} diff --git a/libs/common/src/modules/user-space/repositories/index.ts b/libs/common/src/modules/user-space/repositories/index.ts deleted file mode 100644 index 3ad6d48..0000000 --- a/libs/common/src/modules/user-space/repositories/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './user.space.repository'; diff --git a/libs/common/src/modules/user-space/repositories/user.space.repository.ts b/libs/common/src/modules/user-space/repositories/user.space.repository.ts deleted file mode 100644 index b4b7507..0000000 --- a/libs/common/src/modules/user-space/repositories/user.space.repository.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { DataSource, Repository } from 'typeorm'; -import { Injectable } from '@nestjs/common'; -import { UserSpaceEntity } from '../entities/user.space.entity'; - -@Injectable() -export class UserSpaceRepository extends Repository { - constructor(private dataSource: DataSource) { - super(UserSpaceEntity, dataSource.createEntityManager()); - } -} diff --git a/libs/common/src/modules/user-space/user.space.repository.module.ts b/libs/common/src/modules/user-space/user.space.repository.module.ts deleted file mode 100644 index 9655252..0000000 --- a/libs/common/src/modules/user-space/user.space.repository.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { UserSpaceEntity } from './entities/user.space.entity'; - -@Module({ - providers: [], - exports: [], - controllers: [], - imports: [TypeOrmModule.forFeature([UserSpaceEntity])], -}) -export class UserSpaceRepositoryModule {} diff --git a/libs/common/src/modules/user/dtos/user.dto.ts b/libs/common/src/modules/user/dtos/user.dto.ts index 706ca31..0a4bda2 100644 --- a/libs/common/src/modules/user/dtos/user.dto.ts +++ b/libs/common/src/modules/user/dtos/user.dto.ts @@ -1,4 +1,4 @@ -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsString } from 'class-validator'; export class UserDto { @IsString() @@ -21,3 +21,67 @@ export class UserDto { @IsNotEmpty() public lastName: string; } + +export class UserNotificationDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public userUuid: string; + + @IsString() + @IsNotEmpty() + public subscriptionUuid: string; + + @IsBoolean() + @IsNotEmpty() + public active: boolean; +} + +export class UserOtpDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public email: string; + + @IsString() + @IsNotEmpty() + public otpCode: string; + + @IsString() + @IsNotEmpty() + public expiryTime: string; +} + +export class UserRoleDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public userUuid: string; + + @IsString() + @IsNotEmpty() + public roleTypeUuid: string; +} + +export class UserSpaceDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public spaceUuid: string; + + @IsString() + @IsNotEmpty() + public userUuid: string; +} diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 5b754e8..141f5de 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -1,15 +1,31 @@ -import { DeviceUserPermissionEntity } from '../../device-user-permission/entities/device.user.permission.entity'; -import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'; -import { UserDto } from '../dtos'; +import { + Column, + DeleteDateColumn, + Entity, + ManyToOne, + OneToMany, + Unique, +} from 'typeorm'; +import { + UserDto, + UserNotificationDto, + UserOtpDto, + UserRoleDto, + UserSpaceDto, +} from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { UserSpaceEntity } from '../../user-space/entities'; -import { UserRoleEntity } from '../../user-role/entities'; -import { DeviceNotificationEntity } from '../../device-notification/entities'; -import { UserNotificationEntity } from '../../user-notification/entities'; -import { DeviceEntity } from '../../device/entities'; +import { + DeviceEntity, + DeviceNotificationEntity, + DeviceUserPermissionEntity, +} from '../../device/entities'; import { defaultProfilePicture } from '@app/common/constants/default.profile.picture'; import { RegionEntity } from '../../region/entities'; import { TimeZoneEntity } from '../../timezone/entities'; +import { OtpType } from '../../../../src/constants/otp-type.enum'; +import { RoleTypeEntity } from '../../role-type/entities'; +import { SpaceEntity } from '../../space/entities'; +import { VisitorPasswordEntity } from '../../visitor-password/entities'; @Entity({ name: 'user' }) export class UserEntity extends AbstractEntity { @@ -94,8 +110,112 @@ export class UserEntity extends AbstractEntity { nullable: true, }) timezone: TimeZoneEntity; + @OneToMany( + () => VisitorPasswordEntity, + (visitorPassword) => visitorPassword.user, + ) + public visitorPasswords: VisitorPasswordEntity[]; constructor(partial: Partial) { super(); Object.assign(this, partial); } } + +@Entity({ name: 'user-notification' }) +@Unique(['user', 'subscriptionUuid']) +export class UserNotificationEntity extends AbstractEntity { + @ManyToOne(() => UserEntity, (user) => user.roles, { + nullable: false, + }) + user: UserEntity; + @Column({ + nullable: false, + }) + subscriptionUuid: string; + + @Column({ + nullable: false, + default: true, + }) + active: boolean; + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} + +@Entity({ name: 'user-otp' }) +export class UserOtpEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', + nullable: false, + }) + public uuid: string; + + @Column({ nullable: false }) + email: string; + + @Column({ nullable: false }) + otpCode: string; + + @Column({ nullable: false }) + expiryTime: Date; + + @Column({ + type: 'enum', + enum: Object.values(OtpType), + }) + type: OtpType; + + @DeleteDateColumn({ nullable: true }) + deletedAt?: Date; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} + +@Entity({ name: 'user-role' }) +@Unique(['user', 'roleType']) +export class UserRoleEntity extends AbstractEntity { + @ManyToOne(() => UserEntity, (user) => user.roles, { + nullable: false, + }) + user: UserEntity; + + @ManyToOne(() => RoleTypeEntity, (roleType) => roleType.roles, { + nullable: false, + }) + roleType: RoleTypeEntity; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} + +@Entity({ name: 'user-space' }) +@Unique(['user', 'space']) +export class UserSpaceEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value + nullable: false, + }) + public uuid: string; + + @ManyToOne(() => UserEntity, (user) => user.userSpaces, { nullable: false }) + user: UserEntity; + + @ManyToOne(() => SpaceEntity, (space) => space.userSpaces, { + nullable: false, + }) + space: SpaceEntity; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/user/repositories/user.repository.ts b/libs/common/src/modules/user/repositories/user.repository.ts index f83f14f..ffc1aa1 100644 --- a/libs/common/src/modules/user/repositories/user.repository.ts +++ b/libs/common/src/modules/user/repositories/user.repository.ts @@ -1,6 +1,12 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; -import { UserEntity } from '../entities/user.entity'; +import { + UserEntity, + UserNotificationEntity, + UserOtpEntity, + UserRoleEntity, + UserSpaceEntity, +} from '../entities/'; @Injectable() export class UserRepository extends Repository { @@ -8,3 +14,31 @@ export class UserRepository extends Repository { super(UserEntity, dataSource.createEntityManager()); } } + +@Injectable() +export class UserNotificationRepository extends Repository { + constructor(private dataSource: DataSource) { + super(UserNotificationEntity, dataSource.createEntityManager()); + } +} + +@Injectable() +export class UserOtpRepository extends Repository { + constructor(private dataSource: DataSource) { + super(UserOtpEntity, dataSource.createEntityManager()); + } +} + +@Injectable() +export class UserRoleRepository extends Repository { + constructor(private dataSource: DataSource) { + super(UserRoleEntity, dataSource.createEntityManager()); + } +} + +@Injectable() +export class UserSpaceRepository extends Repository { + constructor(private dataSource: DataSource) { + super(UserSpaceEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/user/user.repository.module.ts b/libs/common/src/modules/user/user.repository.module.ts index 1b40c6a..11cefe0 100644 --- a/libs/common/src/modules/user/user.repository.module.ts +++ b/libs/common/src/modules/user/user.repository.module.ts @@ -1,11 +1,25 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { UserEntity } from './entities/user.entity'; +import { + UserEntity, + UserNotificationEntity, + UserOtpEntity, + UserRoleEntity, + UserSpaceEntity, +} from './entities'; @Module({ providers: [], exports: [], controllers: [], - imports: [TypeOrmModule.forFeature([UserEntity])], + imports: [ + TypeOrmModule.forFeature([ + UserEntity, + UserNotificationEntity, + UserOtpEntity, + UserRoleEntity, + UserSpaceEntity, + ]), + ], }) export class UserRepositoryModule {} diff --git a/libs/common/src/modules/visitor-password/dtos/index.ts b/libs/common/src/modules/visitor-password/dtos/index.ts new file mode 100644 index 0000000..daeb5d8 --- /dev/null +++ b/libs/common/src/modules/visitor-password/dtos/index.ts @@ -0,0 +1 @@ +export * from './visitor-password.dto'; diff --git a/libs/common/src/modules/device-notification/dtos/device.notification.dto.ts b/libs/common/src/modules/visitor-password/dtos/visitor-password.dto.ts similarity index 63% rename from libs/common/src/modules/device-notification/dtos/device.notification.dto.ts rename to libs/common/src/modules/visitor-password/dtos/visitor-password.dto.ts index 0746c14..10ed539 100644 --- a/libs/common/src/modules/device-notification/dtos/device.notification.dto.ts +++ b/libs/common/src/modules/visitor-password/dtos/visitor-password.dto.ts @@ -1,15 +1,15 @@ import { IsNotEmpty, IsString } from 'class-validator'; -export class DeviceNotificationDto { +export class VisitorPasswordDto { @IsString() @IsNotEmpty() public uuid: string; @IsString() @IsNotEmpty() - public userUuid: string; + public authorizerUuid: string; @IsString() @IsNotEmpty() - public deviceUuid: string; + public passwordTuyaUuid: string; } diff --git a/libs/common/src/modules/visitor-password/entities/index.ts b/libs/common/src/modules/visitor-password/entities/index.ts new file mode 100644 index 0000000..8b13691 --- /dev/null +++ b/libs/common/src/modules/visitor-password/entities/index.ts @@ -0,0 +1 @@ +export * from './visitor-password.entity'; diff --git a/libs/common/src/modules/visitor-password/entities/visitor-password.entity.ts b/libs/common/src/modules/visitor-password/entities/visitor-password.entity.ts new file mode 100644 index 0000000..f1c0ed6 --- /dev/null +++ b/libs/common/src/modules/visitor-password/entities/visitor-password.entity.ts @@ -0,0 +1,25 @@ +import { Column, Entity, ManyToOne, JoinColumn, Index } from 'typeorm'; +import { VisitorPasswordDto } from '../dtos'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { UserEntity } from '../../user/entities/user.entity'; + +@Entity({ name: 'visitor-password' }) +@Index('IDX_PASSWORD_TUYA_UUID', ['passwordTuyaUuid']) +export class VisitorPasswordEntity extends AbstractEntity { + @Column({ + nullable: false, + unique: true, + }) + public passwordTuyaUuid: string; + + @ManyToOne(() => UserEntity, (user) => user.visitorPasswords, { + nullable: false, + }) + @JoinColumn({ name: 'authorizer_uuid' }) + public user: UserEntity; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/visitor-password/repositories/index.ts b/libs/common/src/modules/visitor-password/repositories/index.ts new file mode 100644 index 0000000..2b62574 --- /dev/null +++ b/libs/common/src/modules/visitor-password/repositories/index.ts @@ -0,0 +1 @@ +export * from './visitor-password.repository'; diff --git a/libs/common/src/modules/visitor-password/repositories/visitor-password.repository.ts b/libs/common/src/modules/visitor-password/repositories/visitor-password.repository.ts new file mode 100644 index 0000000..4414a5b --- /dev/null +++ b/libs/common/src/modules/visitor-password/repositories/visitor-password.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { VisitorPasswordEntity } from '../entities/visitor-password.entity'; + +@Injectable() +export class VisitorPasswordRepository extends Repository { + constructor(private dataSource: DataSource) { + super(VisitorPasswordEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/visitor-password/visitor-password.repository.module.ts b/libs/common/src/modules/visitor-password/visitor-password.repository.module.ts new file mode 100644 index 0000000..b6a2182 --- /dev/null +++ b/libs/common/src/modules/visitor-password/visitor-password.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { VisitorPasswordEntity } from './entities/visitor-password.entity'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [TypeOrmModule.forFeature([VisitorPasswordEntity])], +}) +export class VisitorPasswordRepositoryModule {} diff --git a/libs/common/src/seed/seeder.module.ts b/libs/common/src/seed/seeder.module.ts index f148182..f63c05d 100644 --- a/libs/common/src/seed/seeder.module.ts +++ b/libs/common/src/seed/seeder.module.ts @@ -7,13 +7,12 @@ import { ConfigModule } from '@nestjs/config'; import { RoleTypeRepositoryModule } from '../modules/role-type/role.type.repository.module'; import { RoleTypeRepository } from '../modules/role-type/repositories'; import { RoleTypeSeeder } from './services/role.type.seeder'; -import { SpaceTypeRepository } from '../modules/space-type/repositories'; +import { SpaceTypeRepository } from '../modules/space/repositories'; import { SpaceTypeSeeder } from './services/space.type.seeder'; -import { SpaceTypeRepositoryModule } from '../modules/space-type/space.type.repository.module'; +import { SpaceRepositoryModule } from '../modules/space/space.repository.module'; import { SuperAdminSeeder } from './services/supper.admin.seeder'; import { UserRepository } from '../modules/user/repositories'; -import { UserRoleRepository } from '../modules/user-role/repositories'; -import { UserRoleRepositoryModule } from '../modules/user-role/user.role.repository.module'; +import { UserRoleRepository } from '../modules/user/repositories'; import { UserRepositoryModule } from '../modules/user/user.repository.module'; import { RegionSeeder } from './services/regions.seeder'; import { RegionRepository } from '../modules/region/repositories'; @@ -44,8 +43,7 @@ import { TimeZoneRepository } from '../modules/timezone/repositories'; PermissionTypeRepositoryModule, RoleTypeRepositoryModule, UserRepositoryModule, - UserRoleRepositoryModule, - SpaceTypeRepositoryModule, + SpaceRepositoryModule, ], }) export class SeederModule {} diff --git a/libs/common/src/seed/services/space.type.seeder.ts b/libs/common/src/seed/services/space.type.seeder.ts index 3cbcb87..56c2ee8 100644 --- a/libs/common/src/seed/services/space.type.seeder.ts +++ b/libs/common/src/seed/services/space.type.seeder.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { SpaceType } from '../../constants/space-type.enum'; -import { SpaceTypeRepository } from '../../modules/space-type/repositories'; +import { SpaceTypeRepository } from '../../modules/space/repositories'; @Injectable() export class SpaceTypeSeeder { diff --git a/libs/common/src/seed/services/supper.admin.seeder.ts b/libs/common/src/seed/services/supper.admin.seeder.ts index 6cb5f60..9cfdfee 100644 --- a/libs/common/src/seed/services/supper.admin.seeder.ts +++ b/libs/common/src/seed/services/supper.admin.seeder.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { UserRepository } from '@app/common/modules/user/repositories'; import { RoleType } from '@app/common/constants/role.type.enum'; -import { UserRoleRepository } from '@app/common/modules/user-role/repositories'; +import { UserRoleRepository } from '@app/common/modules/user/repositories'; import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; import { ConfigService } from '@nestjs/config'; import { HelperHashService } from '../../helper/services'; diff --git a/libs/common/src/util/email.service.ts b/libs/common/src/util/email.service.ts index 1b87334..f3a389d 100644 --- a/libs/common/src/util/email.service.ts +++ b/libs/common/src/util/email.service.ts @@ -19,7 +19,7 @@ export class EmailService { }; } - async sendOTPEmail( + async sendEmail( email: string, subject: string, message: string, diff --git a/package-lock.json b/package-lock.json index f2e66ed..db7b28d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "class-validator": "^0.14.1", "crypto-js": "^4.2.0", "express-rate-limit": "^7.1.5", + "firebase": "^10.12.5", "helmet": "^7.1.0", "ioredis": "^5.3.2", "morgan": "^1.10.0", @@ -1052,6 +1053,547 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@firebase/analytics": { + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.7.tgz", + "integrity": "sha512-GE29uTT6y/Jv2EP0OjpTezeTQZ5FTCTaZXKrrdVGjb/t35AU4u/jiU+hUwUPpuK8fqhhiHkS/AawE3a3ZK/a9Q==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/installations": "0.6.8", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.13.tgz", + "integrity": "sha512-aZ4wGfNDMsCxhKzDbK2g1aV0JKsdQ9FbeIsjpNJPzhahV0XYj+z36Y4RNLPpG/6hHU4gxnezxs+yn3HhHkNL8w==", + "dependencies": { + "@firebase/analytics": "0.10.7", + "@firebase/analytics-types": "0.8.2", + "@firebase/component": "0.6.8", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.2.tgz", + "integrity": "sha512-EnzNNLh+9/sJsimsA/FGqzakmrAUKLeJvjRHlg8df1f97NLUlFidk9600y0ZgWOp3CAxn6Hjtk+08tixlUOWyw==" + }, + "node_modules/@firebase/app": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.10.8.tgz", + "integrity": "sha512-xSLmW0/RShcnUEXH7l+wC0AFWaUtty4tUFF2loIgbtXTRmra0UH/SqYDf/IcfreUninRrCsusNmvoTidGkXJPw==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.7", + "idb": "7.1.1", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.7.tgz", + "integrity": "sha512-EkOeJcMKVR0zZ6z/jqcFTqHb/xq+TVIRIuBNGHdpcIuFU1czhSlegvqv2+nC+nFrkD8M6Xvd3tAlUOkdbMeS6A==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.14.tgz", + "integrity": "sha512-kK3bPfojAfXE53W+20rxMqIxrloFswXG9vh4kEdYL6Wa2IB3sD5++2dPiK3yGxl8oQiqS8qL2wcKB5/xLpEVEg==", + "dependencies": { + "@firebase/app-check": "0.8.7", + "@firebase/app-check-types": "0.5.2", + "@firebase/component": "0.6.8", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.2.tgz", + "integrity": "sha512-LMs47Vinv2HBMZi49C09dJxp0QT5LwDzFaVGf/+ITHe3BlIhUiLNttkATSXplc89A2lAaeTqjgqVkiRfUGyQiQ==" + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.2.tgz", + "integrity": "sha512-FSOEzTzL5bLUbD2co3Zut46iyPWML6xc4x+78TeaXMSuJap5QObfb+rVvZJtla3asN4RwU7elaQaduP+HFizDA==" + }, + "node_modules/@firebase/app-compat": { + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.38.tgz", + "integrity": "sha512-36ZrSvkYLW7QR01Sii2X+IY18ErMpRg6e2B2f/DVTtJBolthwXOnNBps+wvaVBvegdvdVPspgDXZUV0ppqh45w==", + "dependencies": { + "@firebase/app": "0.10.8", + "@firebase/component": "0.6.8", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.2.tgz", + "integrity": "sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ==" + }, + "node_modules/@firebase/auth": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.7.6.tgz", + "integrity": "sha512-T+lA5xoug9CByGYkD5WkfTh2ujEYq/frGZPbk0H+fNU6fNl7nqg88KcsmzsC6Fsqbjm3LLEb/i6wJvF6NSNEig==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0", + "undici": "5.28.4" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^1.18.1" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@firebase/auth-compat": { + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.11.tgz", + "integrity": "sha512-7rE3MkQDoWwI2qd8qsra4/QZCO2GzQSbCL6AVQpult9+Nbimg+5A+YeHxpLTcYAxUV6HDg2CqTDQreFLhcm1CQ==", + "dependencies": { + "@firebase/auth": "1.7.6", + "@firebase/auth-types": "0.12.2", + "@firebase/component": "0.6.8", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0", + "undici": "5.28.4" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.3.tgz", + "integrity": "sha512-Fc9wuJGgxoxQeavybiuwgyi+0rssr76b+nHpj+eGhXFYAdudMWyfBHvFL/I5fEHniUM/UQdFzi9VXJK2iZF7FQ==" + }, + "node_modules/@firebase/auth-types": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.12.2.tgz", + "integrity": "sha512-qsEBaRMoGvHO10unlDJhaKSuPn4pyoTtlQuP1ghZfzB6rNQPuhp/N/DcFZxm9i4v0SogjCbf9reWupwIvfmH6w==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.8.tgz", + "integrity": "sha512-LcNvxGLLGjBwB0dJUsBGCej2fqAepWyBubs4jt1Tiuns7QLbXHuyObZ4aMeBjZjWx4m8g1LoVI9QFpSaq/k4/g==", + "dependencies": { + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.7.tgz", + "integrity": "sha512-wjXr5AO8RPxVVg7rRCYffT7FMtBjHRfJ9KMwi19MbOf0vBf0H9YqW3WCgcnLpXI6ehiUcU3z3qgPnnU0nK6SnA==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.8", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.7", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.7.tgz", + "integrity": "sha512-R/3B+VVzEFN5YcHmfWns3eitA8fHLTL03io+FIoMcTYkajFnrBdS3A+g/KceN9omP7FYYYGTQWF9lvbEx6eMEg==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/database": "1.0.7", + "@firebase/database-types": "1.0.4", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.4.tgz", + "integrity": "sha512-mz9ZzbH6euFXbcBo+enuJ36I5dR5w+enJHHjy9Y5ThCdKUseqfDjW3vCp1YxE9zygFCSjJJ/z1cQ+zodvUcwPQ==", + "dependencies": { + "@firebase/app-types": "0.9.2", + "@firebase/util": "1.9.7" + } + }, + "node_modules/@firebase/firestore": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.6.5.tgz", + "integrity": "sha512-0+Ascaht4qUzj4pCopMPWmoAujk8HKjwCpaNYOOjbYMZ65RVfZPsfZwwbWi/zWMXj6xvPsai5oBiErUUkrLwNw==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.7", + "@firebase/webchannel-wrapper": "1.0.1", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "tslib": "^2.1.0", + "undici": "5.28.4" + }, + "engines": { + "node": ">=10.10.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.3.34", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.34.tgz", + "integrity": "sha512-OBP2F/Ccydl2U2j8XIfpKBxf0EnQHEhbZ4LTwbSS2QlG9+8TwhvKFkKk/ZljWYqaype+qFKPuXZ5flCqYEETeA==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/firestore": "4.6.5", + "@firebase/firestore-types": "3.0.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.2.tgz", + "integrity": "sha512-wp1A+t5rI2Qc/2q7r2ZpjUXkRVPtGMd6zCLsiWurjsQpqPgFin3AhNibKcIzoF2rnToNa/XYtyWXuifjOOwDgg==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/functions": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.11.6.tgz", + "integrity": "sha512-GPfIBPtpwQvsC7SQbgaUjLTdja0CsNwMoKSgrzA1FGGRk4NX6qO7VQU6XCwBiAFWbpbQex6QWkSMsCzLx1uibQ==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/auth-interop-types": "0.2.3", + "@firebase/component": "0.6.8", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0", + "undici": "5.28.4" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.12.tgz", + "integrity": "sha512-r3XUb5VlITWpML46JymfJPkK6I9j4SNlO7qWIXUc0TUmkv0oAfVoiIt1F83/NuMZXaGr4YWA/794nVSy4GV8tw==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/functions": "0.11.6", + "@firebase/functions-types": "0.6.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.2.tgz", + "integrity": "sha512-0KiJ9lZ28nS2iJJvimpY4nNccV21rkQyor5Iheu/nq8aKXJqtJdeSlZDspjPSBBiHRzo7/GMUttegnsEITqR+w==" + }, + "node_modules/@firebase/installations": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.8.tgz", + "integrity": "sha512-57V374qdb2+wT5v7+ntpLXBjZkO6WRgmAUbVkRfFTM/4t980p0FesbqTAcOIiM8U866UeuuuF8lYH70D3jM/jQ==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/util": "1.9.7", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.8.tgz", + "integrity": "sha512-pI2q8JFHB7yIq/szmhzGSWXtOvtzl6tCUmyykv5C8vvfOVJUH6mP4M4iwjbK8S1JotKd/K70+JWyYlxgQ0Kpyw==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/installations": "0.6.8", + "@firebase/installations-types": "0.5.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.2.tgz", + "integrity": "sha512-que84TqGRZJpJKHBlF2pkvc1YcXrtEDOVGiDjovP/a3s6W4nlbohGXEsBJo0JCeeg/UG9A+DEZVDUV9GpklUzA==", + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/logger": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.2.tgz", + "integrity": "sha512-Q1VuA5M1Gjqrwom6I6NUU4lQXdo9IAQieXlujeHZWvRt1b7qQ0KwBaNAjgxG27jgF9/mUwsNmO8ptBCGVYhB0A==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.12.10", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.10.tgz", + "integrity": "sha512-fGbxJPKpl2DIKNJGhbk4mYPcM+qE2gl91r6xPoiol/mN88F5Ym6UeRdMVZah+pijh9WxM55alTYwXuW40r1Y2Q==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/installations": "0.6.8", + "@firebase/messaging-interop-types": "0.2.2", + "@firebase/util": "1.9.7", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.10.tgz", + "integrity": "sha512-FXQm7rcowkDm8kFLduHV35IRYCRo+Ng0PIp/t1+EBuEbyplaKkGjZ932pE+owf/XR+G/60ku2QRBptRGLXZydg==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/messaging": "0.12.10", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.2.tgz", + "integrity": "sha512-l68HXbuD2PPzDUOFb3aG+nZj5KA3INcPwlocwLZOzPp9rFM9yeuI9YLl6DQfguTX5eAGxO0doTR+rDLDvQb5tA==" + }, + "node_modules/@firebase/performance": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.6.8.tgz", + "integrity": "sha512-F+alziiIZ6Yn8FG47mxwljq+4XkgkT2uJIFRlkyViUQRLzrogaUJW6u/+6ZrePXnouKlKIwzqos3PVJraPEcCA==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/installations": "0.6.8", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.8.tgz", + "integrity": "sha512-o7TFClRVJd3VIBoY7KZQqtCeW0PC6v9uBzM6Lfw3Nc9D7hM6OonqecYvh7NwJ6R14k+xM27frLS4BcCvFHKw2A==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/logger": "0.4.2", + "@firebase/performance": "0.6.8", + "@firebase/performance-types": "0.2.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.2.tgz", + "integrity": "sha512-gVq0/lAClVH5STrIdKnHnCo2UcPLjJlDUoEB/tB4KM+hAeHUxWKnpT0nemUPvxZ5nbdY/pybeyMe8Cs29gEcHA==" + }, + "node_modules/@firebase/remote-config": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.4.8.tgz", + "integrity": "sha512-AMLqe6wfIRnjc6FkCWOSUjhc1fSTEf8o+cv1NolFvbiJ/tU+TqN4pI7pT+MIKQzNiq5fxLehkOx+xtAQBxPJKQ==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/installations": "0.6.8", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.8.tgz", + "integrity": "sha512-UxSFOp6dzFj2AHB8Bq/BYtbq5iFyizKx4Rd6WxAdaKYM8cnPMeK+l2v+Oogtjae+AeyHRI+MfL2acsfVe5cd2A==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/logger": "0.4.2", + "@firebase/remote-config": "0.4.8", + "@firebase/remote-config-types": "0.3.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.3.2.tgz", + "integrity": "sha512-0BC4+Ud7y2aPTyhXJTMTFfrGGLqdYXrUB9sJVAB8NiqJswDTc4/2qrE/yfUbnQJhbSi6ZaTTBKyG3n1nplssaA==" + }, + "node_modules/@firebase/storage": { + "version": "0.12.6", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.12.6.tgz", + "integrity": "sha512-Zgb9WuehJxzhj7pGXUvkAEaH+3HvLjD9xSZ9nepuXf5f8378xME7oGJtREr/RnepdDA5YW0XIxe0QQBNHpe1nw==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0", + "undici": "5.28.4" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.9.tgz", + "integrity": "sha512-WWgAp5bTW961oIsCc9+98m4MIVKpEqztAlIngfHfwO/x3DYoBPRl/awMRG3CAXyVxG+7B7oHC5IsnqM+vTwx2A==", + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/storage": "0.12.6", + "@firebase/storage-types": "0.8.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.2.tgz", + "integrity": "sha512-0vWu99rdey0g53lA7IShoA2Lol1jfnPovzLDUBuon65K7uKG9G+L5uO05brD9pMw+l4HRFw23ah3GwTGpEav6g==", + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/util": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.7.tgz", + "integrity": "sha512-fBVNH/8bRbYjqlbIhZ+lBtdAAS4WqZumx03K06/u7fJSpz1TGjEMm1ImvKD47w+xaFKIP2ori6z8BrbakRfjJA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/vertexai-preview": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@firebase/vertexai-preview/-/vertexai-preview-0.0.3.tgz", + "integrity": "sha512-KVtUWLp+ScgiwkDKAvNkVucAyhLVQp6C6lhnVEuIg4mWhWcS3oerjAeVhZT4uNofKwWxRsOaB2Yec7DMTXlQPQ==", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.2", + "@firebase/component": "0.6.8", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.1.tgz", + "integrity": "sha512-jmEnr/pk0yVkA7mIlHNnxCi+wWzOFUg0WyIotgkKAb2u1J7fAeDBcVNSTjTihbAYNusCLQdW5s9IJ5qwnEufcQ==" + }, + "node_modules/@grpc/grpc-js": { + "version": "1.9.15", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", + "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", + "dependencies": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -1712,6 +2254,14 @@ "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==" }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.8.tgz", + "integrity": "sha512-qKwC/M/nNNaKUBMQ0nuzm47b7ZYWQHN3pcXq4IIcoSBc2hOIrflAxJduIvvqmhoz3gR2TacTAs8vlsCVPkiEdQ==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nestjs/cli": { "version": "10.3.2", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.3.2.tgz", @@ -1940,6 +2490,17 @@ } } }, + "node_modules/@nestjs/mongoose": { + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@nestjs/mongoose/-/mongoose-10.0.10.tgz", + "integrity": "sha512-3Ff60ock8nwlAJC823TG91Qy+Qc6av+ddIb6n6wlFsTK0akDF/aTcagX8cF8uI8mWxCWjEwEsgv99vo6p0yJ+w==", + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", + "mongoose": "^6.0.2 || ^7.0.0 || ^8.0.0", + "rxjs": "^7.0.0" + } + }, "node_modules/@nestjs/passport": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-10.0.3.tgz", @@ -2168,6 +2729,60 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -2520,6 +3135,22 @@ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.9.tgz", "integrity": "sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw==" }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "optional": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -3500,6 +4131,14 @@ "node-int64": "^0.4.0" } }, + "node_modules/bson": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz", + "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -5037,6 +5676,17 @@ "reusify": "^1.0.4" } }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -5140,6 +5790,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase": { + "version": "10.12.5", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.12.5.tgz", + "integrity": "sha512-J0yL3yh12CfFprTkSOQ9HqBugERyqvWwOuOoo1j1QHmYe9cYLKnBmtNCvGIYInDcsVUnJoRXCM+hxbGf48oVhg==", + "dependencies": { + "@firebase/analytics": "0.10.7", + "@firebase/analytics-compat": "0.2.13", + "@firebase/app": "0.10.8", + "@firebase/app-check": "0.8.7", + "@firebase/app-check-compat": "0.3.14", + "@firebase/app-compat": "0.2.38", + "@firebase/app-types": "0.9.2", + "@firebase/auth": "1.7.6", + "@firebase/auth-compat": "0.5.11", + "@firebase/database": "1.0.7", + "@firebase/database-compat": "1.0.7", + "@firebase/firestore": "4.6.5", + "@firebase/firestore-compat": "0.3.34", + "@firebase/functions": "0.11.6", + "@firebase/functions-compat": "0.3.12", + "@firebase/installations": "0.6.8", + "@firebase/installations-compat": "0.2.8", + "@firebase/messaging": "0.12.10", + "@firebase/messaging-compat": "0.2.10", + "@firebase/performance": "0.6.8", + "@firebase/performance-compat": "0.2.8", + "@firebase/remote-config": "0.4.8", + "@firebase/remote-config-compat": "0.2.8", + "@firebase/storage": "0.12.6", + "@firebase/storage-compat": "0.3.9", + "@firebase/util": "1.9.7", + "@firebase/vertexai-preview": "0.0.3" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -5696,6 +6380,11 @@ "node": ">= 0.8" } }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -5730,6 +6419,11 @@ "node": ">=0.10.0" } }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -5875,6 +6569,34 @@ "url": "https://opencollective.com/ioredis" } }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "optional": true, + "peer": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "optional": true, + "peer": true + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "optional": true, + "peer": true + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -6944,6 +7666,14 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -7024,6 +7754,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -7097,6 +7832,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -7168,6 +7908,11 @@ "node": ">= 4.0.0" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -7301,6 +8046,225 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mongodb": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", + "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==", + "optional": true, + "peer": true, + "dependencies": { + "bson": "^5.5.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "optionalDependencies": { + "@mongodb-js/saslprep": "^1.1.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.0.0", + "kerberos": "^1.0.0 || ^2.0.0", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "optional": true, + "peer": true, + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "optional": true, + "peer": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "optional": true, + "peer": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb/node_modules/bson": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", + "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==", + "optional": true, + "peer": true, + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongoose": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.5.1.tgz", + "integrity": "sha512-OhVcwVl91A1G6+XpjDcpkGP7l7ikZkxa0DylX7NT/lcEqAjggzSdqDxb48A+xsDxqNAr0ntSJ1yiE3+KJTOd5Q==", + "dependencies": { + "bson": "^6.7.0", + "kareem": "2.6.3", + "mongodb": "6.7.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/mongoose/node_modules/mongodb": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.7.0.tgz", + "integrity": "sha512-TMKyHdtMcO0fYBNORiYdmM25ijsHs+Njs963r4Tro4OQZzqYigAzYQouwWRg4OIaiLRUEGUh/1UAcH5lxdSLIA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.7.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongoose/node_modules/mongodb-connection-string-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", + "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mongoose/node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/mongoose/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/mongoose/node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -7340,6 +8304,25 @@ "node": ">= 0.8" } }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -8140,6 +9123,29 @@ "node": ">= 6" } }, + "node_modules/protobufjs": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.3.2.tgz", + "integrity": "sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -8950,6 +9956,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -8976,6 +9987,32 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "optional": true, + "peer": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -9004,6 +10041,14 @@ "node": ">=0.10.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/spawn-command": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", @@ -9951,6 +10996,17 @@ "node": ">=8" } }, + "node_modules/undici": { + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -10216,6 +11272,27 @@ "node": ">=4.0" } }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", diff --git a/package.json b/package.json index 991e9bf..4883516 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "class-validator": "^0.14.1", "crypto-js": "^4.2.0", "express-rate-limit": "^7.1.5", + "firebase": "^10.12.5", "helmet": "^7.1.0", "ioredis": "^5.3.2", "morgan": "^1.10.0", diff --git a/src/app.module.ts b/src/app.module.ts index 88c0550..2970185 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -23,6 +23,7 @@ import { LoggingInterceptor } from './interceptors/logging.interceptor'; 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'; @Module({ imports: [ ConfigModule.forRoot({ @@ -48,6 +49,7 @@ import { TimeZoneModule } from './timezone/timezone.module'; DoorLockModule, RegionModule, TimeZoneModule, + VisitorPasswordModule, ], controllers: [AuthenticationController], providers: [ diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 10515e7..012312d 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -2,14 +2,16 @@ 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 '../../libs/common/src/modules/user/user.repository.module'; +import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; import { CommonModule } from '../../libs/common/src'; import { UserAuthController } from './controllers'; import { UserAuthService } from './services'; -import { UserRepository } from '../../libs/common/src/modules/user/repositories'; -import { UserSessionRepository } from '../../libs/common/src/modules/session/repositories/session.repository'; -import { UserOtpRepository } from '../../libs/common/src/modules/user-otp/repositories/user-otp.repository'; -import { UserRoleRepository } from '@app/common/modules/user-role/repositories'; +import { UserRepository } from '@app/common/modules/user/repositories'; +import { UserSessionRepository } from '@app/common/modules/session/repositories/session.repository'; +import { + UserRoleRepository, + UserOtpRepository, +} from '@app/common/modules/user/repositories'; import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; @Module({ diff --git a/src/auth/controllers/user-auth.controller.ts b/src/auth/controllers/user-auth.controller.ts index 86f9ce6..e650d39 100644 --- a/src/auth/controllers/user-auth.controller.ts +++ b/src/auth/controllers/user-auth.controller.ts @@ -71,7 +71,7 @@ export class UserAuthController { return { statusCode: HttpStatus.OK, data: { - otp: otpCode, + ...otpCode, }, message: 'Otp Send Successfully', }; diff --git a/src/auth/dtos/user-auth.dto.ts b/src/auth/dtos/user-auth.dto.ts index 1d6e2a5..b934c4c 100644 --- a/src/auth/dtos/user-auth.dto.ts +++ b/src/auth/dtos/user-auth.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; +import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { IsPasswordStrong } from 'src/validators/password.validator'; export class UserSignUpDto { @@ -38,4 +38,8 @@ export class UserSignUpDto { @IsString() @IsNotEmpty() public lastName: string; + + @IsString() + @IsOptional() + public regionUuid: string; } diff --git a/src/auth/dtos/user-login.dto.ts b/src/auth/dtos/user-login.dto.ts index 6a14047..1f3662b 100644 --- a/src/auth/dtos/user-login.dto.ts +++ b/src/auth/dtos/user-login.dto.ts @@ -11,4 +11,9 @@ export class UserLoginDto { @IsString() @IsOptional() password: string; + + @ApiProperty() + @IsString() + @IsOptional() + regionUuid?: string; } diff --git a/src/auth/dtos/user-otp.dto.ts b/src/auth/dtos/user-otp.dto.ts index bab47c8..043eebc 100644 --- a/src/auth/dtos/user-otp.dto.ts +++ b/src/auth/dtos/user-otp.dto.ts @@ -1,6 +1,12 @@ import { OtpType } from '../../../libs/common/src/constants/otp-type.enum'; import { ApiProperty } from '@nestjs/swagger'; -import { IsEmail, IsEnum, IsNotEmpty, IsString } from 'class-validator'; +import { + IsEmail, + IsEnum, + IsNotEmpty, + IsOptional, + IsString, +} from 'class-validator'; export class UserOtpDto { @ApiProperty() @@ -12,6 +18,11 @@ export class UserOtpDto { @IsEnum(OtpType) @IsNotEmpty() type: OtpType; + + @ApiProperty() + @IsOptional() + @IsString() + regionUuid?: string; } export class VerifyOtpDto extends UserOtpDto { diff --git a/src/auth/services/user-auth.service.ts b/src/auth/services/user-auth.service.ts index a514c1c..7b539e2 100644 --- a/src/auth/services/user-auth.service.ts +++ b/src/auth/services/user-auth.service.ts @@ -1,5 +1,5 @@ import { RoleTypeRepository } from './../../../libs/common/src/modules/role-type/repositories/role.type.repository'; -import { UserRoleRepository } from './../../../libs/common/src/modules/user-role/repositories/user.role.repository'; +import { UserRoleRepository } from './../../../libs/common/src/modules/user/repositories/user.repository'; import { UserRepository } from '../../../libs/common/src/modules/user/repositories'; import { BadRequestException, @@ -12,12 +12,15 @@ import { HelperHashService } from '../../../libs/common/src/helper/services'; import { UserLoginDto } from '../dtos/user-login.dto'; import { AuthService } from '../../../libs/common/src/auth/services/auth.service'; import { UserSessionRepository } from '../../../libs/common/src/modules/session/repositories/session.repository'; -import { UserOtpRepository } from '../../../libs/common/src/modules/user-otp/repositories/user-otp.repository'; +import { UserOtpRepository } from '../../../libs/common/src/modules/user/repositories/user.repository'; import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos'; import { EmailService } from '../../../libs/common/src/util/email.service'; import { OtpType } from '../../../libs/common/src/constants/otp-type.enum'; import { UserEntity } from '../../../libs/common/src/modules/user/entities/user.entity'; import * as argon2 from 'argon2'; +import { differenceInSeconds } from '@app/common/helper/differenceInSeconds'; +import { LessThan, MoreThan } from 'typeorm'; +import { ConfigService } from '@nestjs/config'; @Injectable() export class UserAuthService { @@ -30,6 +33,7 @@ export class UserAuthService { private readonly emailService: EmailService, private readonly userRoleRepository: UserRoleRepository, private readonly roleTypeRepository: RoleTypeRepository, + private readonly configService: ConfigService, ) {} async signUp(userSignUpDto: UserSignUpDto): Promise { @@ -44,9 +48,17 @@ export class UserAuthService { ); try { + const { regionUuid, ...rest } = userSignUpDto; const user = await this.userRepository.save({ - ...userSignUpDto, + ...rest, password: hashedPassword, + region: regionUuid + ? { + uuid: regionUuid, + } + : { + regionName: 'United Arab Emirates', + }, }); return user; @@ -80,41 +92,46 @@ export class UserAuthService { } async userLogin(data: UserLoginDto) { - const user = await this.authService.validateUser(data.email, data.password); - - if (!user) { - throw new UnauthorizedException('Invalid login credentials.'); - } - - const session = await Promise.all([ - await this.sessionRepository.update( - { userId: user.id }, - { - isLoggedOut: true, - }, - ), - await this.authService.createSession({ + try { + const user = await this.authService.validateUser( + data.email, + data.password, + data.regionUuid, + ); + if (!user) { + throw new UnauthorizedException('Invalid login credentials.'); + } + const session = await Promise.all([ + await this.sessionRepository.update( + { userId: user.id }, + { + isLoggedOut: true, + }, + ), + await this.authService.createSession({ + userId: user.uuid, + loginTime: new Date(), + isLoggedOut: false, + }), + ]); + return await this.authService.login({ + email: user.email, userId: user.uuid, - loginTime: new Date(), - isLoggedOut: false, - }), - ]); - - return await this.authService.login({ - email: user.email, - userId: user.uuid, - uuid: user.uuid, - roles: user?.roles?.map((role) => { - return { uuid: role.uuid, type: role.roleType.type }; - }), - sessionId: session[1].uuid, - }); + uuid: user.uuid, + roles: user?.roles?.map((role) => { + return { uuid: role.uuid, type: role.roleType.type }; + }), + sessionId: session[1].uuid, + }); + } catch (error) { + throw new BadRequestException('Invalid credentials'); + } } async deleteUser(uuid: string) { const user = await this.findOneById(uuid); if (!user) { - throw new BadRequestException('User does not found'); + throw new BadRequestException('User not found'); } return await this.userRepository.update({ uuid }, { isActive: false }); } @@ -123,8 +140,61 @@ export class UserAuthService { return await this.userRepository.findOne({ where: { uuid: id } }); } - async generateOTP(data: UserOtpDto): Promise { - await this.otpRepository.delete({ email: data.email, type: data.type }); + async generateOTP(data: UserOtpDto): Promise<{ + otpCode: string; + cooldown: number; + }> { + const otpLimiter = new Date(); + otpLimiter.setDate( + otpLimiter.getDate() - this.configService.get('OTP_LIMITER'), + ); + const userExists = await this.userRepository.exists({ + where: { + region: data.regionUuid + ? { + uuid: data.regionUuid, + } + : undefined, + email: data.email, + isUserVerified: data.type === OtpType.PASSWORD ? true : undefined, + }, + }); + if (!userExists) { + throw new BadRequestException('User not found'); + } + await this.otpRepository.softDelete({ email: data.email, type: data.type }); + await this.otpRepository.delete({ + email: data.email, + type: data.type, + createdAt: LessThan(otpLimiter), + }); + const countOfOtp = await this.otpRepository.count({ + withDeleted: true, + where: { + email: data.email, + type: data.type, + createdAt: MoreThan(otpLimiter), + }, + }); + const lastOtp = await this.otpRepository.findOne({ + where: { email: data.email, type: data.type }, + order: { createdAt: 'DESC' }, + withDeleted: true, + }); + let cooldown = 30 * Math.pow(2, countOfOtp - 1); + if (lastOtp) { + const now = new Date(); + const timeSinceLastOtp = differenceInSeconds(now, lastOtp.createdAt); + + if (timeSinceLastOtp < cooldown) { + throw new BadRequestException({ + message: `Please wait ${cooldown - timeSinceLastOtp} more seconds before requesting a new OTP.`, + data: { + cooldown: cooldown - timeSinceLastOtp, + }, + }); + } + } const otpCode = Math.floor(100000 + Math.random() * 900000).toString(); const expiryTime = new Date(); expiryTime.setMinutes(expiryTime.getMinutes() + 1); @@ -134,10 +204,19 @@ export class UserAuthService { expiryTime, type: data.type, }); + const countOfOtpToReturn = await this.otpRepository.count({ + withDeleted: true, + where: { + email: data.email, + type: data.type, + createdAt: MoreThan(otpLimiter), + }, + }); + cooldown = 30 * Math.pow(2, countOfOtpToReturn - 1); const subject = 'OTP send successfully'; const message = `Your OTP code is ${otpCode}`; - this.emailService.sendOTPEmail(data.email, subject, message); - return otpCode; + this.emailService.sendEmail(data.email, subject, message); + return { otpCode, cooldown }; } async verifyOTP(data: VerifyOtpDto): Promise { diff --git a/src/automation/automation.module.ts b/src/automation/automation.module.ts index ebb8bf0..49c3b02 100644 --- a/src/automation/automation.module.ts +++ b/src/automation/automation.module.ts @@ -7,9 +7,10 @@ import { SpaceRepository } from '@app/common/modules/space/repositories'; import { DeviceService } from 'src/device/services'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { ProductRepository } from '@app/common/modules/product/repositories'; +import { DeviceStatusFirebaseModule } from '@app/common/firebase/devices-status/devices-status.module'; @Module({ - imports: [ConfigModule, SpaceRepositoryModule], + imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule], controllers: [AutomationController], providers: [ AutomationService, diff --git a/src/building/building.module.ts b/src/building/building.module.ts index 80391fe..a78c049 100644 --- a/src/building/building.module.ts +++ b/src/building/building.module.ts @@ -4,21 +4,13 @@ import { BuildingController } from './controllers/building.controller'; import { ConfigModule } from '@nestjs/config'; import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; -import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; -import { UserSpaceRepositoryModule } from '@app/common/modules/user-space/user.space.repository.module'; -import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; +import { SpaceTypeRepository } from '@app/common/modules/space/repositories'; +import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { UserRepository } from '@app/common/modules/user/repositories'; import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; @Module({ - imports: [ - ConfigModule, - SpaceRepositoryModule, - SpaceTypeRepositoryModule, - UserSpaceRepositoryModule, - UserRepositoryModule, - ], + imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], controllers: [BuildingController], providers: [ BuildingService, diff --git a/src/building/services/building.service.ts b/src/building/services/building.service.ts index dfc2089..c9f8273 100644 --- a/src/building/services/building.service.ts +++ b/src/building/services/building.service.ts @@ -1,5 +1,5 @@ import { GetBuildingChildDto } from '../dtos/get.building.dto'; -import { SpaceTypeRepository } from '../../../libs/common/src/modules/space-type/repositories/space.type.repository'; +import { SpaceTypeRepository } from '../../../libs/common/src/modules/space/repositories/space.repository'; import { Injectable, HttpException, @@ -17,7 +17,7 @@ import { } from '../interface/building.interface'; import { SpaceEntity } from '@app/common/modules/space/entities'; import { UpdateBuildingNameDto } from '../dtos/update.building.dto'; -import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; +import { UserSpaceRepository } from '@app/common/modules/user/repositories'; @Injectable() export class BuildingService { diff --git a/src/community/community.module.ts b/src/community/community.module.ts index e27627e..d5f7d93 100644 --- a/src/community/community.module.ts +++ b/src/community/community.module.ts @@ -4,22 +4,14 @@ import { CommunityController } from './controllers/community.controller'; import { ConfigModule } from '@nestjs/config'; import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; -import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; -import { UserSpaceRepositoryModule } from '@app/common/modules/user-space/user.space.repository.module'; -import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; +import { SpaceTypeRepository } from '@app/common/modules/space/repositories'; +import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; import { UserRepository } from '@app/common/modules/user/repositories'; import { SpacePermissionService } from '@app/common/helper/services'; @Module({ - imports: [ - ConfigModule, - SpaceRepositoryModule, - SpaceTypeRepositoryModule, - UserSpaceRepositoryModule, - UserRepositoryModule, - ], + imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], controllers: [CommunityController], providers: [ CommunityService, diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index c88acf1..af8d137 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -18,10 +18,10 @@ 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 { 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 { CommunityPermissionGuard } from 'src/guards/community.permission.guard'; +// import { CommunityPermissionGuard } from 'src/guards/community.permission.guard'; @ApiTags('Community Module') @Controller({ @@ -53,7 +53,7 @@ export class CommunityController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CommunityPermissionGuard) + @UseGuards(JwtAuthGuard) @Get(':communityUuid') async getCommunityByUuid(@Param('communityUuid') communityUuid: string) { try { @@ -67,9 +67,22 @@ export class CommunityController { ); } } - @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CommunityPermissionGuard) + @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, + ); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) @Get('child/:communityUuid') async getCommunityChildByUuid( @Param('communityUuid') communityUuid: string, @@ -103,7 +116,7 @@ export class CommunityController { } } @ApiBearerAuth() - @UseGuards(AdminRoleGuard, CheckUserCommunityGuard) + @UseGuards(AdminRoleGuard) @Post('user') async addUserCommunity(@Body() addUserCommunityDto: AddUserCommunityDto) { try { @@ -121,7 +134,7 @@ export class CommunityController { } } @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CommunityPermissionGuard) + @UseGuards(JwtAuthGuard) @Put('rename/:communityUuid') async renameCommunityByUuid( @Param('communityUuid') communityUuid: string, diff --git a/src/community/interface/community.interface.ts b/src/community/interface/community.interface.ts index 31c0579..e65a053 100644 --- a/src/community/interface/community.interface.ts +++ b/src/community/interface/community.interface.ts @@ -24,3 +24,12 @@ export interface GetCommunityByUserUuidInterface { name: string; type: string; } +export interface Community { + uuid: string; + createdAt: Date; + updatedAt: Date; + name: string; + type: string; +} + +export interface GetCommunitiesInterface extends Array {} diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index 66c7037..27d7bcf 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -1,5 +1,5 @@ import { GetCommunityChildDto } from './../dtos/get.community.dto'; -import { SpaceTypeRepository } from './../../../libs/common/src/modules/space-type/repositories/space.type.repository'; +import { SpaceTypeRepository } from './../../../libs/common/src/modules/space/repositories/space.repository'; import { Injectable, HttpException, @@ -10,13 +10,14 @@ import { SpaceRepository } from '@app/common/modules/space/repositories'; import { AddCommunityDto, AddUserCommunityDto } from '../dtos'; import { CommunityChildInterface, + GetCommunitiesInterface, GetCommunityByUserUuidInterface, GetCommunityByUuidInterface, RenameCommunityByUuidInterface, } from '../interface/community.interface'; import { SpaceEntity } from '@app/common/modules/space/entities'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; -import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; +import { UserSpaceRepository } from '@app/common/modules/user/repositories'; @Injectable() export class CommunityService { @@ -79,6 +80,23 @@ export class CommunityService { } } } + async getCommunities(): Promise { + try { + const community = await this.spaceRepository.find({ + where: { spaceType: { type: 'community' } }, + relations: ['spaceType'], + }); + return community.map((community) => ({ + uuid: community.uuid, + createdAt: community.createdAt, + updatedAt: community.updatedAt, + name: community.spaceName, + type: community.spaceType.type, + })); + } catch (err) { + throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } async getCommunityChildByUuid( communityUuid: string, getCommunityChildDto: GetCommunityChildDto, diff --git a/src/device-messages/device-messages.module.ts b/src/device-messages/device-messages.module.ts index 451f013..185d38b 100644 --- a/src/device-messages/device-messages.module.ts +++ b/src/device-messages/device-messages.module.ts @@ -2,11 +2,11 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { DeviceMessagesSubscriptionController } from './controllers'; import { DeviceMessagesSubscriptionService } from './services'; -import { DeviceNotificationRepositoryModule } from '@app/common/modules/device-notification/device.notification.module'; -import { DeviceNotificationRepository } from '@app/common/modules/device-notification/repositories'; +import { DeviceRepositoryModule } from '@app/common/modules/device/device.repository.module'; +import { DeviceNotificationRepository } from '@app/common/modules/device/repositories'; @Module({ - imports: [ConfigModule, DeviceNotificationRepositoryModule], + imports: [ConfigModule, DeviceRepositoryModule], controllers: [DeviceMessagesSubscriptionController], providers: [DeviceNotificationRepository, DeviceMessagesSubscriptionService], exports: [DeviceMessagesSubscriptionService], diff --git a/src/device-messages/services/device-messages.service.ts b/src/device-messages/services/device-messages.service.ts index 73c8182..4722b79 100644 --- a/src/device-messages/services/device-messages.service.ts +++ b/src/device-messages/services/device-messages.service.ts @@ -1,6 +1,6 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { DeviceMessagesAddDto } from '../dtos/device-messages.dto'; -import { DeviceNotificationRepository } from '@app/common/modules/device-notification/repositories'; +import { DeviceNotificationRepository } from '@app/common/modules/device/repositories'; @Injectable() export class DeviceMessagesSubscriptionService { diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index a54b5a9..bc399b6 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -14,7 +14,10 @@ import { } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { AddDeviceDto, UpdateDeviceInRoomDto } from '../dtos/add.device.dto'; -import { GetDeviceByRoomUuidDto } from '../dtos/get.device.dto'; +import { + GetDeviceByRoomUuidDto, + GetDeviceLogsDto, +} from '../dtos/get.device.dto'; import { ControlDeviceDto } from '../dtos/control.device.dto'; import { CheckRoomGuard } from 'src/guards/room.guard'; import { CheckUserHavePermission } from 'src/guards/user.device.permission.guard'; @@ -223,4 +226,33 @@ export class DeviceController { ); } } + @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, + ); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('report-logs/:deviceUuid') + async getBuildingChildByUuid( + @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, + ); + } + } } diff --git a/src/device/device.module.ts b/src/device/device.module.ts index 07d1ad0..5d17318 100644 --- a/src/device/device.module.ts +++ b/src/device/device.module.ts @@ -8,10 +8,16 @@ import { DeviceRepositoryModule } from '@app/common/modules/device'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { DeviceUserPermissionRepository } from '@app/common/modules/device-user-permission/repositories'; +import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { UserRepository } from '@app/common/modules/user/repositories'; +import { DeviceStatusFirebaseModule } from '@app/common/firebase/devices-status/devices-status.module'; @Module({ - imports: [ConfigModule, ProductRepositoryModule, DeviceRepositoryModule], + imports: [ + ConfigModule, + ProductRepositoryModule, + DeviceRepositoryModule, + DeviceStatusFirebaseModule, + ], controllers: [DeviceController], providers: [ DeviceService, diff --git a/src/device/dtos/get.device.dto.ts b/src/device/dtos/get.device.dto.ts index 26002bb..99c0dad 100644 --- a/src/device/dtos/get.device.dto.ts +++ b/src/device/dtos/get.device.dto.ts @@ -10,3 +10,12 @@ export class GetDeviceByRoomUuidDto { @IsNotEmpty() public roomUuid: string; } +export class GetDeviceLogsDto { + @ApiProperty({ + description: 'code', + required: true, + }) + @IsString() + @IsNotEmpty() + public code: string; +} diff --git a/src/device/interfaces/get.device.interface.ts b/src/device/interfaces/get.device.interface.ts index 526c199..880a976 100644 --- a/src/device/interfaces/get.device.interface.ts +++ b/src/device/interfaces/get.device.interface.ts @@ -64,3 +64,9 @@ export interface updateDeviceFirmwareInterface { result: boolean; msg: string; } +export interface getDeviceLogsInterface { + data: []; + startTime: number; + endTime: number; + deviceUuid?: string; +} diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 0848bb3..0641d6f 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -15,9 +15,13 @@ import { GetDeviceDetailsFunctionsStatusInterface, GetDeviceDetailsInterface, controlDeviceInterface, + getDeviceLogsInterface, updateDeviceFirmwareInterface, } from '../interfaces/get.device.interface'; -import { GetDeviceByRoomUuidDto } from '../dtos/get.device.dto'; +import { + GetDeviceByRoomUuidDto, + GetDeviceLogsDto, +} from '../dtos/get.device.dto'; import { ControlDeviceDto } from '../dtos/control.device.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { DeviceRepository } from '@app/common/modules/device/repositories'; @@ -25,6 +29,7 @@ import { PermissionType } from '@app/common/constants/permission-type.enum'; import { In } from 'typeorm'; import { ProductType } from '@app/common/constants/product-type.enum'; import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service'; @Injectable() export class DeviceService { @@ -33,6 +38,7 @@ export class DeviceService { private readonly configService: ConfigService, private readonly deviceRepository: DeviceRepository, private readonly productRepository: ProductRepository, + private readonly deviceStatusFirebaseService: DeviceStatusFirebaseService, private readonly spaceRepository: SpaceRepository, ) { const accessKey = this.configService.get('auth-config.ACCESS_KEY'); @@ -72,14 +78,28 @@ export class DeviceService { if (!device.productUuid) { throw new Error('Product UUID is missing for the device.'); } - - return await this.deviceRepository.save({ + const deviceSaved = await this.deviceRepository.save({ deviceTuyaUuid: addDeviceDto.deviceTuyaUuid, productDevice: { uuid: device.productUuid }, user: { uuid: addDeviceDto.userUuid, }, }); + if (deviceSaved.uuid) { + const deviceStatus = await this.getDevicesInstructionStatus( + deviceSaved.uuid, + ); + if (deviceStatus.productUuid) { + await this.deviceStatusFirebaseService.addDeviceStatusToFirebase({ + deviceUuid: deviceSaved.uuid, + deviceTuyaUuid: addDeviceDto.deviceTuyaUuid, + status: deviceStatus.status, + productUuid: deviceStatus.productUuid, + productType: deviceStatus.productType, + }); + } + } + return deviceSaved; } catch (error) { if (error.code === '23505') { throw new HttpException( @@ -604,4 +624,122 @@ export class DeviceService { ); } } + async getAllDevices(): Promise { + try { + const devices = await this.deviceRepository.find({ + relations: [ + 'spaceDevice.parent', + 'productDevice', + 'permission', + 'permission.permissionType', + ], + }); + const devicesData = await Promise.allSettled( + devices.map(async (device) => { + let battery = null; + + // Check if the device is a door lock (DL) + if (device.productDevice.prodType === ProductType.DL) { + const doorLockInstructionsStatus = + await this.getDevicesInstructionStatus(device.uuid); + + const batteryStatus: any = doorLockInstructionsStatus.status.find( + (status: any) => status.code === 'residual_electricity', + ); + + if (batteryStatus) { + battery = batteryStatus.value; + } + } + + const spaceDevice = device?.spaceDevice; + const parentDevice = spaceDevice?.parent; + return { + room: { + uuid: spaceDevice?.uuid, + name: spaceDevice?.spaceName, + }, + unit: { + uuid: parentDevice?.uuid, + name: parentDevice?.spaceName, + }, + productUuid: device.productDevice.uuid, + productType: device.productDevice.prodType, + permissionType: device.permission[0].permissionType.type, + ...(await this.getDeviceDetailsByDeviceIdTuya( + device.deviceTuyaUuid, + )), + uuid: device.uuid, + ...(battery && { battery }), + } as GetDeviceDetailsInterface; + }), + ); + + // Filter out rejected promises and extract the fulfilled values + const fulfilledDevices = devicesData + .filter((result) => result.status === 'fulfilled') + .map( + (result) => + (result as PromiseFulfilledResult).value, + ); + + return fulfilledDevices; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getDeviceLogs(deviceUuid: string, query: GetDeviceLogsDto) { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + + if (!deviceDetails) { + throw new NotFoundException('Device Not Found'); + } + + const response = await this.getDeviceLogsTuya( + deviceDetails.deviceTuyaUuid, + query.code, + ); + + return { + deviceUuid, + ...response, + }; + } catch (error) { + throw new HttpException( + error.message || 'Device Not Found', + HttpStatus.NOT_FOUND, + ); + } + } + async getDeviceLogsTuya( + deviceId: string, + code: string, + ): Promise { + try { + const now = Date.now(); + const oneHourAgo = now - 1 * 60 * 60 * 1000; + const path = `/v2.0/cloud/thing/${deviceId}/report-logs?start_time=${oneHourAgo}&end_time=${now}&codes=${code}&size=50`; + const response = await this.tuya.request({ + method: 'GET', + path, + }); + // Convert keys to camel case + const camelCaseResponse = convertKeysToCamelCase(response); + const logs = camelCaseResponse.result.logs ?? []; + return { + startTime: oneHourAgo, + endTime: now, + data: logs, + } as getDeviceLogsInterface; + } catch (error) { + throw new HttpException( + 'Error fetching device logs from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/door-lock/controllers/door.lock.controller.ts b/src/door-lock/controllers/door.lock.controller.ts index 44b9276..56ba2b8 100644 --- a/src/door-lock/controllers/door.lock.controller.ts +++ b/src/door-lock/controllers/door.lock.controller.ts @@ -113,7 +113,7 @@ export class DoorLockController { @Param('doorLockUuid') doorLockUuid: string, ) { try { - return await this.doorLockService.getOnlineTemporaryPasswords( + return await this.doorLockService.getOnlineTemporaryPasswordsMultiple( doorLockUuid, ); } catch (error) { @@ -211,4 +211,23 @@ export class DoorLockController { ); } } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('open/:doorLockUuid') + async openDoorLock(@Param('doorLockUuid') doorLockUuid: string) { + try { + 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, + ); + } + } } diff --git a/src/door-lock/door.lock.module.ts b/src/door-lock/door.lock.module.ts index 3b1920e..b72fad9 100644 --- a/src/door-lock/door.lock.module.ts +++ b/src/door-lock/door.lock.module.ts @@ -5,10 +5,24 @@ import { ConfigModule } from '@nestjs/config'; import { DeviceRepositoryModule } from '@app/common/modules/device'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { PasswordEncryptionService } from './services/encryption.services'; +import { VisitorPasswordRepository } from '@app/common/modules/visitor-password/repositories'; +import { DeviceService } from 'src/device/services'; +import { ProductRepository } from '@app/common/modules/product/repositories'; +import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; @Module({ imports: [ConfigModule, DeviceRepositoryModule], controllers: [DoorLockController], - providers: [DoorLockService, PasswordEncryptionService, DeviceRepository], + providers: [ + DoorLockService, + PasswordEncryptionService, + DeviceRepository, + VisitorPasswordRepository, + DeviceService, + ProductRepository, + DeviceStatusFirebaseService, + SpaceRepository, + ], exports: [DoorLockService], }) export class DoorLockModule {} diff --git a/src/door-lock/services/door.lock.service.ts b/src/door-lock/services/door.lock.service.ts index a5892a6..5864c5d 100644 --- a/src/door-lock/services/door.lock.service.ts +++ b/src/door-lock/services/door.lock.service.ts @@ -1,4 +1,9 @@ -import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { + Injectable, + HttpException, + HttpStatus, + BadRequestException, +} from '@nestjs/common'; import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { ConfigService } from '@nestjs/config'; import { @@ -16,6 +21,9 @@ import { PasswordEncryptionService } from './encryption.services'; import { AddDoorLockOfflineTempMultipleTimeDto } from '../dtos/add.offline-temp.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; 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'; @Injectable() export class DoorLockService { @@ -23,7 +31,9 @@ export class DoorLockService { constructor( private readonly configService: ConfigService, private readonly deviceRepository: DeviceRepository, + private readonly deviceService: DeviceService, private readonly passwordEncryptionService: PasswordEncryptionService, + private readonly visitorPasswordRepository: VisitorPasswordRepository, ) { const accessKey = this.configService.get('auth-config.ACCESS_KEY'); const secretKey = this.configService.get('auth-config.SECRET_KEY'); @@ -83,7 +93,11 @@ export class DoorLockService { ); } } - async getOfflineMultipleTimeTemporaryPasswords(doorLockUuid: string) { + async getOfflineMultipleTimeTemporaryPasswords( + doorLockUuid: string, + fromVisitor?: boolean, + isExpired?: boolean, + ) { try { const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid); @@ -95,15 +109,53 @@ export class DoorLockService { HttpStatus.BAD_REQUEST, ); } + const deviceTuyaDetails = + await this.deviceService.getDeviceDetailsByDeviceIdTuya( + deviceDetails.deviceTuyaUuid, + ); const passwords = await this.getTemporaryOfflinePasswordsTuya( deviceDetails.deviceTuyaUuid, 'multiple', + isExpired, ); - - if (passwords.result.records.length > 0) { - return convertKeysToCamelCase(passwords.result.records); + if (!passwords.result.records.length && fromVisitor) { + throw new BadRequestException(); + } + if (passwords.result.records.length > 0) { + return fromVisitor + ? Promise.all( + convertKeysToCamelCase(passwords.result.records).map( + async (password) => { + const passwordFromDB = await this.getVisitorPasswordFromDB( + password.pwdId, + ); + const timestampInSeconds = Math.floor(Date.now() / 1000); + return { + passwordId: `${password.pwdId}`, + invalidTime: `${password.gmtExpired}`, + effectiveTime: `${password.gmtStart}`, + createdTime: `${password.gmtCreate}`, + passwordName: password.pwdName, + passwordType: 'OFFLINE_MULTIPLE', + deviceUuid: doorLockUuid, + deviceName: deviceTuyaDetails.name, + passwordStatus: isExpired + ? 'EXPIRED' + : timestampInSeconds > password.gmtStart + ? 'EFFECTIVE' + : 'TO_BE_EFFECTIVE', + authorizerEmail: passwordFromDB + ? passwordFromDB.user.email + : 'OTHER', + authorizerDate: passwordFromDB + ? `${Math.floor(new Date(passwordFromDB.createdAt).getTime() / 1000)}` + : '', + }; + }, + ), + ) + : convertKeysToCamelCase(passwords.result.records); } - return passwords; } catch (error) { throw new HttpException( @@ -113,10 +165,13 @@ export class DoorLockService { ); } } - async getOfflineOneTimeTemporaryPasswords(doorLockUuid: string) { + async getOfflineOneTimeTemporaryPasswords( + doorLockUuid: string, + fromVisitor?: boolean, + isExpired?: boolean, + ) { try { const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid); - if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); } else if (deviceDetails.productDevice.prodType !== ProductType.DL) { @@ -125,13 +180,53 @@ export class DoorLockService { HttpStatus.BAD_REQUEST, ); } + const deviceTuyaDetails = + await this.deviceService.getDeviceDetailsByDeviceIdTuya( + deviceDetails.deviceTuyaUuid, + ); const passwords = await this.getTemporaryOfflinePasswordsTuya( deviceDetails.deviceTuyaUuid, 'once', + isExpired, ); - + if (!passwords.result.records.length && fromVisitor) { + throw new BadRequestException(); + } if (passwords.result.records.length > 0) { - return convertKeysToCamelCase(passwords.result.records); + return fromVisitor + ? Promise.all( + convertKeysToCamelCase(passwords.result.records).map( + async (password) => { + const passwordFromDB = await this.getVisitorPasswordFromDB( + password.pwdId, + ); + + const timestampInSeconds = Math.floor(Date.now() / 1000); + return { + passwordId: `${password.pwdId}`, + invalidTime: `${password.gmtExpired}`, + effectiveTime: `${password.gmtStart}`, + createdTime: `${password.gmtCreate}`, + passwordName: password.pwdName, + passwordType: 'OFFLINE_ONETIME', + deviceUuid: doorLockUuid, + deviceName: deviceTuyaDetails.name, + passwordStatus: isExpired + ? 'EXPIRED' + : timestampInSeconds > password.gmtStart + ? 'EFFECTIVE' + : 'TO_BE_EFFECTIVE', + authorizerEmail: passwordFromDB + ? passwordFromDB.user.email + : 'OTHER', + authorizerDate: passwordFromDB + ? `${Math.floor(new Date(passwordFromDB.createdAt).getTime() / 1000)}` + : '', + }; + }, + ), + ) + : convertKeysToCamelCase(passwords.result.records); } return passwords; @@ -142,7 +237,11 @@ export class DoorLockService { ); } } - async getOnlineTemporaryPasswords(doorLockUuid: string) { + async getOnlineTemporaryPasswordsMultiple( + doorLockUuid: string, + fromVisitor?: boolean, + isExpired?: boolean, + ) { try { const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid); @@ -154,11 +253,15 @@ export class DoorLockService { HttpStatus.BAD_REQUEST, ); } - const passwords = await this.getOnlineTemporaryPasswordsTuya( + const deviceTuyaDetails = + await this.deviceService.getDeviceDetailsByDeviceIdTuya( + deviceDetails.deviceTuyaUuid, + ); + const passwords = await this.getOnlineTemporaryPasswordsMultipleTuya( deviceDetails.deviceTuyaUuid, + isExpired, ); - - if (passwords.result.length > 0) { + if (passwords.result?.length > 0) { const passwordFiltered = passwords.result .filter((item) => item.type === 0) .map((password: any) => { @@ -181,9 +284,42 @@ export class DoorLockService { return password; }); - return convertKeysToCamelCase(passwordFiltered); + return fromVisitor + ? Promise.all( + convertKeysToCamelCase(passwordFiltered).map(async (password) => { + const passwordFromDB = await this.getVisitorPasswordFromDB( + password.id, + ); + const timestampInSeconds = Math.floor(Date.now() / 1000); + return { + passwordId: `${password.id}`, + invalidTime: `${password.invalidTime}`, + effectiveTime: `${password.effectiveTime}`, + createdTime: '', + scheduleList: password?.scheduleList, + passwordName: password.name, + passwordType: 'ONLINE_MULTIPLE', + deviceUuid: doorLockUuid, + deviceName: deviceTuyaDetails.name, + passwordStatus: isExpired + ? 'EXPIRED' + : timestampInSeconds > password.effectiveTime + ? 'EFFECTIVE' + : 'TO_BE_EFFECTIVE', + authorizerEmail: passwordFromDB + ? passwordFromDB.user.email + : 'OTHER', + authorizerDate: passwordFromDB + ? `${Math.floor(new Date(passwordFromDB.createdAt).getTime() / 1000)}` + : '', + }; + }), + ) + : convertKeysToCamelCase(passwordFiltered); + } + if (fromVisitor) { + throw new BadRequestException(); } - return passwords; } catch (error) { throw new HttpException( @@ -192,11 +328,128 @@ export class DoorLockService { ); } } - async getOnlineTemporaryPasswordsTuya( + async getVisitorPasswordFromDB(passwordId: string) { + try { + return await this.visitorPasswordRepository.findOne({ + where: { + passwordTuyaUuid: passwordId, + }, + relations: ['user'], + select: { + user: { + email: true, + }, + }, + }); + } catch (error) { + throw new HttpException( + error.message || 'Error retrieving visitor password from database', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getOnlineTemporaryPasswordsOneTime( doorLockUuid: string, + fromVisitor?: boolean, + isExpired?: boolean, + ) { + try { + // Retrieve device details + const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid); + + if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } else if (deviceDetails.productDevice.prodType !== ProductType.DL) { + throw new HttpException( + 'This is not a door lock device', + HttpStatus.BAD_REQUEST, + ); + } + const deviceTuyaDetails = + await this.deviceService.getDeviceDetailsByDeviceIdTuya( + deviceDetails.deviceTuyaUuid, + ); + // Get online temporary passwords + const passwords = await this.getOnlineTemporaryPasswordsMultipleTuya( + deviceDetails.deviceTuyaUuid, + isExpired, + ); + if (passwords.result?.length > 0) { + // Filter and map the passwords + const passwordFiltered = passwords.result + .filter((item) => item.type === 1) + .map((password: any) => { + if (password.schedule_list?.length > 0) { + password.schedule_list = password.schedule_list.map( + (schedule) => { + schedule.working_day = this.getDaysFromWorkingDayValue( + schedule.working_day, + ); + schedule.effective_time = this.minutesToTime( + schedule.effective_time, + ); + schedule.invalid_time = this.minutesToTime( + schedule.invalid_time, + ); + return schedule; + }, + ); + } + return password; + }); + + // Process passwords with optimized query handling + return fromVisitor + ? Promise.all( + convertKeysToCamelCase(passwordFiltered).map(async (password) => { + const passwordFromDB = await this.getVisitorPasswordFromDB( + password.id, + ); + const timestampInSeconds = Math.floor(Date.now() / 1000); + return { + passwordId: `${password.id}`, + invalidTime: `${password.invalidTime}`, + effectiveTime: `${password.effectiveTime}`, + createdTime: '', + passwordName: password.name, + passwordType: 'ONLINE_ONETIME', + deviceUuid: doorLockUuid, + deviceName: deviceTuyaDetails.name, + passwordStatus: isExpired + ? 'EXPIRED' + : timestampInSeconds > password.effectiveTime + ? 'EFFECTIVE' + : 'TO_BE_EFFECTIVE', + authorizerEmail: passwordFromDB + ? passwordFromDB.user.email + : 'OTHER', + authorizerDate: passwordFromDB + ? `${Math.floor(new Date(passwordFromDB.createdAt).getTime() / 1000)}` + : '', + }; + }), + ) + : convertKeysToCamelCase(passwordFiltered); + } + if (fromVisitor) { + throw new BadRequestException(); + } + return passwords; + } catch (error) { + throw new HttpException( + error.message || 'Error getting online temporary passwords', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getOnlineTemporaryPasswordsMultipleTuya( + doorLockUuid: string, + isExpired?: boolean, ): Promise { try { - const path = `/v1.0/devices/${doorLockUuid}/door-lock/temp-passwords?valid=true`; + const path = `/v1.0/devices/${doorLockUuid}/door-lock/temp-passwords?valid=${!isExpired}`; const response = await this.tuya.request({ method: 'GET', @@ -214,9 +467,10 @@ export class DoorLockService { async getTemporaryOfflinePasswordsTuya( doorLockUuid: string, type: string, + isExpired?: boolean, ): Promise { try { - const path = `/v1.0/devices/${doorLockUuid}/door-lock/offline-temp-password?pwd_type_codes=${type}&target_status=EFFECTIVE&page_no=1&page_size=100`; + const path = `/v1.0/devices/${doorLockUuid}/door-lock/offline-temp-password?pwd_type_codes=${type}&target_status=${isExpired ? 'INEFFECTIVE' : 'EFFECTIVE'}&page_no=1&page_size=100`; const response = await this.tuya.request({ method: 'GET', @@ -639,4 +893,56 @@ export class DoorLockService { ); } } + async openDoorLock( + doorLockUuid: string, + ): Promise { + try { + // Fetch ticket and encrypted password + const { ticketKey, encryptedPassword, deviceTuyaUuid, ticketId } = + await this.getTicketAndEncryptedPassword( + doorLockUuid, + defaultDoorLockPass, + ); + + // Validate ticket and device Tuya UUID + if (!ticketKey || !encryptedPassword || !deviceTuyaUuid) { + throw new HttpException( + 'Invalid Ticket or Device UUID', + HttpStatus.NOT_FOUND, + ); + } + + // Retrieve device details + const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid); + + // Validate device details + if (!deviceDetails?.deviceTuyaUuid) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } + + if (deviceDetails.productDevice.prodType !== ProductType.DL) { + throw new HttpException( + 'This is not a door lock device', + HttpStatus.BAD_REQUEST, + ); + } + + // Construct the path for the Tuya API request + const path = `/v1.0/devices/${deviceDetails.deviceTuyaUuid}/door-lock/password-free/open-door`; + + // Make the Tuya API request to open the door + const response = await this.tuya.request({ + method: 'POST', + path, + body: { ticket_id: ticketId }, + }); + + return response as deleteTemporaryPasswordInterface; + } catch (error) { + const status = error.status || HttpStatus.INTERNAL_SERVER_ERROR; + const message = error.message || 'Error opening door lock from Tuya'; + + throw new HttpException(message, status); + } + } } diff --git a/src/floor/floor.module.ts b/src/floor/floor.module.ts index 71a6c67..9fdc1c7 100644 --- a/src/floor/floor.module.ts +++ b/src/floor/floor.module.ts @@ -4,21 +4,13 @@ import { FloorController } from './controllers/floor.controller'; import { ConfigModule } from '@nestjs/config'; import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; -import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; -import { UserSpaceRepositoryModule } from '@app/common/modules/user-space/user.space.repository.module'; -import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; +import { SpaceTypeRepository } from '@app/common/modules/space/repositories'; +import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; import { UserRepository } from '@app/common/modules/user/repositories'; @Module({ - imports: [ - ConfigModule, - SpaceRepositoryModule, - SpaceTypeRepositoryModule, - UserSpaceRepositoryModule, - UserRepositoryModule, - ], + imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], controllers: [FloorController], providers: [ FloorService, diff --git a/src/floor/services/floor.service.ts b/src/floor/services/floor.service.ts index af4b26f..5113206 100644 --- a/src/floor/services/floor.service.ts +++ b/src/floor/services/floor.service.ts @@ -1,5 +1,5 @@ import { GetFloorChildDto } from '../dtos/get.floor.dto'; -import { SpaceTypeRepository } from '../../../libs/common/src/modules/space-type/repositories/space.type.repository'; +import { SpaceTypeRepository } from '../../../libs/common/src/modules/space/repositories/space.repository'; import { Injectable, HttpException, @@ -17,7 +17,7 @@ import { } from '../interface/floor.interface'; import { SpaceEntity } from '@app/common/modules/space/entities'; import { UpdateFloorNameDto } from '../dtos/update.floor.dto'; -import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; +import { UserSpaceRepository } from '@app/common/modules/user/repositories'; @Injectable() export class FloorService { diff --git a/src/region/controllers/region.controller.ts b/src/region/controllers/region.controller.ts index cea276a..d914642 100644 --- a/src/region/controllers/region.controller.ts +++ b/src/region/controllers/region.controller.ts @@ -1,13 +1,6 @@ -import { - Controller, - Get, - HttpException, - HttpStatus, - UseGuards, -} from '@nestjs/common'; +import { Controller, Get, HttpException, HttpStatus } from '@nestjs/common'; import { RegionService } from '../services/region.service'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; -import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; +import { ApiTags } from '@nestjs/swagger'; @ApiTags('Region Module') @Controller({ @@ -17,8 +10,6 @@ import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; export class RegionController { constructor(private readonly regionService: RegionService) {} - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) @Get() async getAllRegions() { try { diff --git a/src/role/role.module.ts b/src/role/role.module.ts index a487979..4e51725 100644 --- a/src/role/role.module.ts +++ b/src/role/role.module.ts @@ -4,10 +4,10 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { RoleService } from './services/role.service'; import { RoleController } from './controllers/role.controller'; -import { DeviceUserPermissionRepository } from '@app/common/modules/device-user-permission/repositories'; +import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; -import { UserRoleRepository } from '@app/common/modules/user-role/repositories'; +import { UserRoleRepository } from '@app/common/modules/user/repositories'; @Module({ imports: [ConfigModule, DeviceRepositoryModule], diff --git a/src/role/services/role.service.ts b/src/role/services/role.service.ts index ece3780..42076d4 100644 --- a/src/role/services/role.service.ts +++ b/src/role/services/role.service.ts @@ -1,7 +1,7 @@ import { RoleTypeRepository } from './../../../libs/common/src/modules/role-type/repositories/role.type.repository'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { AddUserRoleDto } from '../dtos/role.add.dto'; -import { UserRoleRepository } from '@app/common/modules/user-role/repositories'; +import { UserRoleRepository } from '@app/common/modules/user/repositories'; import { QueryFailedError } from 'typeorm'; @Injectable() diff --git a/src/room/room.module.ts b/src/room/room.module.ts index 2d6d98c..4a07d1a 100644 --- a/src/room/room.module.ts +++ b/src/room/room.module.ts @@ -4,21 +4,13 @@ import { RoomController } from './controllers/room.controller'; import { ConfigModule } from '@nestjs/config'; import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; -import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; -import { UserSpaceRepositoryModule } from '@app/common/modules/user-space/user.space.repository.module'; -import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; +import { SpaceTypeRepository } from '@app/common/modules/space/repositories'; +import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; import { UserRepository } from '@app/common/modules/user/repositories'; @Module({ - imports: [ - ConfigModule, - SpaceRepositoryModule, - SpaceTypeRepositoryModule, - UserSpaceRepositoryModule, - UserRepositoryModule, - ], + imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], controllers: [RoomController], providers: [ RoomService, diff --git a/src/room/services/room.service.ts b/src/room/services/room.service.ts index b9ea30e..7babd6b 100644 --- a/src/room/services/room.service.ts +++ b/src/room/services/room.service.ts @@ -1,4 +1,4 @@ -import { SpaceTypeRepository } from '../../../libs/common/src/modules/space-type/repositories/space.type.repository'; +import { SpaceTypeRepository } from '../../../libs/common/src/modules/space/repositories/space.repository'; import { Injectable, HttpException, @@ -14,7 +14,7 @@ import { GetRoomByUserUuidInterface, } from '../interface/room.interface'; import { UpdateRoomNameDto } from '../dtos/update.room.dto'; -import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; +import { UserSpaceRepository } from '@app/common/modules/user/repositories'; @Injectable() export class RoomService { diff --git a/src/scene/scene.module.ts b/src/scene/scene.module.ts index 248f2e6..c673748 100644 --- a/src/scene/scene.module.ts +++ b/src/scene/scene.module.ts @@ -7,9 +7,10 @@ import { SpaceRepository } from '@app/common/modules/space/repositories'; import { DeviceService } from 'src/device/services'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { ProductRepository } from '@app/common/modules/product/repositories'; +import { DeviceStatusFirebaseModule } from '@app/common/firebase/devices-status/devices-status.module'; @Module({ - imports: [ConfigModule, SpaceRepositoryModule], + imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule], controllers: [SceneController], providers: [ SceneService, diff --git a/src/unit/services/unit.service.ts b/src/unit/services/unit.service.ts index 1106043..b5dfb8f 100644 --- a/src/unit/services/unit.service.ts +++ b/src/unit/services/unit.service.ts @@ -1,5 +1,5 @@ import { GetUnitChildDto } from '../dtos/get.unit.dto'; -import { SpaceTypeRepository } from '../../../libs/common/src/modules/space-type/repositories/space.type.repository'; +import { SpaceTypeRepository } from '../../../libs/common/src/modules/space/repositories/space.repository'; import { Injectable, HttpException, @@ -18,7 +18,7 @@ import { } from '../interface/unit.interface'; import { SpaceEntity } from '@app/common/modules/space/entities'; import { UpdateUnitNameDto } from '../dtos/update.unit.dto'; -import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; +import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { generateRandomString } from '@app/common/helper/randomString'; import { UserDevicePermissionService } from 'src/user-device-permission/services'; import { PermissionType } from '@app/common/constants/permission-type.enum'; diff --git a/src/unit/unit.module.ts b/src/unit/unit.module.ts index 7924e4a..7ecd965 100644 --- a/src/unit/unit.module.ts +++ b/src/unit/unit.module.ts @@ -4,24 +4,16 @@ import { UnitController } from './controllers/unit.controller'; import { ConfigModule } from '@nestjs/config'; import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; import { SpaceRepository } from '@app/common/modules/space/repositories'; -import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module'; -import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories'; -import { UserSpaceRepositoryModule } from '@app/common/modules/user-space/user.space.repository.module'; -import { UserSpaceRepository } from '@app/common/modules/user-space/repositories'; +import { SpaceTypeRepository } from '@app/common/modules/space/repositories'; +import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; import { UserRepository } from '@app/common/modules/user/repositories'; import { UserDevicePermissionService } from 'src/user-device-permission/services'; -import { DeviceUserPermissionRepository } from '@app/common/modules/device-user-permission/repositories'; +import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; @Module({ - imports: [ - ConfigModule, - SpaceRepositoryModule, - SpaceTypeRepositoryModule, - UserSpaceRepositoryModule, - UserRepositoryModule, - ], + imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], controllers: [UnitController], providers: [ UnitService, diff --git a/src/user-device-permission/services/user-device-permission.service.ts b/src/user-device-permission/services/user-device-permission.service.ts index c1f3d07..9ec93e6 100644 --- a/src/user-device-permission/services/user-device-permission.service.ts +++ b/src/user-device-permission/services/user-device-permission.service.ts @@ -1,7 +1,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { UserDevicePermissionAddDto } from '../dtos/user-device-permission.add.dto'; import { UserDevicePermissionEditDto } from '../dtos/user-device-permission.edit.dto'; -import { DeviceUserPermissionRepository } from '@app/common/modules/device-user-permission/repositories'; +import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; @Injectable() diff --git a/src/user-device-permission/user-device-permission.module.ts b/src/user-device-permission/user-device-permission.module.ts index e2a8b46..53b7377 100644 --- a/src/user-device-permission/user-device-permission.module.ts +++ b/src/user-device-permission/user-device-permission.module.ts @@ -4,7 +4,7 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { UserDevicePermissionService } from './services/user-device-permission.service'; import { UserDevicePermissionController } from './controllers/user-device-permission.controller'; -import { DeviceUserPermissionRepository } from '@app/common/modules/device-user-permission/repositories'; +import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; @Module({ diff --git a/src/user-notification/services/user-notification.service.ts b/src/user-notification/services/user-notification.service.ts index d8992c0..8c9616b 100644 --- a/src/user-notification/services/user-notification.service.ts +++ b/src/user-notification/services/user-notification.service.ts @@ -3,7 +3,7 @@ import { UserNotificationAddDto, UserNotificationUpdateDto, } from '../dtos/user-notification.dto'; -import { UserNotificationRepository } from '@app/common/modules/user-notification/repositories'; +import { UserNotificationRepository } from '@app/common/modules/user/repositories'; @Injectable() export class UserNotificationService { diff --git a/src/user-notification/user-notification.module.ts b/src/user-notification/user-notification.module.ts index 0b03df1..da564cf 100644 --- a/src/user-notification/user-notification.module.ts +++ b/src/user-notification/user-notification.module.ts @@ -1,12 +1,12 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { UserNotificationRepositoryModule } from '@app/common/modules/user-notification/user.notification.repository.module'; -import { UserNotificationRepository } from '@app/common/modules/user-notification/repositories'; +import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module'; +import { UserNotificationRepository } from '@app/common/modules/user/repositories'; import { UserNotificationService } from 'src/user-notification/services'; import { UserNotificationController } from 'src/user-notification/controllers'; @Module({ - imports: [ConfigModule, UserNotificationRepositoryModule], + imports: [ConfigModule, UserRepositoryModule], controllers: [UserNotificationController], providers: [UserNotificationRepository, UserNotificationService], exports: [UserNotificationService], diff --git a/src/vistor-password/controllers/index.ts b/src/vistor-password/controllers/index.ts new file mode 100644 index 0000000..ca4b986 --- /dev/null +++ b/src/vistor-password/controllers/index.ts @@ -0,0 +1 @@ +export * from './visitor-password.controller'; diff --git a/src/vistor-password/controllers/visitor-password.controller.ts b/src/vistor-password/controllers/visitor-password.controller.ts new file mode 100644 index 0000000..bdd4324 --- /dev/null +++ b/src/vistor-password/controllers/visitor-password.controller.ts @@ -0,0 +1,161 @@ +import { VisitorPasswordService } from '../services/visitor-password.service'; +import { + Body, + Controller, + Post, + HttpException, + HttpStatus, + UseGuards, + Get, + Req, +} from '@nestjs/common'; +import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { + AddDoorLockOfflineMultipleDto, + AddDoorLockOfflineOneTimeDto, + AddDoorLockOnlineMultipleDto, + AddDoorLockOnlineOneTimeDto, +} from '../dtos/temp-pass.dto'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; + +@ApiTags('Visitor Password Module') +@Controller({ + version: '1', + path: 'visitor-password', +}) +export class VisitorPasswordController { + constructor( + private readonly visitorPasswordService: VisitorPasswordService, + ) {} + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('temporary-password/online/multiple-time') + async addOnlineTemporaryPasswordMultipleTime( + @Body() addDoorLockOnlineMultipleDto: AddDoorLockOnlineMultipleDto, + @Req() req: any, + ) { + try { + const userUuid = req.user.uuid; + const temporaryPasswords = + await this.visitorPasswordService.addOnlineTemporaryPasswordMultipleTime( + addDoorLockOnlineMultipleDto, + userUuid, + ); + + return { + statusCode: HttpStatus.CREATED, + data: temporaryPasswords, + }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('temporary-password/online/one-time') + async addOnlineTemporaryPassword( + @Body() addDoorLockOnlineOneTimeDto: AddDoorLockOnlineOneTimeDto, + @Req() req: any, + ) { + try { + const userUuid = req.user.uuid; + const temporaryPasswords = + await this.visitorPasswordService.addOnlineTemporaryPasswordOneTime( + addDoorLockOnlineOneTimeDto, + userUuid, + ); + + return { + statusCode: HttpStatus.CREATED, + data: temporaryPasswords, + }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('temporary-password/offline/one-time') + async addOfflineOneTimeTemporaryPassword( + @Body() addDoorLockOfflineOneTimeDto: AddDoorLockOfflineOneTimeDto, + @Req() req: any, + ) { + try { + const userUuid = req.user.uuid; + const temporaryPassword = + await this.visitorPasswordService.addOfflineOneTimeTemporaryPassword( + addDoorLockOfflineOneTimeDto, + userUuid, + ); + + return { + statusCode: HttpStatus.CREATED, + data: temporaryPassword, + }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('temporary-password/offline/multiple-time') + async addOfflineMultipleTimeTemporaryPassword( + @Body() + addDoorLockOfflineMultipleDto: AddDoorLockOfflineMultipleDto, + @Req() req: any, + ) { + try { + const userUuid = req.user.uuid; + const temporaryPassword = + await this.visitorPasswordService.addOfflineMultipleTimeTemporaryPassword( + addDoorLockOfflineMultipleDto, + userUuid, + ); + + return { + statusCode: HttpStatus.CREATED, + data: temporaryPassword, + }; + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get() + async GetVisitorPassword() { + try { + return await this.visitorPasswordService.getPasswords(); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get('/devices') + async GetVisitorDevices() { + try { + return await this.visitorPasswordService.getAllPassDevices(); + } catch (error) { + throw new HttpException( + error.message || 'Internal server error', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/vistor-password/dtos/index.ts b/src/vistor-password/dtos/index.ts new file mode 100644 index 0000000..aa0f905 --- /dev/null +++ b/src/vistor-password/dtos/index.ts @@ -0,0 +1 @@ +export * from './temp-pass.dto'; diff --git a/src/vistor-password/dtos/temp-pass.dto.ts b/src/vistor-password/dtos/temp-pass.dto.ts new file mode 100644 index 0000000..0590935 --- /dev/null +++ b/src/vistor-password/dtos/temp-pass.dto.ts @@ -0,0 +1,213 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IsNotEmpty, + IsString, + IsArray, + ValidateNested, + IsEnum, + Length, + IsOptional, + IsEmail, +} from 'class-validator'; +import { Type } from 'class-transformer'; +import { WorkingDays } from '@app/common/constants/working-days'; + +class ScheduleDto { + @ApiProperty({ + description: 'effectiveTime', + required: true, + }) + @IsString() + @IsNotEmpty() + public effectiveTime: string; + + @ApiProperty({ + description: 'invalidTime', + required: true, + }) + @IsString() + @IsNotEmpty() + public invalidTime: string; + + @ApiProperty({ + description: 'workingDay', + enum: WorkingDays, + isArray: true, + required: true, + }) + @IsArray() + @IsEnum(WorkingDays, { each: true }) + @IsNotEmpty() + public workingDay: WorkingDays[]; +} + +export class AddDoorLockOnlineMultipleDto { + @ApiProperty({ + description: 'email', + required: true, + }) + @IsEmail() + @IsNotEmpty() + public email: string; + @ApiProperty({ + description: 'devicesUuid', + required: true, + }) + @IsArray() + @IsNotEmpty() + public devicesUuid: [string]; + + @ApiProperty({ + description: 'password name', + required: true, + }) + @IsString() + @IsNotEmpty() + public passwordName: string; + @ApiProperty({ + description: 'password', + required: true, + }) + @IsString() + @IsNotEmpty() + @Length(7, 7) + public password: string; + + @ApiProperty({ + description: 'effectiveTime', + required: true, + }) + @IsString() + @IsNotEmpty() + public effectiveTime: string; + + @ApiProperty({ + description: 'invalidTime', + required: true, + }) + @IsString() + @IsNotEmpty() + public invalidTime: string; + + @ApiProperty({ + description: 'scheduleList', + type: [ScheduleDto], + required: false, + }) + @IsArray() + @ValidateNested({ each: true }) + @IsOptional() + @Type(() => ScheduleDto) + public scheduleList: ScheduleDto[]; +} +export class AddDoorLockOnlineOneTimeDto { + @ApiProperty({ + description: 'email', + required: true, + }) + @IsEmail() + @IsNotEmpty() + public email: string; + @ApiProperty({ + description: 'devicesUuid', + required: true, + }) + @IsArray() + @IsNotEmpty() + public devicesUuid: [string]; + + @ApiProperty({ + description: 'password name', + required: true, + }) + @IsString() + @IsNotEmpty() + public passwordName: string; + @ApiProperty({ + description: 'password', + required: true, + }) + @IsString() + @IsNotEmpty() + @Length(7, 7) + public password: string; + + @ApiProperty({ + description: 'effectiveTime', + required: true, + }) + @IsString() + @IsNotEmpty() + public effectiveTime: string; + + @ApiProperty({ + description: 'invalidTime', + required: true, + }) + @IsString() + @IsNotEmpty() + public invalidTime: string; +} +export class AddDoorLockOfflineOneTimeDto { + @ApiProperty({ + description: 'email', + required: true, + }) + @IsEmail() + @IsNotEmpty() + public email: string; + @ApiProperty({ + description: 'password name', + required: true, + }) + @IsString() + @IsNotEmpty() + public passwordName: string; + @ApiProperty({ + description: 'devicesUuid', + required: true, + }) + @IsArray() + @IsNotEmpty() + public devicesUuid: [string]; +} +export class AddDoorLockOfflineMultipleDto { + @ApiProperty({ + description: 'email', + required: true, + }) + @IsEmail() + @IsNotEmpty() + public email: string; + @ApiProperty({ + description: 'devicesUuid', + required: true, + }) + @IsArray() + @IsNotEmpty() + public devicesUuid: [string]; + + @ApiProperty({ + description: 'password name', + required: true, + }) + @IsString() + @IsNotEmpty() + public passwordName: string; + + @ApiProperty({ + description: 'effectiveTime', + required: true, + }) + @IsString() + @IsNotEmpty() + public effectiveTime: string; + + @ApiProperty({ + description: 'invalidTime', + required: true, + }) + @IsString() + @IsNotEmpty() + public invalidTime: string; +} diff --git a/src/vistor-password/interfaces/visitor-password.interface.ts b/src/vistor-password/interfaces/visitor-password.interface.ts new file mode 100644 index 0000000..6dda2eb --- /dev/null +++ b/src/vistor-password/interfaces/visitor-password.interface.ts @@ -0,0 +1,66 @@ +import { WorkingDays } from '@app/common/constants/working-days'; + +export interface createTickInterface { + success: boolean; + result: { + expire_time: number; + ticket_id: string; + ticket_key: string; + id?: number; + offline_temp_password_id?: string; + offline_temp_password?: string; + }; + msg?: string; +} +export interface addDeviceObjectInterface { + passwordName: string; + encryptedPassword: string; + effectiveTime: string; + invalidTime: string; + ticketId: string; + scheduleList?: any[]; +} + +export interface ScheduleDto { + effectiveTime: string; + invalidTime: string; + workingDay: WorkingDays[]; +} + +export interface AddDoorLockOnlineInterface { + passwordName: string; + password: string; + effectiveTime: string; + invalidTime: string; + scheduleList?: ScheduleDto[]; +} +export interface getPasswordInterface { + success: boolean; + result: [ + { + effective_time: number; + id: number; + invalid_time: number; + passwordName: string; + phase: number; + phone: string; + schedule_list?: []; + sn: number; + time_zone: string; + type: number; + }, + ]; + msg?: string; +} +export interface deleteTemporaryPasswordInterface { + success: boolean; + result: boolean; + msg?: string; +} +export interface getPasswordOfflineInterface { + success: boolean; + result: { + records: []; + }; + msg?: string; +} diff --git a/src/vistor-password/services/index.ts b/src/vistor-password/services/index.ts new file mode 100644 index 0000000..14d8814 --- /dev/null +++ b/src/vistor-password/services/index.ts @@ -0,0 +1 @@ +export * from './visitor-password.service'; diff --git a/src/vistor-password/services/visitor-password.service.ts b/src/vistor-password/services/visitor-password.service.ts new file mode 100644 index 0000000..f15b8e4 --- /dev/null +++ b/src/vistor-password/services/visitor-password.service.ts @@ -0,0 +1,861 @@ +import { VisitorPasswordRepository } from './../../../libs/common/src/modules/visitor-password/repositories/visitor-password.repository'; +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { TuyaContext } from '@tuya/tuya-connector-nodejs'; +import { ConfigService } from '@nestjs/config'; +import { + addDeviceObjectInterface, + createTickInterface, +} from '../interfaces/visitor-password.interface'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { ProductType } from '@app/common/constants/product-type.enum'; + +import { + AddDoorLockOfflineMultipleDto, + AddDoorLockOfflineOneTimeDto, + AddDoorLockOnlineMultipleDto, + AddDoorLockOnlineOneTimeDto, +} from '../dtos'; +import { EmailService } from '@app/common/util/email.service'; +import { PasswordEncryptionService } from 'src/door-lock/services/encryption.services'; +import { DoorLockService } from 'src/door-lock/services'; +import { GetDeviceDetailsInterface } from 'src/device/interfaces/get.device.interface'; +import { DeviceService } from 'src/device/services'; + +@Injectable() +export class VisitorPasswordService { + private tuya: TuyaContext; + constructor( + private readonly configService: ConfigService, + private readonly deviceRepository: DeviceRepository, + private readonly visitorPasswordRepository: VisitorPasswordRepository, + private readonly emailService: EmailService, + private readonly doorLockService: DoorLockService, + private readonly deviceService: DeviceService, + private readonly passwordEncryptionService: PasswordEncryptionService, + ) { + const accessKey = this.configService.get('auth-config.ACCESS_KEY'); + const secretKey = this.configService.get('auth-config.SECRET_KEY'); + const tuyaEuUrl = this.configService.get('tuya-config.TUYA_EU_URL'); + this.tuya = new TuyaContext({ + baseUrl: tuyaEuUrl, + accessKey, + secretKey, + }); + } + + async addOfflineMultipleTimeTemporaryPassword( + addDoorLockOfflineMultipleDto: AddDoorLockOfflineMultipleDto, + userUuid: string, + ) { + try { + const deviceResults = await Promise.allSettled( + addDoorLockOfflineMultipleDto.devicesUuid.map(async (deviceUuid) => { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + + if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } + + if (deviceDetails.productDevice.prodType !== ProductType.DL) { + throw new HttpException( + 'This is not a door lock device', + HttpStatus.BAD_REQUEST, + ); + } + + const createMultipleOfflinePass = + await this.addOfflineTemporaryPasswordTuya( + deviceDetails.deviceTuyaUuid, + 'multiple', + addDoorLockOfflineMultipleDto, + addDoorLockOfflineMultipleDto.passwordName, + ); + + if (!createMultipleOfflinePass.success) { + throw new HttpException( + createMultipleOfflinePass.msg, + HttpStatus.BAD_REQUEST, + ); + } + + // Send email if the password creation is successful + if (createMultipleOfflinePass.result.offline_temp_password_id) { + const emailSubject = 'One-Time Password Creation Success'; + const emailBody = `Your multiple time offline temporary password "${createMultipleOfflinePass.result.offline_temp_password}" has been successfully created for the device with UUID: ${deviceUuid}.\n\nThank you for using our service.\n\nBest Regards,\nSyncrow`; + + await this.emailService.sendEmail( + addDoorLockOfflineMultipleDto.email, + emailSubject, + emailBody, + ); + } + if (createMultipleOfflinePass.result.offline_temp_password_id) { + await this.visitorPasswordRepository.save({ + passwordTuyaUuid: `${createMultipleOfflinePass.result.offline_temp_password_id}`, + user: { + uuid: userUuid, + }, + }); + } + return { + success: true, + result: createMultipleOfflinePass.result, + deviceUuid, + }; + } catch (error) { + return { + success: false, + deviceUuid, + error: error.message || 'Error processing device', + }; + } + }), + ); + + // Process results: separate successful and failed operations + const successfulResults = deviceResults + .filter( + (result) => + result.status === 'fulfilled' && + (result as PromiseFulfilledResult).value.success, + ) + .map((result) => (result as PromiseFulfilledResult).value); + + const failedResults = deviceResults + .filter( + (result) => + result.status === 'rejected' || + !(result as PromiseFulfilledResult).value.success, + ) + .map((result) => + result.status === 'rejected' + ? { + success: false, + error: + (result as PromiseRejectedResult).reason.message || + 'Error processing device', + } + : (result as PromiseFulfilledResult).value, + ); + + // Return results if there are successful operations + if (successfulResults.length > 0) { + return { + successOperations: successfulResults, + failedOperations: failedResults, + }; + } + + // If all failed, throw an error with all failure messages + throw new HttpException( + failedResults.map((res) => res.error).join('; ') || + 'All device operations failed', + HttpStatus.BAD_REQUEST, + ); + } catch (error) { + console.error(error); + + throw new HttpException( + error.message || + 'Error adding offline multiple-time temporary password from Tuya', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async addOfflineOneTimeTemporaryPassword( + addDoorLockOfflineOneTimeDto: AddDoorLockOfflineOneTimeDto, + userUuid: string, + ) { + try { + const deviceResults = await Promise.allSettled( + addDoorLockOfflineOneTimeDto.devicesUuid.map(async (deviceUuid) => { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(deviceUuid); + + if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } + + if (deviceDetails.productDevice.prodType !== ProductType.DL) { + throw new HttpException( + 'This is not a door lock device', + HttpStatus.BAD_REQUEST, + ); + } + + const createOnceOfflinePass = + await this.addOfflineTemporaryPasswordTuya( + deviceDetails.deviceTuyaUuid, + 'once', + null, + addDoorLockOfflineOneTimeDto.passwordName, + ); + + if (!createOnceOfflinePass.success) { + throw new HttpException( + createOnceOfflinePass.msg, + HttpStatus.BAD_REQUEST, + ); + } + + // Send email if the password creation is successful + if (createOnceOfflinePass.result.offline_temp_password_id) { + const emailSubject = 'One-Time Password Creation Success'; + const emailBody = `Your one-time offline temporary password "${createOnceOfflinePass.result.offline_temp_password}" has been successfully created for the device with UUID: ${deviceUuid}.\n\nThank you for using our service.\n\nBest Regards,\nSyncrow`; + + await this.emailService.sendEmail( + addDoorLockOfflineOneTimeDto.email, + emailSubject, + emailBody, + ); + } + if (createOnceOfflinePass.result.offline_temp_password_id) { + await this.visitorPasswordRepository.save({ + passwordTuyaUuid: `${createOnceOfflinePass.result.offline_temp_password_id}`, + user: { + uuid: userUuid, + }, + }); + } + return { + success: true, + result: createOnceOfflinePass.result, + deviceUuid, + }; + } catch (error) { + return { + success: false, + deviceUuid, + error: error.message || 'Error processing device', + }; + } + }), + ); + + // Process results: separate successful and failed operations + const successfulResults = deviceResults + .filter( + (result) => + result.status === 'fulfilled' && + (result as PromiseFulfilledResult).value.success, + ) + .map((result) => (result as PromiseFulfilledResult).value); + + const failedResults = deviceResults + .filter( + (result) => + result.status === 'rejected' || + !(result as PromiseFulfilledResult).value.success, + ) + .map((result) => + result.status === 'rejected' + ? { + success: false, + error: + (result as PromiseRejectedResult).reason.message || + 'Error processing device', + } + : (result as PromiseFulfilledResult).value, + ); + + // Return results if there are successful operations + if (successfulResults.length > 0) { + return { + successOperations: successfulResults, + failedOperations: failedResults, + }; + } + + // If all failed, throw an error with all failure messages + throw new HttpException( + failedResults.map((res) => res.error).join('; ') || + 'All device operations failed', + HttpStatus.BAD_REQUEST, + ); + } catch (error) { + console.error(error); + + throw new HttpException( + error.message || + 'Error adding offline one-time temporary password from Tuya', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async addOfflineTemporaryPasswordTuya( + doorLockUuid: string, + type: string, + addDoorLockOfflineMultipleDto: AddDoorLockOfflineMultipleDto, + passwordName: string, + ): Promise { + try { + const path = `/v1.1/devices/${doorLockUuid}/door-lock/offline-temp-password`; + + const response = await this.tuya.request({ + method: 'POST', + path, + body: { + ...(type === 'multiple' && { + effective_time: addDoorLockOfflineMultipleDto.effectiveTime, + invalid_time: addDoorLockOfflineMultipleDto.invalidTime, + }), + name: passwordName, + type, + }, + }); + + return response as createTickInterface; + } catch (error) { + throw new HttpException( + 'Error adding offline temporary password from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async addOnlineTemporaryPasswordMultipleTime( + addDoorLockOnlineMultipleDto: AddDoorLockOnlineMultipleDto, + userUuid: string, + ) { + try { + const deviceResults = await Promise.allSettled( + addDoorLockOnlineMultipleDto.devicesUuid.map(async (deviceUuid) => { + try { + const passwordData = await this.getTicketAndEncryptedPassword( + deviceUuid, + addDoorLockOnlineMultipleDto.password, + ); + + if ( + !passwordData.ticketKey || + !passwordData.encryptedPassword || + !passwordData.deviceTuyaUuid + ) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } + + const addDeviceObj: addDeviceObjectInterface = { + ...addDoorLockOnlineMultipleDto, + ...passwordData, + }; + + const createPass = + await this.addOnlineTemporaryPasswordMultipleTuya( + addDeviceObj, + passwordData.deviceTuyaUuid, + ); + + if (!createPass.success) { + throw new HttpException(createPass.msg, HttpStatus.BAD_REQUEST); + } + + // Send email if the password creation is successful + if (createPass.result.id) { + const emailSubject = 'Password Creation Success'; + const emailBody = `Your temporary password "${addDoorLockOnlineMultipleDto.password}" has been successfully created for the device with UUID: ${deviceUuid}.\n\nThank you for using our service.\n\nBest Regards,\nSyncrow`; + + await this.emailService.sendEmail( + addDoorLockOnlineMultipleDto.email, + emailSubject, + emailBody, + ); + } + if (createPass.result.id) { + await this.visitorPasswordRepository.save({ + passwordTuyaUuid: `${createPass.result.id}`, + user: { + uuid: userUuid, + }, + }); + } + return { + success: true, + id: createPass.result.id, + deviceUuid, + }; + } catch (error) { + return { + success: false, + deviceUuid, + error: error.message || 'Error processing device', + }; + } + }), + ); + + // Process results: separate successful and failed operations + const successfulResults = deviceResults + .filter( + (result) => + result.status === 'fulfilled' && + (result as PromiseFulfilledResult).value.success, + ) + .map((result) => (result as PromiseFulfilledResult).value); + + const failedResults = deviceResults + .filter( + (result) => + result.status === 'rejected' || + !(result as PromiseFulfilledResult).value.success, + ) + .map((result) => + result.status === 'rejected' + ? { + success: false, + error: + (result as PromiseRejectedResult).reason.message || + 'Error processing device', + } + : (result as PromiseFulfilledResult).value, + ); + + // Return results if there are successful operations + if (successfulResults?.length > 0) { + return { + successOperations: successfulResults, + failedOperations: failedResults, + }; + } + + // Throw an error if all operations failed + throw new HttpException( + failedResults.map((res) => res.error).join('; ') || + 'All device operations failed', + HttpStatus.BAD_REQUEST, + ); + } catch (error) { + console.error(error); + + throw new HttpException( + error.message || 'Error adding online temporary password from Tuya', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getPasswords() { + const deviceIds = await this.deviceRepository.find({ + where: { + productDevice: { + prodType: ProductType.DL, + }, + }, + }); + const data = []; + deviceIds.forEach((deviceId) => { + data.push( + this.doorLockService + .getOnlineTemporaryPasswordsOneTime(deviceId.uuid, true, false) + .catch(() => {}), + this.doorLockService + .getOnlineTemporaryPasswordsOneTime(deviceId.uuid, true, true) + .catch(() => {}), + this.doorLockService + .getOnlineTemporaryPasswordsMultiple(deviceId.uuid, true, false) + .catch(() => {}), + this.doorLockService + .getOnlineTemporaryPasswordsMultiple(deviceId.uuid, true, true) + .catch(() => {}), + this.doorLockService + .getOfflineOneTimeTemporaryPasswords(deviceId.uuid, true, false) + .catch(() => {}), + this.doorLockService + .getOfflineOneTimeTemporaryPasswords(deviceId.uuid, true, true) + .catch(() => {}), + this.doorLockService + .getOfflineMultipleTimeTemporaryPasswords(deviceId.uuid, true, false) + .catch(() => {}), + this.doorLockService + .getOfflineMultipleTimeTemporaryPasswords(deviceId.uuid, true, true) + .catch(() => {}), + ); + }); + return (await Promise.all(data)).flat().filter((datum) => { + return datum != null; + }); + } + + async getAllPassDevices() { + const devices = await this.deviceRepository.find({ + where: { + productDevice: { + prodType: ProductType.DL, + }, + }, + relations: ['productDevice'], + }); + const devicesData = await Promise.all( + devices?.map(async (device) => { + return { + productUuid: device.productDevice.uuid, + productType: device.productDevice.prodType, + ...(await this.deviceService.getDeviceDetailsByDeviceIdTuya( + device.deviceTuyaUuid, + )), + uuid: device.uuid, + } as GetDeviceDetailsInterface; + }), + ); + return devicesData; + } + + async addOnlineTemporaryPasswordOneTime( + addDoorLockOnlineOneTimeDto: AddDoorLockOnlineOneTimeDto, + userUuid: string, + ) { + try { + const deviceResults = await Promise.allSettled( + addDoorLockOnlineOneTimeDto.devicesUuid.map(async (deviceUuid) => { + try { + const passwordData = await this.getTicketAndEncryptedPassword( + deviceUuid, + addDoorLockOnlineOneTimeDto.password, + ); + + if ( + !passwordData.ticketKey || + !passwordData.encryptedPassword || + !passwordData.deviceTuyaUuid + ) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } + + const addDeviceObj: addDeviceObjectInterface = { + ...addDoorLockOnlineOneTimeDto, + ...passwordData, + }; + + const createPass = await this.addOnlineTemporaryPasswordOneTimeTuya( + addDeviceObj, + passwordData.deviceTuyaUuid, + ); + + if (!createPass.success) { + throw new HttpException(createPass.msg, HttpStatus.BAD_REQUEST); + } + + // Send email if the password creation is successful + if (createPass.result.id) { + const emailSubject = 'Password Creation Success'; + const emailBody = `Your temporary password "${addDoorLockOnlineOneTimeDto.password}" has been successfully created for the device with UUID: ${deviceUuid}.\n\nThank you for using our service.\n\nBest Regards,\nSyncrow`; + + await this.emailService.sendEmail( + addDoorLockOnlineOneTimeDto.email, + emailSubject, + emailBody, + ); + } + if (createPass.result.id) { + await this.visitorPasswordRepository.save({ + passwordTuyaUuid: `${createPass.result.id}`, + user: { + uuid: userUuid, + }, + }); + } + return { + success: true, + id: createPass.result.id, + deviceUuid, + }; + } catch (error) { + return { + success: false, + deviceUuid, + error: error.message || 'Error processing device', + }; + } + }), + ); + + // Process results: separate successful and failed operations + const successfulResults = deviceResults + .filter( + (result) => + result.status === 'fulfilled' && + (result as PromiseFulfilledResult).value.success, + ) + .map((result) => (result as PromiseFulfilledResult).value); + + const failedResults = deviceResults + .filter( + (result) => + result.status === 'rejected' || + !(result as PromiseFulfilledResult).value.success, + ) + .map((result) => + result.status === 'rejected' + ? { + success: false, + error: + (result as PromiseRejectedResult).reason.message || + 'Error processing device', + } + : (result as PromiseFulfilledResult).value, + ); + + // Return results if there are successful operations + if (successfulResults.length > 0) { + return { + successOperations: successfulResults, + failedOperations: failedResults, + }; + } + + // Throw an error if all operations failed + throw new HttpException( + failedResults.map((res) => res.error).join('; ') || + 'All device operations failed', + HttpStatus.BAD_REQUEST, + ); + } catch (error) { + console.error(error); + + throw new HttpException( + error.message || 'Error adding online temporary password from Tuya', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getTicketAndEncryptedPassword( + doorLockUuid: string, + passwordPlan: string, + ) { + try { + const deviceDetails = await this.getDeviceByDeviceUuid(doorLockUuid); + + if (!deviceDetails || !deviceDetails.deviceTuyaUuid) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } else if (deviceDetails.productDevice.prodType !== ProductType.DL) { + throw new HttpException( + 'This is not a door lock device', + HttpStatus.BAD_REQUEST, + ); + } + const ticketDetails = await this.createDoorLockTicketTuya( + deviceDetails.deviceTuyaUuid, + ); + + if (!ticketDetails.result.ticket_id || !ticketDetails.result.ticket_key) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } + const decrypted = + this.passwordEncryptionService.generateEncryptedPassword( + passwordPlan, + ticketDetails.result.ticket_key, + ); + return { + ticketId: ticketDetails.result.ticket_id, + ticketKey: ticketDetails.result.ticket_key, + encryptedPassword: decrypted, + deviceTuyaUuid: deviceDetails.deviceTuyaUuid, + }; + } catch (error) { + throw new HttpException( + error.message || 'Error processing the request', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async createDoorLockTicketTuya( + deviceUuid: string, + ): Promise { + try { + const path = `/v1.0/smart-lock/devices/${deviceUuid}/password-ticket`; + const response = await this.tuya.request({ + method: 'POST', + path, + }); + + return response as createTickInterface; + } catch (error) { + throw new HttpException( + 'Error creating door lock ticket from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async addOnlineTemporaryPasswordMultipleTuya( + addDeviceObj: addDeviceObjectInterface, + doorLockUuid: string, + ): Promise { + try { + const path = `/v1.0/devices/${doorLockUuid}/door-lock/temp-password`; + let scheduleList; + if (addDeviceObj?.scheduleList?.length > 0) { + scheduleList = addDeviceObj.scheduleList.map((schedule) => ({ + effective_time: this.timeToMinutes(schedule.effectiveTime), + invalid_time: this.timeToMinutes(schedule.invalidTime), + working_day: this.getWorkingDayValue(schedule.workingDay), + })); + } + + const response = await this.tuya.request({ + method: 'POST', + path, + body: { + name: addDeviceObj.passwordName, + password: addDeviceObj.encryptedPassword, + effective_time: addDeviceObj.effectiveTime, + invalid_time: addDeviceObj.invalidTime, + password_type: 'ticket', + ticket_id: addDeviceObj.ticketId, + ...(addDeviceObj?.scheduleList?.length > 0 && { + schedule_list: scheduleList, + }), + + type: '0', + }, + }); + + return response as createTickInterface; + } catch (error) { + throw new HttpException( + error.msg || 'Error adding online temporary password from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + getWorkingDayValue(days) { + // Array representing the days of the week + const weekDays = ['Sat', 'Fri', 'Thu', 'Wed', 'Tue', 'Mon', 'Sun']; + + // Initialize a binary string with 7 bits + let binaryString = '0000000'; + + // Iterate through the input array and update the binary string + days.forEach((day) => { + const index = weekDays.indexOf(day); + if (index !== -1) { + // Set the corresponding bit to '1' + binaryString = + binaryString.substring(0, index) + + '1' + + binaryString.substring(index + 1); + } + }); + + // Convert the binary string to an integer + const workingDayValue = parseInt(binaryString, 2); + + return workingDayValue; + } + getDaysFromWorkingDayValue(workingDayValue) { + // Array representing the days of the week + const weekDays = ['Sat', 'Fri', 'Thu', 'Wed', 'Tue', 'Mon', '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'); + + // 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') { + days.push(weekDays[i]); + } + } + + return days; + } + timeToMinutes(timeStr) { + try { + // Special case for "24:00" + if (timeStr === '24:00') { + return 1440; + } + + // Regular expression to validate the 24-hour time format (HH:MM) + const timePattern = /^([01]\d|2[0-3]):([0-5]\d)$/; + const match = timeStr.match(timePattern); + + if (!match) { + throw new Error('Invalid time format'); + } + + // Extract hours and minutes from the matched groups + const hours = parseInt(match[1], 10); + const minutes = parseInt(match[2], 10); + + // Calculate the total minutes + const totalMinutes = hours * 60 + minutes; + + return totalMinutes; + } catch (error) { + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + minutesToTime(totalMinutes) { + try { + if ( + typeof totalMinutes !== 'number' || + totalMinutes < 0 || + totalMinutes > 1440 + ) { + throw new Error('Invalid minutes value'); + } + + if (totalMinutes === 1440) { + return '24:00'; + } + + const hours = Math.floor(totalMinutes / 60); + const minutes = totalMinutes % 60; + + const formattedHours = String(hours).padStart(2, '0'); + const formattedMinutes = String(minutes).padStart(2, '0'); + + return `${formattedHours}:${formattedMinutes}`; + } catch (error) { + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + async getDeviceByDeviceUuid( + deviceUuid: string, + withProductDevice: boolean = true, + ) { + try { + return await this.deviceRepository.findOne({ + where: { + uuid: deviceUuid, + }, + ...(withProductDevice && { relations: ['productDevice'] }), + }); + } catch (error) { + throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND); + } + } + async addOnlineTemporaryPasswordOneTimeTuya( + addDeviceObj: addDeviceObjectInterface, + doorLockUuid: string, + ): Promise { + try { + const path = `/v1.0/devices/${doorLockUuid}/door-lock/temp-password`; + const response = await this.tuya.request({ + method: 'POST', + path, + body: { + name: addDeviceObj.passwordName, + password: addDeviceObj.encryptedPassword, + effective_time: addDeviceObj.effectiveTime, + invalid_time: addDeviceObj.invalidTime, + password_type: 'ticket', + ticket_id: addDeviceObj.ticketId, + type: '1', + }, + }); + + return response as createTickInterface; + } catch (error) { + throw new HttpException( + error.msg || 'Error adding online temporary password from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/vistor-password/visitor-password.module.ts b/src/vistor-password/visitor-password.module.ts new file mode 100644 index 0000000..19e61ea --- /dev/null +++ b/src/vistor-password/visitor-password.module.ts @@ -0,0 +1,31 @@ +import { Module } from '@nestjs/common'; +import { VisitorPasswordService } from './services/visitor-password.service'; +import { VisitorPasswordController } from './controllers/visitor-password.controller'; +import { ConfigModule } from '@nestjs/config'; +import { DeviceRepositoryModule } from '@app/common/modules/device'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { EmailService } from '@app/common/util/email.service'; +import { PasswordEncryptionService } from 'src/door-lock/services/encryption.services'; +import { DoorLockModule } from 'src/door-lock/door.lock.module'; +import { DeviceService } from 'src/device/services'; +import { ProductRepository } from '@app/common/modules/product/repositories'; +import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { VisitorPasswordRepository } from '@app/common/modules/visitor-password/repositories'; +@Module({ + imports: [ConfigModule, DeviceRepositoryModule, DoorLockModule], + controllers: [VisitorPasswordController], + providers: [ + VisitorPasswordService, + EmailService, + PasswordEncryptionService, + DeviceService, + ProductRepository, + DeviceStatusFirebaseService, + SpaceRepository, + DeviceRepository, + VisitorPasswordRepository, + ], + exports: [VisitorPasswordService], +}) +export class VisitorPasswordModule {}