Merge pull request #21 from SyncrowIOT/SP-169-be-create-space-and-space-types

Sp 169 be create space and space types
This commit is contained in:
Ammar Qaffaf
2024-04-30 05:38:22 -04:00
committed by GitHub
165 changed files with 5397 additions and 1109 deletions

View File

@ -1,28 +1,28 @@
import { PassportModule } from '@nestjs/passport'; import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt'; import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { HelperModule } from '../helper/helper.module'; import { HelperModule } from '../helper/helper.module';
import { JwtStrategy } from './strategies/jwt.strategy'; import { JwtStrategy } from './strategies/jwt.strategy';
import { UserSessionRepository } from '../modules/session/repositories/session.repository'; import { UserSessionRepository } from '../modules/session/repositories/session.repository';
import { AuthService } from './services/auth.service'; import { AuthService } from './services/auth.service';
import { UserRepository } from '../modules/user/repositories'; import { UserRepository } from '../modules/user/repositories';
import { RefreshTokenStrategy } from './strategies/refresh-token.strategy';
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot(), ConfigModule.forRoot(),
PassportModule, PassportModule,
JwtModule.registerAsync({ JwtModule.register({}),
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
secret: configService.get('JWT_SECRET'),
signOptions: { expiresIn: configService.get('JWT_EXPIRE_TIME') },
}),
}),
HelperModule, HelperModule,
], ],
providers: [JwtStrategy, UserSessionRepository, AuthService, UserRepository], providers: [
JwtStrategy,
RefreshTokenStrategy,
UserSessionRepository,
AuthService,
UserRepository,
],
exports: [AuthService], exports: [AuthService],
}) })
export class AuthModule {} export class AuthModule {}

View File

@ -1,9 +1,11 @@
import { BadRequestException, Injectable } from '@nestjs/common'; import { BadRequestException, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt'; import { JwtService } from '@nestjs/jwt';
import * as argon2 from 'argon2';
import { HelperHashService } from '../../helper/services'; import { HelperHashService } from '../../helper/services';
import { UserRepository } from '../../../../common/src/modules/user/repositories'; import { UserRepository } from '../../../../common/src/modules/user/repositories';
import { UserSessionRepository } from '../../../../common/src/modules/session/repositories/session.repository'; import { UserSessionRepository } from '../../../../common/src/modules/session/repositories/session.repository';
import { UserSessionEntity } from '../../../../common/src/modules/session/entities'; import { UserSessionEntity } from '../../../../common/src/modules/session/entities';
import { ConfigService } from '@nestjs/config';
@Injectable() @Injectable()
export class AuthService { export class AuthService {
@ -12,6 +14,7 @@ export class AuthService {
private readonly userRepository: UserRepository, private readonly userRepository: UserRepository,
private readonly sessionRepository: UserSessionRepository, private readonly sessionRepository: UserSessionRepository,
private readonly helperHashService: HelperHashService, private readonly helperHashService: HelperHashService,
private readonly configService: ConfigService,
) {} ) {}
async validateUser(email: string, pass: string): Promise<any> { async validateUser(email: string, pass: string): Promise<any> {
@ -40,6 +43,24 @@ export class AuthService {
return await this.sessionRepository.save(data); return await this.sessionRepository.save(data);
} }
async getTokens(payload) {
const [accessToken, refreshToken] = await Promise.all([
this.jwtService.signAsync(payload, {
secret: this.configService.get<string>('JWT_SECRET'),
expiresIn: '24h',
}),
this.jwtService.signAsync(payload, {
secret: this.configService.get<string>('JWT_SECRET'),
expiresIn: '7d',
}),
]);
return {
accessToken,
refreshToken,
};
}
async login(user: any) { async login(user: any) {
const payload = { const payload = {
email: user.email, email: user.email,
@ -48,8 +69,22 @@ export class AuthService {
type: user.type, type: user.type,
sessionId: user.sessionId, sessionId: user.sessionId,
}; };
return { const tokens = await this.getTokens(payload);
access_token: this.jwtService.sign(payload), await this.updateRefreshToken(user.uuid, tokens.refreshToken);
}; return tokens;
}
async updateRefreshToken(userId: string, refreshToken: string) {
const hashedRefreshToken = await this.hashData(refreshToken);
await this.userRepository.update(
{ uuid: userId },
{
refreshToken: hashedRefreshToken,
},
);
}
hashData(data: string) {
return argon2.hash(data);
} }
} }

View File

@ -6,7 +6,7 @@ import { UserSessionRepository } from '../../../src/modules/session/repositories
import { AuthInterface } from '../interfaces/auth.interface'; import { AuthInterface } from '../interfaces/auth.interface';
@Injectable() @Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) { export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor( constructor(
private readonly sessionRepository: UserSessionRepository, private readonly sessionRepository: UserSessionRepository,
private readonly configService: ConfigService, private readonly configService: ConfigService,

View File

@ -0,0 +1,42 @@
import { ConfigService } from '@nestjs/config';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { BadRequestException, Injectable } from '@nestjs/common';
import { UserSessionRepository } from '../../../src/modules/session/repositories/session.repository';
import { AuthInterface } from '../interfaces/auth.interface';
@Injectable()
export class RefreshTokenStrategy extends PassportStrategy(
Strategy,
'jwt-refresh',
) {
constructor(
private readonly sessionRepository: UserSessionRepository,
private readonly configService: ConfigService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('JWT_SECRET'),
});
}
async validate(payload: AuthInterface) {
const validateUser = await this.sessionRepository.findOne({
where: {
uuid: payload.sessionId,
isLoggedOut: false,
},
});
if (validateUser) {
return {
email: payload.email,
userId: payload.id,
uuid: payload.uuid,
sessionId: payload.sessionId,
};
} else {
throw new BadRequestException('Unauthorized');
}
}
}

View File

@ -0,0 +1,4 @@
export enum PermissionType {
READ = 'READ',
CONTROLLABLE = 'CONTROLLABLE',
}

View File

@ -5,8 +5,17 @@ import { SnakeNamingStrategy } from './strategies';
import { UserEntity } from '../modules/user/entities/user.entity'; import { UserEntity } from '../modules/user/entities/user.entity';
import { UserSessionEntity } from '../modules/session/entities/session.entity'; import { UserSessionEntity } from '../modules/session/entities/session.entity';
import { UserOtpEntity } from '../modules/user-otp/entities'; import { UserOtpEntity } from '../modules/user-otp/entities';
import { HomeEntity } from '../modules/home/entities';
import { ProductEntity } from '../modules/product/entities'; import { ProductEntity } from '../modules/product/entities';
import {
DeviceEntity,
DeviceUserPermissionEntity,
} 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 { GroupEntity } from '../modules/group/entities';
import { GroupDeviceEntity } from '../modules/group-device/entities';
@Module({ @Module({
imports: [ imports: [
@ -25,12 +34,19 @@ import { ProductEntity } from '../modules/product/entities';
UserEntity, UserEntity,
UserSessionEntity, UserSessionEntity,
UserOtpEntity, UserOtpEntity,
HomeEntity,
ProductEntity, ProductEntity,
DeviceUserPermissionEntity,
DeviceEntity,
PermissionTypeEntity,
SpaceEntity,
SpaceTypeEntity,
UserSpaceEntity,
GroupEntity,
GroupDeviceEntity,
], ],
namingStrategy: new SnakeNamingStrategy(), namingStrategy: new SnakeNamingStrategy(),
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
logging: true, logging: false,
extra: { extra: {
charset: 'utf8mb4', charset: 'utf8mb4',
max: 20, // set pool max size max: 20, // set pool max size

View File

@ -0,0 +1,11 @@
import { UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
export class RefreshTokenGuard extends AuthGuard('jwt-refresh') {
handleRequest(err, user) {
if (err || !user) {
throw err || new UnauthorizedException();
}
return user;
}
}

View File

@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DeviceEntity, DeviceUserPermissionEntity } from './entities';
@Module({
providers: [],
exports: [],
controllers: [],
imports: [
TypeOrmModule.forFeature([DeviceEntity, DeviceUserPermissionEntity]),
],
})
export class DeviceRepositoryModule {}

View File

@ -1,6 +1,6 @@
import { IsNotEmpty, IsString } from 'class-validator'; import { IsNotEmpty, IsString } from 'class-validator';
export class HomeDto { export class DeviceUserTypeDto {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public uuid: string; public uuid: string;
@ -11,9 +11,9 @@ export class HomeDto {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public homeId: string; public deviceUuid: string;
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public homeName: string; public permissionTypeUuid: string;
} }

View File

@ -0,0 +1,19 @@
import { IsNotEmpty, IsString } from 'class-validator';
export class DeviceDto {
@IsString()
@IsNotEmpty()
public uuid: string;
@IsString()
@IsNotEmpty()
spaceUuid: string;
@IsString()
@IsNotEmpty()
deviceTuyaUuid: string;
@IsString()
@IsNotEmpty()
productUuid: string;
}

View File

@ -0,0 +1,2 @@
export * from './device.dto';
export * from './device-user-type.dto';

View File

@ -0,0 +1,42 @@
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { DeviceUserTypeDto } from '../dtos/device-user-type.dto';
import { DeviceEntity } from './device.entity';
import { PermissionTypeEntity } from '../../permission/entities';
@Entity({ name: 'device-user-permission' })
export class DeviceUserPermissionEntity extends AbstractEntity<DeviceUserTypeDto> {
@Column({
nullable: false,
})
public userUuid: string;
@Column({
nullable: false,
})
deviceUuid: string;
@Column({
nullable: false,
})
public permissionTypeUuid: string;
@ManyToOne(() => DeviceEntity, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
@JoinColumn({ name: 'device_uuid', referencedColumnName: 'uuid' })
device: DeviceEntity;
@ManyToOne(() => PermissionTypeEntity, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
@JoinColumn({ name: 'permission_type_uuid', referencedColumnName: 'uuid' })
type: PermissionTypeEntity;
constructor(partial: Partial<DeviceUserPermissionEntity>) {
super();
Object.assign(this, partial);
}
}

View File

@ -0,0 +1,53 @@
import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { DeviceDto } from '../dtos/device.dto';
import { DeviceUserPermissionEntity } from './device-user-type.entity';
import { GroupDeviceEntity } from '../../group-device/entities';
import { SpaceEntity } from '../../space/entities';
import { ProductEntity } from '../../product/entities';
@Entity({ name: 'device' })
@Unique(['spaceDevice', 'deviceTuyaUuid'])
export class DeviceEntity extends AbstractEntity<DeviceDto> {
@Column({
nullable: false,
})
deviceTuyaUuid: string;
@Column({
nullable: true,
default: true,
})
isActive: true;
@OneToMany(
() => DeviceUserPermissionEntity,
(permission) => permission.device,
{
nullable: true,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
},
)
permission: DeviceUserPermissionEntity[];
@OneToMany(
() => GroupDeviceEntity,
(userGroupDevices) => userGroupDevices.device,
)
userGroupDevices: GroupDeviceEntity[];
@ManyToOne(() => SpaceEntity, (space) => space.devicesSpaceEntity, {
nullable: false,
})
spaceDevice: SpaceEntity;
@ManyToOne(() => ProductEntity, (product) => product.devicesProductEntity, {
nullable: false,
})
productDevice: ProductEntity;
constructor(partial: Partial<DeviceEntity>) {
super();
Object.assign(this, partial);
}
}

View File

@ -0,0 +1,2 @@
export * from './device.entity';
export * from './device-user-type.entity';

View File

@ -0,0 +1 @@
export * from './device.repository.module';

View File

@ -0,0 +1,10 @@
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { DeviceUserPermissionEntity } from '../entities';
@Injectable()
export class DeviceUserTypeRepository extends Repository<DeviceUserPermissionEntity> {
constructor(private dataSource: DataSource) {
super(DeviceUserPermissionEntity, dataSource.createEntityManager());
}
}

View File

@ -0,0 +1,10 @@
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { DeviceEntity } from '../entities';
@Injectable()
export class DeviceRepository extends Repository<DeviceEntity> {
constructor(private dataSource: DataSource) {
super(DeviceEntity, dataSource.createEntityManager());
}
}

View File

@ -0,0 +1,2 @@
export * from './device.repository';
export * from './device-user-type.repository';

View File

@ -0,0 +1,15 @@
import { IsNotEmpty, IsString } from 'class-validator';
export class GroupDeviceDto {
@IsString()
@IsNotEmpty()
public uuid: string;
@IsString()
@IsNotEmpty()
public deviceUuid: string;
@IsString()
@IsNotEmpty()
public groupUuid: string;
}

View File

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

View File

@ -0,0 +1,48 @@
import { Column, Entity, ManyToOne, Unique } from 'typeorm';
import { GroupDeviceDto } from '../dtos';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { DeviceEntity } from '../../device/entities';
import { GroupEntity } from '../../group/entities';
@Entity({ name: 'group-device' })
@Unique(['device', 'group'])
export class GroupDeviceEntity extends AbstractEntity<GroupDeviceDto> {
@Column({
type: 'uuid',
default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value
nullable: false,
})
public uuid: string;
@Column({
type: 'string',
nullable: false,
})
deviceUuid: string;
@Column({
type: 'string',
nullable: false,
})
groupUuid: string;
@ManyToOne(() => DeviceEntity, (device) => device.userGroupDevices, {
nullable: false,
})
device: DeviceEntity;
@ManyToOne(() => GroupEntity, (group) => group.groupDevices, {
nullable: false,
})
group: GroupEntity;
@Column({
nullable: true,
default: true,
})
public isActive: boolean;
constructor(partial: Partial<GroupDeviceEntity>) {
super();
Object.assign(this, partial);
}
}

View File

@ -0,0 +1 @@
export * from './group.device.entity';

View File

@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { GroupDeviceEntity } from './entities/group.device.entity';
@Module({
providers: [],
exports: [],
controllers: [],
imports: [TypeOrmModule.forFeature([GroupDeviceEntity])],
})
export class GroupDeviceRepositoryModule {}

View File

@ -0,0 +1,10 @@
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { GroupDeviceEntity } from '../entities/group.device.entity';
@Injectable()
export class GroupDeviceRepository extends Repository<GroupDeviceEntity> {
constructor(private dataSource: DataSource) {
super(GroupDeviceEntity, dataSource.createEntityManager());
}
}

View File

@ -0,0 +1 @@
export * from './group.device.repository';

View File

@ -0,0 +1,11 @@
import { IsNotEmpty, IsString } from 'class-validator';
export class GroupDto {
@IsString()
@IsNotEmpty()
public uuid: string;
@IsString()
@IsNotEmpty()
public groupName: string;
}

View File

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

View File

@ -0,0 +1,35 @@
import { Column, Entity, OneToMany } from 'typeorm';
import { GroupDto } from '../dtos';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { GroupDeviceEntity } from '../../group-device/entities';
@Entity({ name: 'group' })
export class GroupEntity extends AbstractEntity<GroupDto> {
@Column({
type: 'uuid',
default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value
nullable: false,
})
public uuid: string;
@Column({
nullable: false,
})
public groupName: string;
@OneToMany(() => GroupDeviceEntity, (groupDevice) => groupDevice.group, {
cascade: true,
onDelete: 'CASCADE',
})
groupDevices: GroupDeviceEntity[];
@Column({
nullable: true,
default: true,
})
public isActive: boolean;
constructor(partial: Partial<GroupEntity>) {
super();
Object.assign(this, partial);
}
}

View File

@ -0,0 +1 @@
export * from './group.entity';

View File

@ -1,11 +1,11 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { HomeEntity } from './entities/home.entity'; import { GroupEntity } from './entities/group.entity';
@Module({ @Module({
providers: [], providers: [],
exports: [], exports: [],
controllers: [], controllers: [],
imports: [TypeOrmModule.forFeature([HomeEntity])], imports: [TypeOrmModule.forFeature([GroupEntity])],
}) })
export class HomeRepositoryModule {} export class GroupRepositoryModule {}

View File

@ -0,0 +1,10 @@
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { GroupEntity } from '../entities/group.entity';
@Injectable()
export class GroupRepository extends Repository<GroupEntity> {
constructor(private dataSource: DataSource) {
super(GroupEntity, dataSource.createEntityManager());
}
}

View File

@ -0,0 +1 @@
export * from './group.repository';

View File

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

View File

@ -1,34 +0,0 @@
import { Column, Entity } from 'typeorm';
import { HomeDto } from '../dtos';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
@Entity({ name: 'home' })
export class HomeEntity extends AbstractEntity<HomeDto> {
@Column({
type: 'uuid',
default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value
nullable: false,
})
public uuid: string;
@Column({
nullable: false,
})
userUuid: string;
@Column({
nullable: false,
unique: true,
})
public homeId: string;
@Column({
nullable: false,
})
public homeName: string;
constructor(partial: Partial<HomeEntity>) {
super();
Object.assign(this, partial);
}
}

View File

@ -1 +0,0 @@
export * from './home.entity';

View File

@ -1,10 +0,0 @@
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { HomeEntity } from '../entities/home.entity';
@Injectable()
export class HomeRepository extends Repository<HomeEntity> {
constructor(private dataSource: DataSource) {
super(HomeEntity, dataSource.createEntityManager());
}
}

View File

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

View File

@ -0,0 +1,11 @@
import { PermissionType } from '@app/common/constants/permission-type.enum';
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
export class PermissionTypeDto {
@IsString()
@IsNotEmpty()
public uuid: string;
@IsEnum(PermissionType)
public type: PermissionType;
}

View File

@ -0,0 +1 @@
export * from './permission.entity';

View File

@ -0,0 +1,30 @@
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/entities';
@Entity({ name: 'permission-type' })
export class PermissionTypeEntity extends AbstractEntity<PermissionTypeDto> {
@Column({
nullable: false,
enum: Object.values(PermissionType),
})
type: string;
@OneToMany(
() => DeviceUserPermissionEntity,
(permission) => permission.type,
{
nullable: true,
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
},
)
permission: DeviceUserPermissionEntity[];
constructor(partial: Partial<PermissionTypeEntity>) {
super();
Object.assign(this, partial);
}
}

View File

@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PermissionTypeEntity } from './entities/permission.entity';
@Module({
providers: [],
exports: [],
controllers: [],
imports: [TypeOrmModule.forFeature([PermissionTypeEntity])],
})
export class PermissionTypeRepositoryModule {}

View File

@ -0,0 +1 @@
export * from './permission.repository';

View File

@ -0,0 +1,10 @@
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { PermissionTypeEntity } from '../entities/permission.entity';
@Injectable()
export class PermissionTypeRepository extends Repository<PermissionTypeEntity> {
constructor(private dataSource: DataSource) {
super(PermissionTypeEntity, dataSource.createEntityManager());
}
}

View File

@ -1,6 +1,7 @@
import { Column, Entity } from 'typeorm'; import { Column, Entity, OneToMany } from 'typeorm';
import { ProductDto } from '../dtos'; import { ProductDto } from '../dtos';
import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { DeviceEntity } from '../../device/entities';
@Entity({ name: 'product' }) @Entity({ name: 'product' })
export class ProductEntity extends AbstractEntity<ProductDto> { export class ProductEntity extends AbstractEntity<ProductDto> {
@ -20,6 +21,11 @@ export class ProductEntity extends AbstractEntity<ProductDto> {
}) })
public prodType: string; public prodType: string;
@OneToMany(
() => DeviceEntity,
(devicesProductEntity) => devicesProductEntity.productDevice,
)
devicesProductEntity: DeviceEntity[];
constructor(partial: Partial<ProductEntity>) { constructor(partial: Partial<ProductEntity>) {
super(); super();
Object.assign(this, partial); Object.assign(this, partial);

View File

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

View File

@ -0,0 +1,11 @@
import { IsNotEmpty, IsString } from 'class-validator';
export class SpaceTypeDto {
@IsString()
@IsNotEmpty()
public uuid: string;
@IsString()
@IsNotEmpty()
public type: string;
}

View File

@ -0,0 +1 @@
export * from './space.type.entity';

View File

@ -0,0 +1,26 @@
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<SpaceTypeDto> {
@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<SpaceTypeEntity>) {
super();
Object.assign(this, partial);
}
}

View File

@ -0,0 +1 @@
export * from './space.type.repository';

View File

@ -0,0 +1,10 @@
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { SpaceTypeEntity } from '../entities/space.type.entity';
@Injectable()
export class SpaceTypeRepository extends Repository<SpaceTypeEntity> {
constructor(private dataSource: DataSource) {
super(SpaceTypeEntity, dataSource.createEntityManager());
}
}

View File

@ -0,0 +1,11 @@
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 {}

View File

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

View File

@ -0,0 +1,19 @@
import { IsNotEmpty, IsString } from 'class-validator';
export class SpaceDto {
@IsString()
@IsNotEmpty()
public uuid: string;
@IsString()
@IsNotEmpty()
public parentUuid: string;
@IsString()
@IsNotEmpty()
public spaceName: string;
@IsString()
@IsNotEmpty()
public spaceTypeUuid: string;
}

View File

@ -0,0 +1 @@
export * from './space.entity';

View File

@ -0,0 +1,44 @@
import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
import { SpaceDto } from '../dtos';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { SpaceTypeEntity } from '../../space-type/entities';
import { UserSpaceEntity } from '../../user-space/entities';
import { DeviceEntity } from '../../device/entities';
@Entity({ name: 'space' })
export class SpaceEntity extends AbstractEntity<SpaceDto> {
@Column({
type: 'uuid',
default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value
nullable: false,
})
public uuid: string;
@Column({
nullable: false,
})
public spaceName: string;
@ManyToOne(() => SpaceEntity, (space) => space.children, { nullable: true })
parent: SpaceEntity;
@OneToMany(() => SpaceEntity, (space) => space.parent)
children: SpaceEntity[];
@ManyToOne(() => SpaceTypeEntity, (spaceType) => spaceType.spaces, {
nullable: false,
})
spaceType: SpaceTypeEntity;
@OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.space)
userSpaces: UserSpaceEntity[];
@OneToMany(
() => DeviceEntity,
(devicesSpaceEntity) => devicesSpaceEntity.spaceDevice,
)
devicesSpaceEntity: DeviceEntity[];
constructor(partial: Partial<SpaceEntity>) {
super();
Object.assign(this, partial);
}
}

View File

@ -0,0 +1 @@
export * from './space.repository';

View File

@ -0,0 +1,10 @@
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { SpaceEntity } from '../entities/space.entity';
@Injectable()
export class SpaceRepository extends Repository<SpaceEntity> {
constructor(private dataSource: DataSource) {
super(SpaceEntity, dataSource.createEntityManager());
}
}

View File

@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { SpaceEntity } from './entities/space.entity';
@Module({
providers: [],
exports: [],
controllers: [],
imports: [TypeOrmModule.forFeature([SpaceEntity])],
})
export class SpaceRepositoryModule {}

View File

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

View File

@ -0,0 +1,15 @@
import { IsNotEmpty, IsString } from 'class-validator';
export class UserSpaceDto {
@IsString()
@IsNotEmpty()
public uuid: string;
@IsString()
@IsNotEmpty()
public spaceUuid: string;
@IsString()
@IsNotEmpty()
public userUuid: string;
}

View File

@ -0,0 +1 @@
export * from './user.space.entity';

View File

@ -0,0 +1,29 @@
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<UserSpaceDto> {
@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<UserSpaceEntity>) {
super();
Object.assign(this, partial);
}
}

View File

@ -0,0 +1 @@
export * from './user.space.repository';

View File

@ -0,0 +1,10 @@
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { UserSpaceEntity } from '../entities/user.space.entity';
@Injectable()
export class UserSpaceRepository extends Repository<UserSpaceEntity> {
constructor(private dataSource: DataSource) {
super(UserSpaceEntity, dataSource.createEntityManager());
}
}

View File

@ -0,0 +1,11 @@
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 {}

View File

@ -0,0 +1 @@
export * from './user.entity';

View File

@ -1,6 +1,7 @@
import { Column, Entity } from 'typeorm'; import { Column, Entity, OneToMany } from 'typeorm';
import { UserDto } from '../dtos'; import { UserDto } from '../dtos';
import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { UserSpaceEntity } from '../../user-space/entities';
@Entity({ name: 'user' }) @Entity({ name: 'user' })
export class UserEntity extends AbstractEntity<UserDto> { export class UserEntity extends AbstractEntity<UserDto> {
@ -30,12 +31,26 @@ export class UserEntity extends AbstractEntity<UserDto> {
}) })
public lastName: string; public lastName: string;
@Column({
nullable: true,
})
public refreshToken: string;
@Column({ @Column({
nullable: true, nullable: true,
default: false, default: false,
}) })
public isUserVerified: boolean; public isUserVerified: boolean;
@Column({
nullable: false,
default: true,
})
public isActive: boolean;
@OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.user)
userSpaces: UserSpaceEntity[];
constructor(partial: Partial<UserEntity>) { constructor(partial: Partial<UserEntity>) {
super(); super();
Object.assign(this, partial); Object.assign(this, partial);

41
package-lock.json generated
View File

@ -18,6 +18,7 @@
"@nestjs/swagger": "^7.3.0", "@nestjs/swagger": "^7.3.0",
"@nestjs/typeorm": "^10.0.2", "@nestjs/typeorm": "^10.0.2",
"@tuya/tuya-connector-nodejs": "^2.1.2", "@tuya/tuya-connector-nodejs": "^2.1.2",
"argon2": "^0.40.1",
"axios": "^1.6.7", "axios": "^1.6.7",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
@ -2111,6 +2112,14 @@
"npm": ">=5.0.0" "npm": ">=5.0.0"
} }
}, },
"node_modules/@phc/format": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
"integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==",
"engines": {
"node": ">=10"
}
},
"node_modules/@pkgjs/parseargs": { "node_modules/@pkgjs/parseargs": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@ -3032,6 +3041,20 @@
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"devOptional": true "devOptional": true
}, },
"node_modules/argon2": {
"version": "0.40.1",
"resolved": "https://registry.npmjs.org/argon2/-/argon2-0.40.1.tgz",
"integrity": "sha512-DjtHDwd7pm12qeWyfihHoM8Bn5vGcgH6sKwgPqwNYroRmxlrzadHEvMyuvQxN/V8YSyRRKD5x6ito09q1e9OyA==",
"hasInstallScript": true,
"dependencies": {
"@phc/format": "^1.0.0",
"node-addon-api": "^7.1.0",
"node-gyp-build": "^4.8.0"
},
"engines": {
"node": ">=16.17.0"
}
},
"node_modules/argparse": { "node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@ -7168,6 +7191,14 @@
"integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==",
"dev": true "dev": true
}, },
"node_modules/node-addon-api": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz",
"integrity": "sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==",
"engines": {
"node": "^16 || ^18 || >= 20"
}
},
"node_modules/node-emoji": { "node_modules/node-emoji": {
"version": "1.11.0", "version": "1.11.0",
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
@ -7196,6 +7227,16 @@
} }
} }
}, },
"node_modules/node-gyp-build": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz",
"integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/node-int64": { "node_modules/node-int64": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",

View File

@ -29,6 +29,7 @@
"@nestjs/swagger": "^7.3.0", "@nestjs/swagger": "^7.3.0",
"@nestjs/typeorm": "^10.0.2", "@nestjs/typeorm": "^10.0.2",
"@tuya/tuya-connector-nodejs": "^2.1.2", "@tuya/tuya-connector-nodejs": "^2.1.2",
"argon2": "^0.40.1",
"axios": "^1.6.7", "axios": "^1.6.7",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",

View File

@ -4,10 +4,14 @@ import config from './config';
import { AuthenticationModule } from './auth/auth.module'; import { AuthenticationModule } from './auth/auth.module';
import { AuthenticationController } from './auth/controllers/authentication.controller'; import { AuthenticationController } from './auth/controllers/authentication.controller';
import { UserModule } from './users/user.module'; import { UserModule } from './users/user.module';
import { HomeModule } from './home/home.module';
import { RoomModule } from './room/room.module'; import { RoomModule } from './room/room.module';
import { GroupModule } from './group/group.module'; import { GroupModule } from './group/group.module';
import { DeviceModule } from './device/device.module'; import { DeviceModule } from './device/device.module';
import { UserDevicePermissionModule } from './user-device-permission/user-device-permission.module';
import { CommunityModule } from './community/community.module';
import { BuildingModule } from './building/building.module';
import { FloorModule } from './floor/floor.module';
import { UnitModule } from './unit/unit.module';
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot({ ConfigModule.forRoot({
@ -15,10 +19,15 @@ import { DeviceModule } from './device/device.module';
}), }),
AuthenticationModule, AuthenticationModule,
UserModule, UserModule,
HomeModule, CommunityModule,
BuildingModule,
FloorModule,
UnitModule,
RoomModule,
RoomModule, RoomModule,
GroupModule, GroupModule,
DeviceModule, DeviceModule,
UserDevicePermissionModule,
], ],
controllers: [AuthenticationController], controllers: [AuthenticationController],
}) })

View File

@ -2,9 +2,11 @@ import {
Body, Body,
Controller, Controller,
Delete, Delete,
Get,
HttpStatus, HttpStatus,
Param, Param,
Post, Post,
Req,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { UserAuthService } from '../services/user-auth.service'; import { UserAuthService } from '../services/user-auth.service';
@ -14,6 +16,7 @@ import { ResponseMessage } from '../../../libs/common/src/response/response.deco
import { UserLoginDto } from '../dtos/user-login.dto'; import { UserLoginDto } from '../dtos/user-login.dto';
import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard'; import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard';
import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos'; import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos';
import { RefreshTokenGuard } from '@app/common/guards/jwt-refresh.auth.guard';
@Controller({ @Controller({
version: '1', version: '1',
@ -93,4 +96,33 @@ export class UserAuthController {
message: 'Password changed successfully', message: 'Password changed successfully',
}; };
} }
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get('user/list')
async userList() {
const userList = await this.userAuthService.userList();
return {
statusCode: HttpStatus.OK,
data: userList,
message: 'User List Fetched Successfully',
};
}
@ApiBearerAuth()
@UseGuards(RefreshTokenGuard)
@Get('refresh-token')
async refreshToken(@Req() req) {
const refreshToken = await this.userAuthService.refreshToken(
req.user.uuid,
req.headers.authorization,
req.user.type,
req.user.sessionId,
);
return {
statusCode: HttpStatus.OK,
data: refreshToken,
message: 'Refresh Token added Successfully',
};
}
} }

View File

@ -1,6 +1,7 @@
import { UserRepository } from '../../../libs/common/src/modules/user/repositories'; import { UserRepository } from '../../../libs/common/src/modules/user/repositories';
import { import {
BadRequestException, BadRequestException,
ForbiddenException,
Injectable, Injectable,
UnauthorizedException, UnauthorizedException,
} from '@nestjs/common'; } from '@nestjs/common';
@ -14,7 +15,7 @@ import { ForgetPasswordDto, UserOtpDto, VerifyOtpDto } from '../dtos';
import { EmailService } from '../../../libs/common/src/util/email.service'; import { EmailService } from '../../../libs/common/src/util/email.service';
import { OtpType } from '../../../libs/common/src/constants/otp-type.enum'; import { OtpType } from '../../../libs/common/src/constants/otp-type.enum';
import { UserEntity } from '../../../libs/common/src/modules/user/entities/user.entity'; import { UserEntity } from '../../../libs/common/src/modules/user/entities/user.entity';
import { ILoginResponse } from '../constants/login.response.constant'; import * as argon2 from 'argon2';
@Injectable() @Injectable()
export class UserAuthService { export class UserAuthService {
@ -64,7 +65,7 @@ export class UserAuthService {
); );
} }
async userLogin(data: UserLoginDto): Promise<ILoginResponse> { async userLogin(data: UserLoginDto) {
const user = await this.authService.validateUser(data.email, data.password); const user = await this.authService.validateUser(data.email, data.password);
if (!user) { if (!user) {
throw new UnauthorizedException('Invalid login credentials.'); throw new UnauthorizedException('Invalid login credentials.');
@ -86,7 +87,7 @@ export class UserAuthService {
return await this.authService.login({ return await this.authService.login({
email: user.email, email: user.email,
userId: user.id, userId: user.uuid,
uuid: user.uuid, uuid: user.uuid,
sessionId: session[1].uuid, sessionId: session[1].uuid,
}); });
@ -97,7 +98,7 @@ export class UserAuthService {
if (!user) { if (!user) {
throw new BadRequestException('User does not found'); throw new BadRequestException('User does not found');
} }
return await this.userRepository.delete({ uuid }); return await this.userRepository.update({ uuid }, { isActive: false });
} }
async findOneById(id: string): Promise<UserEntity> { async findOneById(id: string): Promise<UserEntity> {
@ -148,4 +149,41 @@ export class UserAuthService {
return true; return true;
} }
async userList(): Promise<UserEntity[]> {
return await this.userRepository.find({
where: { isActive: true },
select: {
firstName: true,
lastName: true,
email: true,
isActive: true,
},
});
}
async refreshToken(
userId: string,
refreshToken: string,
type: string,
sessionId: string,
) {
const user = await this.userRepository.findOne({ where: { uuid: userId } });
if (!user || !user.refreshToken)
throw new ForbiddenException('Access Denied');
const refreshTokenMatches = await argon2.verify(
user.refreshToken,
refreshToken,
);
if (!refreshTokenMatches) throw new ForbiddenException('Access Denied');
const tokens = await this.authService.getTokens({
email: user.email,
userId: user.uuid,
uuid: user.uuid,
type,
sessionId,
});
await this.authService.updateRefreshToken(user.uuid, tokens.refreshToken);
return tokens;
}
} }

View File

@ -0,0 +1,32 @@
import { Module } from '@nestjs/common';
import { BuildingService } from './services/building.service';
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 { UserRepository } from '@app/common/modules/user/repositories';
import { UserRepositoryModule } from '@app/common/modules/user/user.repository.module';
@Module({
imports: [
ConfigModule,
SpaceRepositoryModule,
SpaceTypeRepositoryModule,
UserSpaceRepositoryModule,
UserRepositoryModule,
],
controllers: [BuildingController],
providers: [
BuildingService,
SpaceRepository,
SpaceTypeRepository,
UserSpaceRepository,
UserRepository,
],
exports: [BuildingService],
})
export class BuildingModule {}

View File

@ -0,0 +1,144 @@
import { BuildingService } from '../services/building.service';
import {
Body,
Controller,
Get,
HttpException,
HttpStatus,
Param,
Post,
Put,
Query,
UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard';
import { AddBuildingDto, AddUserBuildingDto } from '../dtos/add.building.dto';
import { GetBuildingChildDto } from '../dtos/get.building.dto';
import { UpdateBuildingNameDto } from '../dtos/update.building.dto';
import { CheckCommunityTypeGuard } from 'src/guards/community.type.guard';
import { CheckUserBuildingGuard } from 'src/guards/user.building.guard';
@ApiTags('Building Module')
@Controller({
version: '1',
path: 'building',
})
export class BuildingController {
constructor(private readonly buildingService: BuildingService) {}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, CheckCommunityTypeGuard)
@Post()
async addBuilding(@Body() addBuildingDto: AddBuildingDto) {
try {
await this.buildingService.addBuilding(addBuildingDto);
return { message: 'Building added successfully' };
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':buildingUuid')
async getBuildingByUuid(@Param('buildingUuid') buildingUuid: string) {
try {
const building =
await this.buildingService.getBuildingByUuid(buildingUuid);
return building;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get('child/:buildingUuid')
async getBuildingChildByUuid(
@Param('buildingUuid') buildingUuid: string,
@Query() query: GetBuildingChildDto,
) {
try {
const building = await this.buildingService.getBuildingChildByUuid(
buildingUuid,
query,
);
return building;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get('parent/:buildingUuid')
async getBuildingParentByUuid(@Param('buildingUuid') buildingUuid: string) {
try {
const building =
await this.buildingService.getBuildingParentByUuid(buildingUuid);
return building;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, CheckUserBuildingGuard)
@Post('user')
async addUserBuilding(@Body() addUserBuildingDto: AddUserBuildingDto) {
try {
await this.buildingService.addUserBuilding(addUserBuildingDto);
return { message: 'user building added successfully' };
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get('user/:userUuid')
async getBuildingsByUserId(@Param('userUuid') userUuid: string) {
try {
return await this.buildingService.getBuildingsByUserId(userUuid);
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Put('rename/:buildingUuid')
async renameBuildingByUuid(
@Param('buildingUuid') buildingUuid: string,
@Body() updateBuildingDto: UpdateBuildingNameDto,
) {
try {
const building = await this.buildingService.renameBuildingByUuid(
buildingUuid,
updateBuildingDto,
);
return building;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}

View File

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

View File

@ -0,0 +1,42 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class AddBuildingDto {
@ApiProperty({
description: 'buildingName',
required: true,
})
@IsString()
@IsNotEmpty()
public buildingName: string;
@ApiProperty({
description: 'communityUuid',
required: true,
})
@IsString()
@IsNotEmpty()
public communityUuid: string;
constructor(dto: Partial<AddBuildingDto>) {
Object.assign(this, dto);
}
}
export class AddUserBuildingDto {
@ApiProperty({
description: 'buildingUuid',
required: true,
})
@IsString()
@IsNotEmpty()
public buildingUuid: string;
@ApiProperty({
description: 'userUuid',
required: true,
})
@IsString()
@IsNotEmpty()
public userUuid: string;
constructor(dto: Partial<AddUserBuildingDto>) {
Object.assign(this, dto);
}
}

View File

@ -0,0 +1,51 @@
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import {
IsBoolean,
IsInt,
IsNotEmpty,
IsOptional,
IsString,
Min,
} from 'class-validator';
export class GetBuildingDto {
@ApiProperty({
description: 'buildingUuid',
required: true,
})
@IsString()
@IsNotEmpty()
public buildingUuid: string;
}
export class GetBuildingChildDto {
@ApiProperty({ example: 1, description: 'Page number', required: true })
@IsInt({ message: 'Page must be a number' })
@Min(1, { message: 'Page must not be less than 1' })
@IsNotEmpty()
public page: number;
@ApiProperty({
example: 10,
description: 'Number of items per page',
required: true,
})
@IsInt({ message: 'Page size must be a number' })
@Min(1, { message: 'Page size must not be less than 1' })
@IsNotEmpty()
public pageSize: number;
@ApiProperty({
example: true,
description: 'Flag to determine whether to fetch full hierarchy',
required: false,
default: false,
})
@IsOptional()
@IsBoolean()
@Transform((value) => {
return value.obj.includeSubSpaces === 'true';
})
public includeSubSpaces: boolean = false;
}

View File

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

View File

@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class UpdateBuildingNameDto {
@ApiProperty({
description: 'buildingName',
required: true,
})
@IsString()
@IsNotEmpty()
public buildingName: string;
constructor(dto: Partial<UpdateBuildingNameDto>) {
Object.assign(this, dto);
}
}

View File

@ -0,0 +1,31 @@
export interface GetBuildingByUuidInterface {
uuid: string;
createdAt: Date;
updatedAt: Date;
name: string;
type: string;
}
export interface BuildingChildInterface {
uuid: string;
name: string;
type: string;
totalCount?: number;
children?: BuildingChildInterface[];
}
export interface BuildingParentInterface {
uuid: string;
name: string;
type: string;
parent?: BuildingParentInterface;
}
export interface RenameBuildingByUuidInterface {
uuid: string;
name: string;
type: string;
}
export interface GetBuildingByUserUuidInterface {
uuid: string;
name: string;
type: string;
}

View File

@ -0,0 +1,310 @@
import { GetBuildingChildDto } from '../dtos/get.building.dto';
import { SpaceTypeRepository } from '../../../libs/common/src/modules/space-type/repositories/space.type.repository';
import {
Injectable,
HttpException,
HttpStatus,
BadRequestException,
} from '@nestjs/common';
import { SpaceRepository } from '@app/common/modules/space/repositories';
import { AddBuildingDto, AddUserBuildingDto } from '../dtos';
import {
BuildingChildInterface,
BuildingParentInterface,
GetBuildingByUserUuidInterface,
GetBuildingByUuidInterface,
RenameBuildingByUuidInterface,
} 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';
@Injectable()
export class BuildingService {
constructor(
private readonly spaceRepository: SpaceRepository,
private readonly spaceTypeRepository: SpaceTypeRepository,
private readonly userSpaceRepository: UserSpaceRepository,
) {}
async addBuilding(addBuildingDto: AddBuildingDto) {
try {
const spaceType = await this.spaceTypeRepository.findOne({
where: {
type: 'building',
},
});
if (!spaceType) {
throw new BadRequestException('Invalid building UUID');
}
await this.spaceRepository.save({
spaceName: addBuildingDto.buildingName,
parent: { uuid: addBuildingDto.communityUuid },
spaceType: { uuid: spaceType.uuid },
});
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Building not found', HttpStatus.NOT_FOUND);
}
}
}
async getBuildingByUuid(
buildingUuid: string,
): Promise<GetBuildingByUuidInterface> {
try {
const building = await this.spaceRepository.findOne({
where: {
uuid: buildingUuid,
spaceType: {
type: 'building',
},
},
relations: ['spaceType'],
});
if (
!building ||
!building.spaceType ||
building.spaceType.type !== 'building'
) {
throw new BadRequestException('Invalid building UUID');
}
return {
uuid: building.uuid,
createdAt: building.createdAt,
updatedAt: building.updatedAt,
name: building.spaceName,
type: building.spaceType.type,
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Building not found', HttpStatus.NOT_FOUND);
}
}
}
async getBuildingChildByUuid(
buildingUuid: string,
getBuildingChildDto: GetBuildingChildDto,
): Promise<BuildingChildInterface> {
try {
const { includeSubSpaces, page, pageSize } = getBuildingChildDto;
const space = await this.spaceRepository.findOneOrFail({
where: { uuid: buildingUuid },
relations: ['children', 'spaceType'],
});
if (!space || !space.spaceType || space.spaceType.type !== 'building') {
throw new BadRequestException('Invalid building UUID');
}
const totalCount = await this.spaceRepository.count({
where: { parent: { uuid: space.uuid } },
});
const children = await this.buildHierarchy(
space,
includeSubSpaces,
page,
pageSize,
);
return {
uuid: space.uuid,
name: space.spaceName,
type: space.spaceType.type,
totalCount,
children,
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Building not found', HttpStatus.NOT_FOUND);
}
}
}
private async buildHierarchy(
space: SpaceEntity,
includeSubSpaces: boolean,
page: number,
pageSize: number,
): Promise<BuildingChildInterface[]> {
const children = await this.spaceRepository.find({
where: { parent: { uuid: space.uuid } },
relations: ['spaceType'],
skip: (page - 1) * pageSize,
take: pageSize,
});
if (!children || children.length === 0 || !includeSubSpaces) {
return children
.filter(
(child) =>
child.spaceType.type !== 'building' &&
child.spaceType.type !== 'community',
) // Filter remaining building and community types
.map((child) => ({
uuid: child.uuid,
name: child.spaceName,
type: child.spaceType.type,
}));
}
const childHierarchies = await Promise.all(
children
.filter(
(child) =>
child.spaceType.type !== 'building' &&
child.spaceType.type !== 'community',
) // Filter remaining building and community types
.map(async (child) => ({
uuid: child.uuid,
name: child.spaceName,
type: child.spaceType.type,
children: await this.buildHierarchy(child, true, 1, pageSize),
})),
);
return childHierarchies;
}
async getBuildingParentByUuid(
buildingUuid: string,
): Promise<BuildingParentInterface> {
try {
const building = await this.spaceRepository.findOne({
where: {
uuid: buildingUuid,
spaceType: {
type: 'building',
},
},
relations: ['spaceType', 'parent', 'parent.spaceType'],
});
if (
!building ||
!building.spaceType ||
building.spaceType.type !== 'building'
) {
throw new BadRequestException('Invalid building UUID');
}
return {
uuid: building.uuid,
name: building.spaceName,
type: building.spaceType.type,
parent: {
uuid: building.parent.uuid,
name: building.parent.spaceName,
type: building.parent.spaceType.type,
},
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Building not found', HttpStatus.NOT_FOUND);
}
}
}
async getBuildingsByUserId(
userUuid: string,
): Promise<GetBuildingByUserUuidInterface[]> {
try {
const buildings = await this.userSpaceRepository.find({
relations: ['space', 'space.spaceType'],
where: {
user: { uuid: userUuid },
space: { spaceType: { type: 'building' } },
},
});
if (buildings.length === 0) {
throw new HttpException(
'this user has no buildings',
HttpStatus.NOT_FOUND,
);
}
const spaces = buildings.map((building) => ({
uuid: building.space.uuid,
name: building.space.spaceName,
type: building.space.spaceType.type,
}));
return spaces;
} catch (err) {
if (err instanceof HttpException) {
throw err;
} else {
throw new HttpException('user not found', HttpStatus.NOT_FOUND);
}
}
}
async addUserBuilding(addUserBuildingDto: AddUserBuildingDto) {
try {
await this.userSpaceRepository.save({
user: { uuid: addUserBuildingDto.userUuid },
space: { uuid: addUserBuildingDto.buildingUuid },
});
} catch (err) {
if (err.code === '23505') {
throw new HttpException(
'User already belongs to this building',
HttpStatus.BAD_REQUEST,
);
}
throw new HttpException(
err.message || 'Internal Server Error',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async renameBuildingByUuid(
buildingUuid: string,
updateBuildingNameDto: UpdateBuildingNameDto,
): Promise<RenameBuildingByUuidInterface> {
try {
const building = await this.spaceRepository.findOneOrFail({
where: { uuid: buildingUuid },
relations: ['spaceType'],
});
if (
!building ||
!building.spaceType ||
building.spaceType.type !== 'building'
) {
throw new BadRequestException('Invalid building UUID');
}
await this.spaceRepository.update(
{ uuid: buildingUuid },
{ spaceName: updateBuildingNameDto.buildingName },
);
// Fetch the updated building
const updatedBuilding = await this.spaceRepository.findOneOrFail({
where: { uuid: buildingUuid },
relations: ['spaceType'],
});
return {
uuid: updatedBuilding.uuid,
name: updatedBuilding.spaceName,
type: updatedBuilding.spaceType.type,
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Building not found', HttpStatus.NOT_FOUND);
}
}
}
}

View File

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

View File

@ -0,0 +1,32 @@
import { Module } from '@nestjs/common';
import { CommunityService } from './services/community.service';
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 { UserRepositoryModule } from '@app/common/modules/user/user.repository.module';
import { UserRepository } from '@app/common/modules/user/repositories';
@Module({
imports: [
ConfigModule,
SpaceRepositoryModule,
SpaceTypeRepositoryModule,
UserSpaceRepositoryModule,
UserRepositoryModule,
],
controllers: [CommunityController],
providers: [
CommunityService,
SpaceRepository,
SpaceTypeRepository,
UserSpaceRepository,
UserRepository,
],
exports: [CommunityService],
})
export class CommunityModule {}

View File

@ -0,0 +1,131 @@
import { CommunityService } from '../services/community.service';
import {
Body,
Controller,
Get,
HttpException,
HttpStatus,
Param,
Post,
Put,
Query,
UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard';
import {
AddCommunityDto,
AddUserCommunityDto,
} 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';
@ApiTags('Community Module')
@Controller({
version: '1',
path: 'community',
})
export class CommunityController {
constructor(private readonly communityService: CommunityService) {}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Post()
async addCommunity(@Body() addCommunityDto: AddCommunityDto) {
try {
await this.communityService.addCommunity(addCommunityDto);
return { message: 'Community added successfully' };
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':communityUuid')
async getCommunityByUuid(@Param('communityUuid') communityUuid: string) {
try {
const community =
await this.communityService.getCommunityByUuid(communityUuid);
return community;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get('child/:communityUuid')
async getCommunityChildByUuid(
@Param('communityUuid') communityUuid: string,
@Query() query: GetCommunityChildDto,
) {
try {
const community = await this.communityService.getCommunityChildByUuid(
communityUuid,
query,
);
return community;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get('user/:userUuid')
async getCommunitiesByUserId(@Param('userUuid') userUuid: string) {
try {
return await this.communityService.getCommunitiesByUserId(userUuid);
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, CheckUserCommunityGuard)
@Post('user')
async addUserCommunity(@Body() addUserCommunityDto: AddUserCommunityDto) {
try {
await this.communityService.addUserCommunity(addUserCommunityDto);
return { message: 'user community added successfully' };
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Put('rename/:communityUuid')
async renameCommunityByUuid(
@Param('communityUuid') communityUuid: string,
@Body() updateCommunityDto: UpdateCommunityNameDto,
) {
try {
const community = await this.communityService.renameCommunityByUuid(
communityUuid,
updateCommunityDto,
);
return community;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}

View File

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

View File

@ -0,0 +1,35 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class AddCommunityDto {
@ApiProperty({
description: 'communityName',
required: true,
})
@IsString()
@IsNotEmpty()
public communityName: string;
constructor(dto: Partial<AddCommunityDto>) {
Object.assign(this, dto);
}
}
export class AddUserCommunityDto {
@ApiProperty({
description: 'communityUuid',
required: true,
})
@IsString()
@IsNotEmpty()
public communityUuid: string;
@ApiProperty({
description: 'userUuid',
required: true,
})
@IsString()
@IsNotEmpty()
public userUuid: string;
constructor(dto: Partial<AddUserCommunityDto>) {
Object.assign(this, dto);
}
}

View File

@ -0,0 +1,51 @@
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import {
IsBoolean,
IsInt,
IsNotEmpty,
IsOptional,
IsString,
Min,
} from 'class-validator';
export class GetCommunityDto {
@ApiProperty({
description: 'communityUuid',
required: true,
})
@IsString()
@IsNotEmpty()
public communityUuid: string;
}
export class GetCommunityChildDto {
@ApiProperty({ example: 1, description: 'Page number', required: true })
@IsInt({ message: 'Page must be a number' })
@Min(1, { message: 'Page must not be less than 1' })
@IsNotEmpty()
public page: number;
@ApiProperty({
example: 10,
description: 'Number of items per page',
required: true,
})
@IsInt({ message: 'Page size must be a number' })
@Min(1, { message: 'Page size must not be less than 1' })
@IsNotEmpty()
public pageSize: number;
@ApiProperty({
example: true,
description: 'Flag to determine whether to fetch full hierarchy',
required: false,
default: false,
})
@IsOptional()
@IsBoolean()
@Transform((value) => {
return value.obj.includeSubSpaces === 'true';
})
public includeSubSpaces: boolean = false;
}

View File

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

View File

@ -0,0 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class UpdateCommunityNameDto {
@ApiProperty({
description: 'communityName',
required: true,
})
@IsString()
@IsNotEmpty()
public communityName: string;
constructor(dto: Partial<UpdateCommunityNameDto>) {
Object.assign(this, dto);
}
}

View File

@ -0,0 +1,26 @@
export interface GetCommunityByUuidInterface {
uuid: string;
createdAt: Date;
updatedAt: Date;
name: string;
type: string;
}
export interface CommunityChildInterface {
uuid: string;
name: string;
type: string;
totalCount?: number;
children?: CommunityChildInterface[];
}
export interface RenameCommunityByUuidInterface {
uuid: string;
name: string;
type: string;
}
export interface GetCommunityByUserUuidInterface {
uuid: string;
name: string;
type: string;
}

View File

@ -0,0 +1,253 @@
import { GetCommunityChildDto } from './../dtos/get.community.dto';
import { SpaceTypeRepository } from './../../../libs/common/src/modules/space-type/repositories/space.type.repository';
import {
Injectable,
HttpException,
HttpStatus,
BadRequestException,
} from '@nestjs/common';
import { SpaceRepository } from '@app/common/modules/space/repositories';
import { AddCommunityDto, AddUserCommunityDto } from '../dtos';
import {
CommunityChildInterface,
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';
@Injectable()
export class CommunityService {
constructor(
private readonly spaceRepository: SpaceRepository,
private readonly spaceTypeRepository: SpaceTypeRepository,
private readonly userSpaceRepository: UserSpaceRepository,
) {}
async addCommunity(addCommunityDto: AddCommunityDto) {
try {
const spaceType = await this.spaceTypeRepository.findOne({
where: {
type: 'community',
},
});
await this.spaceRepository.save({
spaceName: addCommunityDto.communityName,
spaceType: { uuid: spaceType.uuid },
});
} catch (err) {
throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
async getCommunityByUuid(
communityUuid: string,
): Promise<GetCommunityByUuidInterface> {
try {
const community = await this.spaceRepository.findOne({
where: {
uuid: communityUuid,
spaceType: {
type: 'community',
},
},
relations: ['spaceType'],
});
if (
!community ||
!community.spaceType ||
community.spaceType.type !== 'community'
) {
throw new BadRequestException('Invalid community UUID');
}
return {
uuid: community.uuid,
createdAt: community.createdAt,
updatedAt: community.updatedAt,
name: community.spaceName,
type: community.spaceType.type,
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Community not found', HttpStatus.NOT_FOUND);
}
}
}
async getCommunityChildByUuid(
communityUuid: string,
getCommunityChildDto: GetCommunityChildDto,
): Promise<CommunityChildInterface> {
try {
const { includeSubSpaces, page, pageSize } = getCommunityChildDto;
const space = await this.spaceRepository.findOneOrFail({
where: { uuid: communityUuid },
relations: ['children', 'spaceType'],
});
if (!space || !space.spaceType || space.spaceType.type !== 'community') {
throw new BadRequestException('Invalid community UUID');
}
const totalCount = await this.spaceRepository.count({
where: { parent: { uuid: space.uuid } },
});
const children = await this.buildHierarchy(
space,
includeSubSpaces,
page,
pageSize,
);
return {
uuid: space.uuid,
name: space.spaceName,
type: space.spaceType.type,
totalCount,
children,
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Community not found', HttpStatus.NOT_FOUND);
}
}
}
private async buildHierarchy(
space: SpaceEntity,
includeSubSpaces: boolean,
page: number,
pageSize: number,
): Promise<CommunityChildInterface[]> {
const children = await this.spaceRepository.find({
where: { parent: { uuid: space.uuid } },
relations: ['spaceType'],
skip: (page - 1) * pageSize,
take: pageSize,
});
if (!children || children.length === 0 || !includeSubSpaces) {
return children
.filter((child) => child.spaceType.type !== 'community') // Filter remaining community type
.map((child) => ({
uuid: child.uuid,
name: child.spaceName,
type: child.spaceType.type,
}));
}
const childHierarchies = await Promise.all(
children
.filter((child) => child.spaceType.type !== 'community') // Filter remaining community type
.map(async (child) => ({
uuid: child.uuid,
name: child.spaceName,
type: child.spaceType.type,
children: await this.buildHierarchy(child, true, 1, pageSize),
})),
);
return childHierarchies;
}
async getCommunitiesByUserId(
userUuid: string,
): Promise<GetCommunityByUserUuidInterface[]> {
try {
const communities = await this.userSpaceRepository.find({
relations: ['space', 'space.spaceType'],
where: {
user: { uuid: userUuid },
space: { spaceType: { type: 'community' } },
},
});
if (communities.length === 0) {
throw new HttpException(
'this user has no communities',
HttpStatus.NOT_FOUND,
);
}
const spaces = communities.map((community) => ({
uuid: community.space.uuid,
name: community.space.spaceName,
type: community.space.spaceType.type,
}));
return spaces;
} catch (err) {
if (err instanceof HttpException) {
throw err;
} else {
throw new HttpException('user not found', HttpStatus.NOT_FOUND);
}
}
}
async addUserCommunity(addUserCommunityDto: AddUserCommunityDto) {
try {
await this.userSpaceRepository.save({
user: { uuid: addUserCommunityDto.userUuid },
space: { uuid: addUserCommunityDto.communityUuid },
});
} catch (err) {
if (err.code === '23505') {
throw new HttpException(
'User already belongs to this community',
HttpStatus.BAD_REQUEST,
);
}
throw new HttpException(
err.message || 'Internal Server Error',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async renameCommunityByUuid(
communityUuid: string,
updateCommunityDto: UpdateCommunityNameDto,
): Promise<RenameCommunityByUuidInterface> {
try {
const community = await this.spaceRepository.findOneOrFail({
where: { uuid: communityUuid },
relations: ['spaceType'],
});
if (
!community ||
!community.spaceType ||
community.spaceType.type !== 'community'
) {
throw new BadRequestException('Invalid community UUID');
}
await this.spaceRepository.update(
{ uuid: communityUuid },
{ spaceName: updateCommunityDto.communityName },
);
// Fetch the updated community
const updatedCommunity = await this.spaceRepository.findOneOrFail({
where: { uuid: communityUuid },
relations: ['spaceType'],
});
return {
uuid: updatedCommunity.uuid,
name: updatedCommunity.spaceName,
type: updatedCommunity.spaceType.type,
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Community not found', HttpStatus.NOT_FOUND);
}
}
}
}

View File

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

View File

@ -1,4 +1,4 @@
import AuthConfig from './auth.config'; import AuthConfig from './auth.config';
import AppConfig from './app.config'; import AppConfig from './app.config';
import JwtConfig from './jwt.config';
export default [AuthConfig, AppConfig]; export default [AuthConfig, AppConfig, JwtConfig];

View File

@ -4,21 +4,25 @@ import {
Controller, Controller,
Get, Get,
Post, Post,
UseGuards,
Query, Query,
Param, Param,
HttpException,
HttpStatus,
UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard';
import { import {
AddDeviceInGroupDto, AddDeviceInGroupDto,
AddDeviceInRoomDto, AddDeviceInRoomDto,
} from '../dtos/add.device.dto'; } from '../dtos/add.device.dto';
import { import {
GetDeviceByGroupIdDto, GetDeviceByGroupIdDto,
GetDeviceByRoomIdDto, GetDeviceByRoomUuidDto,
} from '../dtos/get.device.dto'; } from '../dtos/get.device.dto';
import { ControlDeviceDto } from '../dtos/control.device.dto'; import { ControlDeviceDto } from '../dtos/control.device.dto';
import { CheckRoomGuard } from 'src/guards/room.guard';
import { CheckGroupGuard } from 'src/guards/group.guard';
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
@ApiTags('Device Module') @ApiTags('Device Module')
@Controller({ @Controller({
@ -29,19 +33,38 @@ export class DeviceController {
constructor(private readonly deviceService: DeviceService) {} constructor(private readonly deviceService: DeviceService) {}
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard, CheckRoomGuard)
@Get('room') @Get('room')
async getDevicesByRoomId( async getDevicesByRoomId(
@Query() getDeviceByRoomIdDto: GetDeviceByRoomIdDto, @Query() getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto,
) { ) {
try { try {
return await this.deviceService.getDevicesByRoomId(getDeviceByRoomIdDto); return await this.deviceService.getDevicesByRoomId(
} catch (err) { getDeviceByRoomUuidDto,
throw new Error(err); );
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, CheckRoomGuard)
@Post('room')
async addDeviceInRoom(@Body() addDeviceInRoomDto: AddDeviceInRoomDto) {
try {
return await this.deviceService.addDeviceInRoom(addDeviceInRoomDto);
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard, CheckGroupGuard)
@Get('group') @Get('group')
async getDevicesByGroupId( async getDevicesByGroupId(
@Query() getDeviceByGroupIdDto: GetDeviceByGroupIdDto, @Query() getDeviceByGroupIdDto: GetDeviceByGroupIdDto,
@ -50,68 +73,81 @@ export class DeviceController {
return await this.deviceService.getDevicesByGroupId( return await this.deviceService.getDevicesByGroupId(
getDeviceByGroupIdDto, getDeviceByGroupIdDto,
); );
} catch (err) { } catch (error) {
throw new Error(err); throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard, CheckGroupGuard)
@Get(':deviceId')
async getDeviceDetailsByDeviceId(@Param('deviceId') deviceId: string) {
try {
return await this.deviceService.getDeviceDetailsByDeviceId(deviceId);
} catch (err) {
throw new Error(err);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':deviceId/functions')
async getDeviceInstructionByDeviceId(@Param('deviceId') deviceId: string) {
try {
return await this.deviceService.getDeviceInstructionByDeviceId(deviceId);
} catch (err) {
throw new Error(err);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':deviceId/functions/status')
async getDevicesInstructionStatus(@Param('deviceId') deviceId: string) {
try {
return await this.deviceService.getDevicesInstructionStatus(deviceId);
} catch (err) {
throw new Error(err);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Post('room')
async addDeviceInRoom(@Body() addDeviceInRoomDto: AddDeviceInRoomDto) {
try {
return await this.deviceService.addDeviceInRoom(addDeviceInRoomDto);
} catch (err) {
throw new Error(err);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Post('group') @Post('group')
async addDeviceInGroup(@Body() addDeviceInGroupDto: AddDeviceInGroupDto) { async addDeviceInGroup(@Body() addDeviceInGroupDto: AddDeviceInGroupDto) {
try { try {
return await this.deviceService.addDeviceInGroup(addDeviceInGroupDto); return await this.deviceService.addDeviceInGroup(addDeviceInGroupDto);
} catch (err) { } catch (error) {
throw new Error(err); throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':deviceUuid')
async getDeviceDetailsByDeviceId(@Param('deviceUuid') deviceUuid: string) {
try {
return await this.deviceService.getDeviceDetailsByDeviceId(deviceUuid);
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':deviceUuid/functions')
async getDeviceInstructionByDeviceId(
@Param('deviceUuid') deviceUuid: string,
) {
try {
return await this.deviceService.getDeviceInstructionByDeviceId(
deviceUuid,
);
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':deviceUuid/functions/status')
async getDevicesInstructionStatus(@Param('deviceUuid') deviceUuid: string) {
try {
return await this.deviceService.getDevicesInstructionStatus(deviceUuid);
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Post('control') @Post('control')
async controlDevice(@Body() controlDeviceDto: ControlDeviceDto) { async controlDevice(@Body() controlDeviceDto: ControlDeviceDto) {
try { try {
return await this.deviceService.controlDevice(controlDeviceDto); return await this.deviceService.controlDevice(controlDeviceDto);
} catch (err) { } catch (error) {
throw new Error(err); throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
} }

View File

@ -4,10 +4,34 @@ import { DeviceController } from './controllers/device.controller';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { ProductRepositoryModule } from '@app/common/modules/product/product.repository.module'; import { ProductRepositoryModule } from '@app/common/modules/product/product.repository.module';
import { ProductRepository } from '@app/common/modules/product/repositories'; import { ProductRepository } from '@app/common/modules/product/repositories';
import { DeviceRepositoryModule } from '@app/common/modules/device';
import {
DeviceRepository,
DeviceUserTypeRepository,
} from '@app/common/modules/device/repositories';
import { PermissionTypeRepository } from '@app/common/modules/permission/repositories';
import { SpaceRepository } from '@app/common/modules/space/repositories';
import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories';
import { GroupRepository } from '@app/common/modules/group/repositories';
import { GroupRepositoryModule } from '@app/common/modules/group/group.repository.module';
@Module({ @Module({
imports: [ConfigModule, ProductRepositoryModule], imports: [
ConfigModule,
ProductRepositoryModule,
DeviceRepositoryModule,
GroupRepositoryModule,
],
controllers: [DeviceController], controllers: [DeviceController],
providers: [DeviceService, ProductRepository], providers: [
DeviceService,
ProductRepository,
DeviceUserTypeRepository,
PermissionTypeRepository,
SpaceRepository,
DeviceRepository,
GroupDeviceRepository,
GroupRepository,
],
exports: [DeviceService], exports: [DeviceService],
}) })
export class DeviceModule {} export class DeviceModule {}

View File

@ -1,45 +1,37 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, IsNumberString } from 'class-validator'; import { IsNotEmpty, IsString } from 'class-validator';
export class AddDeviceInRoomDto { export class AddDeviceInRoomDto {
@ApiProperty({ @ApiProperty({
description: 'deviceId', description: 'deviceTuyaUuid',
required: true, required: true,
}) })
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public deviceId: string; public deviceTuyaUuid: string;
@ApiProperty({ @ApiProperty({
description: 'roomId', description: 'roomUuid',
required: true, required: true,
}) })
@IsNumberString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public roomId: string; public roomUuid: string;
} }
export class AddDeviceInGroupDto { export class AddDeviceInGroupDto {
@ApiProperty({ @ApiProperty({
description: 'deviceId', description: 'deviceUuid',
required: true, required: true,
}) })
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public deviceId: string; public deviceUuid: string;
@ApiProperty({ @ApiProperty({
description: 'homeId', description: 'groupUuid',
required: true, required: true,
}) })
@IsNumberString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public homeId: string; public groupUuid: string;
@ApiProperty({
description: 'groupId',
required: true,
})
@IsNumberString()
@IsNotEmpty()
public groupId: string;
} }

View File

@ -3,12 +3,12 @@ import { IsNotEmpty, IsString } from 'class-validator';
export class ControlDeviceDto { export class ControlDeviceDto {
@ApiProperty({ @ApiProperty({
description: 'deviceId', description: 'deviceUuid',
required: true, required: true,
}) })
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public deviceId: string; public deviceUuid: string;
@ApiProperty({ @ApiProperty({
description: 'code', description: 'code',

View File

@ -1,44 +1,21 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsNumberString } from 'class-validator'; import { IsNotEmpty, IsString } from 'class-validator';
export class GetDeviceByRoomIdDto { export class GetDeviceByRoomUuidDto {
@ApiProperty({ @ApiProperty({
description: 'roomId', description: 'roomUuid',
required: true, required: true,
}) })
@IsNumberString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public roomId: string; public roomUuid: string;
@ApiProperty({
description: 'pageSize',
required: true,
})
@IsNumberString()
@IsNotEmpty()
public pageSize: number;
} }
export class GetDeviceByGroupIdDto { export class GetDeviceByGroupIdDto {
@ApiProperty({ @ApiProperty({
description: 'groupId', description: 'groupUuid',
required: true, required: true,
}) })
@IsNumberString() @IsString()
@IsNotEmpty() @IsNotEmpty()
public groupId: string; public groupUuid: string;
@ApiProperty({
description: 'pageSize',
required: true,
})
@IsNumberString()
@IsNotEmpty()
public pageSize: number;
@ApiProperty({
description: 'pageNo',
required: true,
})
@IsNumberString()
@IsNotEmpty()
public pageNo: number;
} }

View File

@ -1,23 +1,28 @@
export interface GetDeviceDetailsInterface { export interface GetDeviceDetailsInterface {
result: { activeTime: number;
productId: string; assetId: string;
}; category: string;
success: boolean; categoryName: string;
msg: string; createTime: number;
} gatewayId: string;
export interface GetDevicesByRoomIdInterface { icon: string;
success: boolean; id: string;
msg: string; ip: string;
result: []; lat: string;
} localKey: string;
lon: string;
export interface GetDevicesByGroupIdInterface { model: string;
success: boolean; name: string;
msg: string; nodeId: string;
result: { online: boolean;
count: number; productId?: string;
data_list: []; productName?: string;
}; sub: boolean;
timeZone: string;
updateTime: number;
uuid: string;
productType: string;
productUuid: string;
} }
export interface addDeviceInRoomInterface { export interface addDeviceInRoomInterface {
@ -44,21 +49,13 @@ export interface GetDeviceDetailsFunctionsStatusInterface {
success: boolean; success: boolean;
msg: string; msg: string;
} }
export interface GetProductInterface {
productType: string;
productId: string;
}
export interface DeviceInstructionResponse { export interface DeviceInstructionResponse {
success: boolean; productUuid: string;
result: {
productId: string;
productType: string; productType: string;
functions: { functions: {
code: string; code: string;
values: any[]; values: any[];
dataType: string; dataType: string;
}[]; }[];
};
msg: string;
} }

View File

@ -1,4 +1,9 @@
import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import {
Injectable,
HttpException,
HttpStatus,
NotFoundException,
} from '@nestjs/common';
import { TuyaContext } from '@tuya/tuya-connector-nodejs'; import { TuyaContext } from '@tuya/tuya-connector-nodejs';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { import {
@ -10,26 +15,24 @@ import {
GetDeviceDetailsFunctionsInterface, GetDeviceDetailsFunctionsInterface,
GetDeviceDetailsFunctionsStatusInterface, GetDeviceDetailsFunctionsStatusInterface,
GetDeviceDetailsInterface, GetDeviceDetailsInterface,
GetDevicesByGroupIdInterface,
GetDevicesByRoomIdInterface,
GetProductInterface,
addDeviceInRoomInterface,
controlDeviceInterface, controlDeviceInterface,
} from '../interfaces/get.device.interface'; } from '../interfaces/get.device.interface';
import { import {
GetDeviceByGroupIdDto, GetDeviceByGroupIdDto,
GetDeviceByRoomIdDto, GetDeviceByRoomUuidDto,
} from '../dtos/get.device.dto'; } from '../dtos/get.device.dto';
import { ControlDeviceDto } from '../dtos/control.device.dto'; import { ControlDeviceDto } from '../dtos/control.device.dto';
import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter';
import { ProductRepository } from '@app/common/modules/product/repositories'; import { DeviceRepository } from '@app/common/modules/device/repositories';
import { GroupDeviceRepository } from '@app/common/modules/group-device/repositories';
@Injectable() @Injectable()
export class DeviceService { export class DeviceService {
private tuya: TuyaContext; private tuya: TuyaContext;
constructor( constructor(
private readonly configService: ConfigService, private readonly configService: ConfigService,
private readonly productRepository: ProductRepository, private readonly deviceRepository: DeviceRepository,
private readonly groupDeviceRepository: GroupDeviceRepository,
) { ) {
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY'); const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
const secretKey = this.configService.get<string>('auth-config.SECRET_KEY'); const secretKey = this.configService.get<string>('auth-config.SECRET_KEY');
@ -40,16 +43,31 @@ export class DeviceService {
}); });
} }
async getDevicesByRoomId(getDeviceByRoomIdDto: GetDeviceByRoomIdDto) { async getDevicesByRoomId(
getDeviceByRoomUuidDto: GetDeviceByRoomUuidDto,
): Promise<GetDeviceDetailsInterface[]> {
try { try {
const response = await this.getDevicesByRoomIdTuya(getDeviceByRoomIdDto); const devices = await this.deviceRepository.find({
where: {
spaceDevice: { uuid: getDeviceByRoomUuidDto.roomUuid },
},
relations: ['spaceDevice', 'productDevice'],
});
const devicesData = await Promise.all(
devices.map(async (device) => {
return { return {
success: response.success, ...(await this.getDeviceDetailsByDeviceIdTuya(
devices: response.result, device.deviceTuyaUuid,
msg: response.msg, )),
}; uuid: device.uuid,
productUuid: device.productDevice.uuid,
productType: device.productDevice.prodType,
} as GetDeviceDetailsInterface;
}),
);
return devicesData;
} catch (error) { } catch (error) {
// Handle the error here
throw new HttpException( throw new HttpException(
'Error fetching devices by room', 'Error fetching devices by room',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
@ -57,44 +75,26 @@ export class DeviceService {
} }
} }
async getDevicesByRoomIdTuya(
getDeviceByRoomIdDto: GetDeviceByRoomIdDto,
): Promise<GetDevicesByRoomIdInterface> {
try {
const path = `/v2.0/cloud/thing/space/device`;
const response = await this.tuya.request({
method: 'GET',
path,
query: {
space_ids: getDeviceByRoomIdDto.roomId,
page_size: getDeviceByRoomIdDto.pageSize,
},
});
return response as GetDevicesByRoomIdInterface;
} catch (error) {
throw new HttpException(
'Error fetching devices by room from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async getDevicesByGroupId(getDeviceByGroupIdDto: GetDeviceByGroupIdDto) { async getDevicesByGroupId(getDeviceByGroupIdDto: GetDeviceByGroupIdDto) {
try { try {
const devicesIds: GetDevicesByGroupIdInterface = const groupDevices = await this.groupDeviceRepository.find({
await this.getDevicesByGroupIdTuya(getDeviceByGroupIdDto); where: { group: { uuid: getDeviceByGroupIdDto.groupUuid } },
const devicesDetails = await Promise.all( relations: ['device'],
devicesIds.result.data_list.map(async (device: any) => { });
const deviceData = await this.getDeviceDetailsByDeviceId( const devicesData = await Promise.all(
device.dev_id, groupDevices.map(async (device) => {
); return {
return deviceData.result; ...(await this.getDeviceDetailsByDeviceIdTuya(
device.device.deviceTuyaUuid,
)),
uuid: device.uuid,
productUuid: device.device.productDevice.uuid,
productType: device.device.productDevice.prodType,
} as GetDeviceDetailsInterface;
}), }),
); );
return {
success: devicesIds.success, return devicesData;
devices: devicesDetails,
msg: devicesIds.msg,
};
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error fetching devices by group', 'Error fetching devices by group',
@ -103,106 +103,74 @@ export class DeviceService {
} }
} }
async getDevicesByGroupIdTuya(
getDeviceByGroupIdDto: GetDeviceByGroupIdDto,
): Promise<GetDevicesByGroupIdInterface> {
try {
const path = `/v2.0/cloud/thing/group/${getDeviceByGroupIdDto.groupId}/devices`;
const response = await this.tuya.request({
method: 'GET',
path,
query: {
page_size: getDeviceByGroupIdDto.pageSize,
page_no: getDeviceByGroupIdDto.pageNo,
},
});
return response as GetDevicesByGroupIdInterface;
} catch (error) {
throw new HttpException(
'Error fetching devices by group from Tuya',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async addDeviceInRoom(addDeviceInRoomDto: AddDeviceInRoomDto) { async addDeviceInRoom(addDeviceInRoomDto: AddDeviceInRoomDto) {
const response = await this.addDeviceInRoomTuya(addDeviceInRoomDto); try {
const device = await this.getDeviceDetailsByDeviceIdTuya(
addDeviceInRoomDto.deviceTuyaUuid,
);
if (response.success) { if (!device.productUuid) {
return { throw new Error('Product UUID is missing for the device.');
success: response.success, }
result: response.result,
msg: response.msg, await this.deviceRepository.save({
}; deviceTuyaUuid: addDeviceInRoomDto.deviceTuyaUuid,
} else { spaceDevice: { uuid: addDeviceInRoomDto.roomUuid },
productDevice: { uuid: device.productUuid },
});
return { message: 'device added in room successfully' };
} catch (error) {
if (error.code === '23505') {
throw new HttpException( throw new HttpException(
response.msg || 'Unknown error', 'Device already exists in the room',
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
); );
} } else {
}
async addDeviceInRoomTuya(
addDeviceInRoomDto: AddDeviceInRoomDto,
): Promise<addDeviceInRoomInterface> {
try {
const path = `/v2.0/cloud/thing/${addDeviceInRoomDto.deviceId}/transfer`;
const response = await this.tuya.request({
method: 'POST',
path,
body: {
space_id: addDeviceInRoomDto.roomId,
},
});
return response as addDeviceInRoomInterface;
} catch (error) {
throw new HttpException( throw new HttpException(
'Error adding device in room from Tuya', 'Failed to add device in room',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
} }
}
async addDeviceInGroup(addDeviceInGroupDto: AddDeviceInGroupDto) { async addDeviceInGroup(addDeviceInGroupDto: AddDeviceInGroupDto) {
const response = await this.addDeviceInGroupTuya(addDeviceInGroupDto); try {
await this.groupDeviceRepository.save({
if (response.success) { device: { uuid: addDeviceInGroupDto.deviceUuid },
return { group: { uuid: addDeviceInGroupDto.groupUuid },
success: response.success, });
result: response.result, return { message: 'device added in group successfully' };
msg: response.msg, } catch (error) {
}; if (error.code === '23505') {
} else {
throw new HttpException( throw new HttpException(
response.msg || 'Unknown error', 'Device already exists in the group',
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
); );
} } else {
}
async addDeviceInGroupTuya(
addDeviceInGroupDto: AddDeviceInGroupDto,
): Promise<addDeviceInRoomInterface> {
try {
const path = `/v2.0/cloud/thing/group/${addDeviceInGroupDto.groupId}/device`;
const response = await this.tuya.request({
method: 'PUT',
path,
body: {
space_id: addDeviceInGroupDto.homeId,
device_ids: addDeviceInGroupDto.deviceId,
},
});
return response as addDeviceInRoomInterface;
} catch (error) {
throw new HttpException( throw new HttpException(
'Error adding device in group from Tuya', 'Failed to add device in group',
HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR,
); );
} }
} }
}
async controlDevice(controlDeviceDto: ControlDeviceDto) { async controlDevice(controlDeviceDto: ControlDeviceDto) {
const response = await this.controlDeviceTuya(controlDeviceDto); try {
const deviceDetails = await this.deviceRepository.findOne({
where: {
uuid: controlDeviceDto.deviceUuid,
},
});
if (!deviceDetails || !deviceDetails.deviceTuyaUuid) {
throw new NotFoundException('Device Not Found');
}
const response = await this.controlDeviceTuya(
deviceDetails.deviceTuyaUuid,
controlDeviceDto,
);
if (response.success) { if (response.success) {
return response; return response;
@ -212,12 +180,16 @@ export class DeviceService {
HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST,
); );
} }
} catch (error) {
throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
}
} }
async controlDeviceTuya( async controlDeviceTuya(
deviceUuid: string,
controlDeviceDto: ControlDeviceDto, controlDeviceDto: ControlDeviceDto,
): Promise<controlDeviceInterface> { ): Promise<controlDeviceInterface> {
try { try {
const path = `/v1.0/iot-03/devices/${controlDeviceDto.deviceId}/commands`; const path = `/v1.0/iot-03/devices/${deviceUuid}/commands`;
const response = await this.tuya.request({ const response = await this.tuya.request({
method: 'POST', method: 'POST',
path, path,
@ -237,20 +209,30 @@ export class DeviceService {
} }
} }
async getDeviceDetailsByDeviceId(deviceId: string) { async getDeviceDetailsByDeviceId(deviceUuid: string) {
try { try {
const response = await this.getDeviceDetailsByDeviceIdTuya(deviceId); const deviceDetails = await this.deviceRepository.findOne({
where: {
uuid: deviceUuid,
},
});
if (!deviceDetails) {
throw new NotFoundException('Device Not Found');
}
const response = await this.getDeviceDetailsByDeviceIdTuya(
deviceDetails.deviceTuyaUuid,
);
return { return {
success: response.success, ...response,
result: response.result, uuid: deviceDetails.uuid,
msg: response.msg, productUuid: deviceDetails.productDevice.uuid,
productType: deviceDetails.productDevice.prodType,
}; };
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
'Error fetching device details',
HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
async getDeviceDetailsByDeviceIdTuya( async getDeviceDetailsByDeviceIdTuya(
@ -262,19 +244,21 @@ export class DeviceService {
method: 'GET', method: 'GET',
path, path,
}); });
// Convert keys to camel case // Convert keys to camel case
const camelCaseResponse = convertKeysToCamelCase(response); const camelCaseResponse = convertKeysToCamelCase(response);
const productType: string = await this.getProductTypeByProductId( const deviceDetails = await this.deviceRepository.findOne({
camelCaseResponse.result.productId, where: {
); deviceTuyaUuid: deviceId,
return {
result: {
...camelCaseResponse.result,
productType: productType,
}, },
success: camelCaseResponse.success, relations: ['productDevice'],
msg: camelCaseResponse.msg, });
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { productName, productId, ...rest } = camelCaseResponse.result;
return {
...rest,
productUuid: deviceDetails.productDevice.uuid,
} as GetDeviceDetailsInterface; } as GetDeviceDetailsInterface;
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
@ -283,69 +267,29 @@ export class DeviceService {
); );
} }
} }
async getProductIdByDeviceId(deviceId: string) {
try {
const deviceDetails: GetDeviceDetailsInterface =
await this.getDeviceDetailsByDeviceId(deviceId);
return deviceDetails.result.productId;
} catch (error) {
throw new HttpException(
'Error fetching product id by device id',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async getProductByProductId(productId: string): Promise<GetProductInterface> {
try {
const product = await this.productRepository
.createQueryBuilder('product')
.where('product.prodId = :productId', { productId })
.select(['product.prodId', 'product.prodType'])
.getOne();
if (product) {
return {
productType: product.prodType,
productId: product.prodId,
};
} else {
throw new HttpException('Product not found', HttpStatus.NOT_FOUND);
}
} catch (error) {
throw new HttpException(
'Error fetching product by product id from db',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async getProductTypeByProductId(productId: string) {
try {
const product = await this.getProductByProductId(productId);
return product.productType;
} catch (error) {
throw new HttpException(
'Error getting product type by product id',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async getDeviceInstructionByDeviceId( async getDeviceInstructionByDeviceId(
deviceId: string, deviceUuid: string,
): Promise<DeviceInstructionResponse> { ): Promise<DeviceInstructionResponse> {
try { try {
const response = await this.getDeviceInstructionByDeviceIdTuya(deviceId); const deviceDetails = await this.deviceRepository.findOne({
where: {
uuid: deviceUuid,
},
relations: ['productDevice'],
});
const productId: string = await this.getProductIdByDeviceId(deviceId); if (!deviceDetails) {
const productType: string = throw new NotFoundException('Device Not Found');
await this.getProductTypeByProductId(productId); }
const response = await this.getDeviceInstructionByDeviceIdTuya(
deviceDetails.deviceTuyaUuid,
);
return { return {
success: response.success, productUuid: deviceDetails.productDevice.uuid,
result: { productType: deviceDetails.productDevice.prodType,
productId: productId,
productType: productType,
functions: response.result.functions.map((fun: any) => { functions: response.result.functions.map((fun: any) => {
return { return {
code: fun.code, code: fun.code,
@ -353,14 +297,9 @@ export class DeviceService {
dataType: fun.type, dataType: fun.type,
}; };
}), }),
},
msg: response.msg,
}; };
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException('Device Not Found', HttpStatus.NOT_FOUND);
'Error fetching device functions by device id',
HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
@ -381,20 +320,26 @@ export class DeviceService {
); );
} }
} }
async getDevicesInstructionStatus(deviceId: string) { async getDevicesInstructionStatus(deviceUuid: string) {
try { try {
const deviceStatus = await this.getDevicesInstructionStatusTuya(deviceId); const deviceDetails = await this.deviceRepository.findOne({
const productId: string = await this.getProductIdByDeviceId(deviceId); where: {
const productType: string = uuid: deviceUuid,
await this.getProductTypeByProductId(productId);
return {
result: {
productId: productId,
productType: productType,
status: deviceStatus.result[0].status,
}, },
success: deviceStatus.success, relations: ['productDevice'],
msg: deviceStatus.msg, });
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) { } catch (error) {
throw new HttpException( throw new HttpException(
@ -405,7 +350,7 @@ export class DeviceService {
} }
async getDevicesInstructionStatusTuya( async getDevicesInstructionStatusTuya(
deviceId: string, deviceUuid: string,
): Promise<GetDeviceDetailsFunctionsStatusInterface> { ): Promise<GetDeviceDetailsFunctionsStatusInterface> {
try { try {
const path = `/v1.0/iot-03/devices/status`; const path = `/v1.0/iot-03/devices/status`;
@ -413,10 +358,10 @@ export class DeviceService {
method: 'GET', method: 'GET',
path, path,
query: { query: {
device_ids: deviceId, device_ids: deviceUuid,
}, },
}); });
return response as unknown as GetDeviceDetailsFunctionsStatusInterface; return response as GetDeviceDetailsFunctionsStatusInterface;
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException(
'Error fetching device functions status from Tuya', 'Error fetching device functions status from Tuya',

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