SPRINT-1 related tasks done

This commit is contained in:
VirajBrainvire
2024-04-18 09:16:59 +05:30
68 changed files with 2560 additions and 148 deletions

View File

@ -9,6 +9,8 @@ import { HomeEntity } from '../modules/home/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';
@Module({
imports: [
@ -31,11 +33,13 @@ import { PermissionTypeEntity } from '../modules/permission/entities';
ProductEntity,
DeviceUserPermissionEntity,
DeviceEntity,
PermissionTypeEntity
PermissionTypeEntity,
SpaceEntity,
SpaceTypeEntity,
],
namingStrategy: new SnakeNamingStrategy(),
synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))),
logging: true,
logging: false,
extra: {
charset: 'utf8mb4',
max: 20, // set pool max size

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,33 @@
import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
import { SpaceDto } from '../dtos';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { SpaceTypeEntity } from '../../space-type/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;
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

@ -9,6 +9,10 @@ import { RoomModule } from './room/room.module';
import { GroupModule } from './group/group.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({
imports: [
ConfigModule.forRoot({
@ -16,6 +20,11 @@ import { UserDevicePermissionModule } from './user-device-permission/user-device
}),
AuthenticationModule,
UserModule,
CommunityModule,
BuildingModule,
FloorModule,
UnitModule,
RoomModule,
HomeModule,
RoomModule,
GroupModule,

View File

@ -0,0 +1,16 @@
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';
@Module({
imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule],
controllers: [BuildingController],
providers: [BuildingService, SpaceRepository, SpaceTypeRepository],
exports: [BuildingService],
})
export class BuildingModule {}

View File

@ -0,0 +1,115 @@
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 } 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';
@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)
@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,23 @@
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);
}
}

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

View File

@ -0,0 +1,255 @@
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 } from '../dtos';
import {
BuildingChildInterface,
BuildingParentInterface,
GetBuildingByUuidInterface,
RenameBuildingByUuidInterface,
} from '../interface/building.interface';
import { SpaceEntity } from '@app/common/modules/space/entities';
import { UpdateBuildingNameDto } from '../dtos/update.building.dto';
@Injectable()
export class BuildingService {
constructor(
private readonly spaceRepository: SpaceRepository,
private readonly spaceTypeRepository: SpaceTypeRepository,
) {}
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 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,16 @@
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';
@Module({
imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule],
controllers: [CommunityController],
providers: [CommunityService, SpaceRepository, SpaceTypeRepository],
exports: [CommunityService],
})
export class CommunityModule {}

View File

@ -0,0 +1,100 @@
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 } from '../dtos/add.community.dto';
import { GetCommunityChildDto } from '../dtos/get.community.dto';
import { UpdateCommunityNameDto } from '../dtos/update.community.dto';
@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)
@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,16 @@
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);
}
}

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

View File

@ -0,0 +1,196 @@
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 } from '../dtos';
import {
CommunityChildInterface,
GetCommunityByUuidInterface,
RenameCommunityByUuidInterface,
} from '../interface/community.interface';
import { SpaceEntity } from '@app/common/modules/space/entities';
import { UpdateCommunityNameDto } from '../dtos/update.community.dto';
@Injectable()
export class CommunityService {
constructor(
private readonly spaceRepository: SpaceRepository,
private readonly spaceTypeRepository: SpaceTypeRepository,
) {}
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 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

@ -0,0 +1,114 @@
import { FloorService } from '../services/floor.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 { AddFloorDto } from '../dtos/add.floor.dto';
import { GetFloorChildDto } from '../dtos/get.floor.dto';
import { UpdateFloorNameDto } from '../dtos/update.floor.dto';
import { CheckBuildingTypeGuard } from 'src/guards/building.type.guard';
@ApiTags('Floor Module')
@Controller({
version: '1',
path: 'floor',
})
export class FloorController {
constructor(private readonly floorService: FloorService) {}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, CheckBuildingTypeGuard)
@Post()
async addFloor(@Body() addFloorDto: AddFloorDto) {
try {
await this.floorService.addFloor(addFloorDto);
return { message: 'Floor added successfully' };
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':floorUuid')
async getFloorByUuid(@Param('floorUuid') floorUuid: string) {
try {
const floor = await this.floorService.getFloorByUuid(floorUuid);
return floor;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get('child/:floorUuid')
async getFloorChildByUuid(
@Param('floorUuid') floorUuid: string,
@Query() query: GetFloorChildDto,
) {
try {
const floor = await this.floorService.getFloorChildByUuid(
floorUuid,
query,
);
return floor;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get('parent/:floorUuid')
async getFloorParentByUuid(@Param('floorUuid') floorUuid: string) {
try {
const floor = await this.floorService.getFloorParentByUuid(floorUuid);
return floor;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Put('rename/:floorUuid')
async renameFloorByUuid(
@Param('floorUuid') floorUuid: string,
@Body() updateFloorNameDto: UpdateFloorNameDto,
) {
try {
const floor = await this.floorService.renameFloorByUuid(
floorUuid,
updateFloorNameDto,
);
return floor;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}

View File

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

View File

@ -0,0 +1,23 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class AddFloorDto {
@ApiProperty({
description: 'floorName',
required: true,
})
@IsString()
@IsNotEmpty()
public floorName: string;
@ApiProperty({
description: 'buildingUuid',
required: true,
})
@IsString()
@IsNotEmpty()
public buildingUuid: string;
constructor(dto: Partial<AddFloorDto>) {
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 GetFloorDto {
@ApiProperty({
description: 'floorUuid',
required: true,
})
@IsString()
@IsNotEmpty()
public floorUuid: string;
}
export class GetFloorChildDto {
@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;
}

1
src/floor/dtos/index.ts Normal file
View File

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

View File

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

16
src/floor/floor.module.ts Normal file
View File

@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { FloorService } from './services/floor.service';
import { FloorController } from './controllers/floor.controller';
import { ConfigModule } from '@nestjs/config';
import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module';
import { SpaceRepository } from '@app/common/modules/space/repositories';
import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module';
import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories';
@Module({
imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule],
controllers: [FloorController],
providers: [FloorService, SpaceRepository, SpaceTypeRepository],
exports: [FloorService],
})
export class FloorModule {}

View File

@ -0,0 +1,26 @@
export interface GetFloorByUuidInterface {
uuid: string;
createdAt: Date;
updatedAt: Date;
name: string;
type: string;
}
export interface FloorChildInterface {
uuid: string;
name: string;
type: string;
totalCount?: number;
children?: FloorChildInterface[];
}
export interface FloorParentInterface {
uuid: string;
name: string;
type: string;
parent?: FloorParentInterface;
}
export interface RenameFloorByUuidInterface {
uuid: string;
name: string;
type: string;
}

View File

@ -0,0 +1,236 @@
import { GetFloorChildDto } from '../dtos/get.floor.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 { AddFloorDto } from '../dtos';
import {
FloorChildInterface,
FloorParentInterface,
GetFloorByUuidInterface,
RenameFloorByUuidInterface,
} from '../interface/floor.interface';
import { SpaceEntity } from '@app/common/modules/space/entities';
import { UpdateFloorNameDto } from '../dtos/update.floor.dto';
@Injectable()
export class FloorService {
constructor(
private readonly spaceRepository: SpaceRepository,
private readonly spaceTypeRepository: SpaceTypeRepository,
) {}
async addFloor(addFloorDto: AddFloorDto) {
try {
const spaceType = await this.spaceTypeRepository.findOne({
where: {
type: 'floor',
},
});
await this.spaceRepository.save({
spaceName: addFloorDto.floorName,
parent: { uuid: addFloorDto.buildingUuid },
spaceType: { uuid: spaceType.uuid },
});
} catch (err) {
throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
async getFloorByUuid(floorUuid: string): Promise<GetFloorByUuidInterface> {
try {
const floor = await this.spaceRepository.findOne({
where: {
uuid: floorUuid,
spaceType: {
type: 'floor',
},
},
relations: ['spaceType'],
});
if (!floor || !floor.spaceType || floor.spaceType.type !== 'floor') {
throw new BadRequestException('Invalid floor UUID');
}
return {
uuid: floor.uuid,
createdAt: floor.createdAt,
updatedAt: floor.updatedAt,
name: floor.spaceName,
type: floor.spaceType.type,
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Floor not found', HttpStatus.NOT_FOUND);
}
}
}
async getFloorChildByUuid(
floorUuid: string,
getFloorChildDto: GetFloorChildDto,
): Promise<FloorChildInterface> {
try {
const { includeSubSpaces, page, pageSize } = getFloorChildDto;
const space = await this.spaceRepository.findOneOrFail({
where: { uuid: floorUuid },
relations: ['children', 'spaceType'],
});
if (!space || !space.spaceType || space.spaceType.type !== 'floor') {
throw new BadRequestException('Invalid floor 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('Floor not found', HttpStatus.NOT_FOUND);
}
}
}
private async buildHierarchy(
space: SpaceEntity,
includeSubSpaces: boolean,
page: number,
pageSize: number,
): Promise<FloorChildInterface[]> {
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 !== 'floor' &&
child.spaceType.type !== 'building' &&
child.spaceType.type !== 'community',
) // Filter remaining floor and 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 !== 'floor' &&
child.spaceType.type !== 'building' &&
child.spaceType.type !== 'community',
) // Filter remaining floor and 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 getFloorParentByUuid(floorUuid: string): Promise<FloorParentInterface> {
try {
const floor = await this.spaceRepository.findOne({
where: {
uuid: floorUuid,
spaceType: {
type: 'floor',
},
},
relations: ['spaceType', 'parent', 'parent.spaceType'],
});
if (!floor || !floor.spaceType || floor.spaceType.type !== 'floor') {
throw new BadRequestException('Invalid floor UUID');
}
return {
uuid: floor.uuid,
name: floor.spaceName,
type: floor.spaceType.type,
parent: {
uuid: floor.parent.uuid,
name: floor.parent.spaceName,
type: floor.parent.spaceType.type,
},
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Floor not found', HttpStatus.NOT_FOUND);
}
}
}
async renameFloorByUuid(
floorUuid: string,
updateFloorDto: UpdateFloorNameDto,
): Promise<RenameFloorByUuidInterface> {
try {
const floor = await this.spaceRepository.findOneOrFail({
where: { uuid: floorUuid },
relations: ['spaceType'],
});
if (!floor || !floor.spaceType || floor.spaceType.type !== 'floor') {
throw new BadRequestException('Invalid floor UUID');
}
await this.spaceRepository.update(
{ uuid: floorUuid },
{ spaceName: updateFloorDto.floorName },
);
// Fetch the updated floor
const updatedFloor = await this.spaceRepository.findOneOrFail({
where: { uuid: floorUuid },
relations: ['spaceType'],
});
return {
uuid: updatedFloor.uuid,
name: updatedFloor.spaceName,
type: updatedFloor.spaceType.type,
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Floor not found', HttpStatus.NOT_FOUND);
}
}
}
}

View File

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

View File

@ -0,0 +1,66 @@
import { SpaceRepository } from '@app/common/modules/space/repositories';
import {
Injectable,
CanActivate,
HttpStatus,
BadRequestException,
ExecutionContext,
} from '@nestjs/common';
@Injectable()
export class CheckBuildingTypeGuard implements CanActivate {
constructor(private readonly spaceRepository: SpaceRepository) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
try {
const { floorName, buildingUuid } = req.body;
if (!floorName) {
throw new BadRequestException('floorName is required');
}
if (!buildingUuid) {
throw new BadRequestException('buildingUuid is required');
}
await this.checkBuildingIsBuildingType(buildingUuid);
return true;
} catch (error) {
this.handleGuardError(error, context);
return false;
}
}
async checkBuildingIsBuildingType(buildingUuid: string) {
const buildingData = await this.spaceRepository.findOne({
where: { uuid: buildingUuid },
relations: ['spaceType'],
});
if (
!buildingData ||
!buildingData.spaceType ||
buildingData.spaceType.type !== 'building'
) {
throw new BadRequestException('Invalid building UUID');
}
}
private handleGuardError(error: Error, context: ExecutionContext) {
const response = context.switchToHttp().getResponse();
console.error(error);
if (error instanceof BadRequestException) {
response
.status(HttpStatus.BAD_REQUEST)
.json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message });
} else {
response.status(HttpStatus.NOT_FOUND).json({
statusCode: HttpStatus.NOT_FOUND,
message: 'Building not found',
});
}
}
}

View File

@ -0,0 +1,67 @@
import {
CanActivate,
ExecutionContext,
Injectable,
HttpStatus,
} from '@nestjs/common';
import { SpaceRepository } from '@app/common/modules/space/repositories';
import { BadRequestException } from '@nestjs/common';
@Injectable()
export class CheckCommunityTypeGuard implements CanActivate {
constructor(private readonly spaceRepository: SpaceRepository) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
try {
const { buildingName, communityUuid } = req.body;
if (!buildingName) {
throw new BadRequestException('buildingName is required');
}
if (!communityUuid) {
throw new BadRequestException('communityUuid is required');
}
await this.checkCommunityIsCommunityType(communityUuid);
return true;
} catch (error) {
this.handleGuardError(error, context);
return false;
}
}
private async checkCommunityIsCommunityType(communityUuid: string) {
const communityData = await this.spaceRepository.findOne({
where: { uuid: communityUuid },
relations: ['spaceType'],
});
if (
!communityData ||
!communityData.spaceType ||
communityData.spaceType.type !== 'community'
) {
throw new BadRequestException('Invalid community UUID');
}
}
private handleGuardError(error: Error, context: ExecutionContext) {
const response = context.switchToHttp().getResponse();
console.error(error);
if (error instanceof BadRequestException) {
response
.status(HttpStatus.BAD_REQUEST)
.json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message });
} else {
response.status(HttpStatus.NOT_FOUND).json({
statusCode: HttpStatus.NOT_FOUND,
message: 'Community not found',
});
}
}
}

View File

@ -0,0 +1,66 @@
import { SpaceRepository } from '@app/common/modules/space/repositories';
import {
Injectable,
CanActivate,
HttpStatus,
ExecutionContext,
BadRequestException,
} from '@nestjs/common';
@Injectable()
export class CheckFloorTypeGuard implements CanActivate {
constructor(private readonly spaceRepository: SpaceRepository) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
try {
const { unitName, floorUuid } = req.body;
if (!unitName) {
throw new BadRequestException('unitName is required');
}
if (!floorUuid) {
throw new BadRequestException('floorUuid is required');
}
await this.checkFloorIsFloorType(floorUuid);
return true;
} catch (error) {
this.handleGuardError(error, context);
return false;
}
}
async checkFloorIsFloorType(floorUuid: string) {
const floorData = await this.spaceRepository.findOne({
where: { uuid: floorUuid },
relations: ['spaceType'],
});
if (
!floorData ||
!floorData.spaceType ||
floorData.spaceType.type !== 'floor'
) {
throw new BadRequestException('Invalid floor UUID');
}
}
private handleGuardError(error: Error, context: ExecutionContext) {
const response = context.switchToHttp().getResponse();
console.error(error);
if (error instanceof BadRequestException) {
response
.status(HttpStatus.BAD_REQUEST)
.json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message });
} else {
response.status(HttpStatus.NOT_FOUND).json({
statusCode: HttpStatus.NOT_FOUND,
message: 'Floor not found',
});
}
}
}

View File

@ -0,0 +1,66 @@
import { SpaceRepository } from '@app/common/modules/space/repositories';
import {
Injectable,
CanActivate,
HttpStatus,
BadRequestException,
ExecutionContext,
} from '@nestjs/common';
@Injectable()
export class CheckUnitTypeGuard implements CanActivate {
constructor(private readonly spaceRepository: SpaceRepository) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
try {
const { roomName, unitUuid } = req.body;
if (!roomName) {
throw new BadRequestException('roomName is required');
}
if (!unitUuid) {
throw new BadRequestException('unitUuid is required');
}
await this.checkFloorIsFloorType(unitUuid);
return true;
} catch (error) {
this.handleGuardError(error, context);
return false;
}
}
async checkFloorIsFloorType(unitUuid: string) {
const unitData = await this.spaceRepository.findOne({
where: { uuid: unitUuid },
relations: ['spaceType'],
});
if (
!unitData ||
!unitData.spaceType ||
unitData.spaceType.type !== 'unit'
) {
throw new BadRequestException('Invalid unit UUID');
}
}
private handleGuardError(error: Error, context: ExecutionContext) {
const response = context.switchToHttp().getResponse();
console.error(error);
if (error instanceof BadRequestException) {
response
.status(HttpStatus.BAD_REQUEST)
.json({ statusCode: HttpStatus.BAD_REQUEST, message: error.message });
} else {
response.status(HttpStatus.NOT_FOUND).json({
statusCode: HttpStatus.NOT_FOUND,
message: 'Unit not found',
});
}
}
}

View File

@ -25,7 +25,14 @@ async function bootstrap() {
setupSwaggerAuthentication(app);
app.useGlobalPipes(new ValidationPipe());
app.useGlobalPipes(
new ValidationPipe({
transform: true, // Auto-transform payloads to their DTO instances.
transformOptions: {
enableImplicitConversion: true, // Convert incoming payloads to their DTO instances if possible.
},
}),
);
await app.listen(process.env.PORT || 4000);
}

View File

@ -3,14 +3,18 @@ import {
Body,
Controller,
Get,
Post,
UseGuards,
Query,
HttpException,
HttpStatus,
Param,
Post,
Put,
UseGuards,
} from '@nestjs/common';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard } from '../../../libs/common/src/guards/jwt.auth.guard';
import { AddRoomDto } from '../dtos/add.room.dto';
import { UpdateRoomNameDto } from '../dtos/update.room.dto';
import { CheckUnitTypeGuard } from 'src/guards/unit.type.guard';
@ApiTags('Room Module')
@Controller({
@ -21,33 +25,68 @@ export class RoomController {
constructor(private readonly roomService: RoomService) {}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get()
async getRoomsByHomeId(@Query('homeId') homeId: string) {
try {
return await this.roomService.getRoomsByHomeId(homeId);
} catch (err) {
throw new Error(err);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':roomId')
async getRoomsByRoomId(@Param('roomId') roomId: string) {
try {
return await this.roomService.getRoomsByRoomId(roomId);
} catch (err) {
throw new Error(err);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@UseGuards(JwtAuthGuard, CheckUnitTypeGuard)
@Post()
async addRoom(@Body() addRoomDto: AddRoomDto) {
try {
return await this.roomService.addRoom(addRoomDto);
} catch (err) {
throw new Error(err);
await this.roomService.addRoom(addRoomDto);
return { message: 'Room added successfully' };
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':roomUuid')
async getRoomByUuid(@Param('roomUuid') roomUuid: string) {
try {
const room = await this.roomService.getRoomByUuid(roomUuid);
return room;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get('parent/:roomUuid')
async getRoomParentByUuid(@Param('roomUuid') roomUuid: string) {
try {
const room = await this.roomService.getRoomParentByUuid(roomUuid);
return room;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Put('rename/:roomUuid')
async renameRoomByUuid(
@Param('roomUuid') roomUuid: string,
@Body() updateRoomNameDto: UpdateRoomNameDto,
) {
try {
const room = await this.roomService.renameRoomByUuid(
roomUuid,
updateRoomNameDto,
);
return room;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}

View File

@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString, IsNumberString } from 'class-validator';
import { IsNotEmpty, IsString } from 'class-validator';
export class AddRoomDto {
@ApiProperty({
@ -11,10 +11,13 @@ export class AddRoomDto {
public roomName: string;
@ApiProperty({
description: 'homeId',
description: 'unitUuid',
required: true,
})
@IsNumberString()
@IsString()
@IsNotEmpty()
public homeId: string;
public unitUuid: string;
constructor(dto: Partial<AddRoomDto>) {
Object.assign(this, dto);
}
}

View File

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

View File

@ -0,0 +1,19 @@
export interface GetRoomByUuidInterface {
uuid: string;
createdAt: Date;
updatedAt: Date;
name: string;
type: string;
}
export interface RoomParentInterface {
uuid: string;
name: string;
type: string;
parent?: RoomParentInterface;
}
export interface RenameRoomByUuidInterface {
uuid: string;
name: string;
type: string;
}

View File

@ -1,12 +0,0 @@
export class GetRoomDetailsInterface {
result: {
id: string;
name: string;
root_id: string;
};
}
export class GetRoomsIdsInterface {
result: {
data: [];
};
}

View File

@ -2,10 +2,15 @@ import { Module } from '@nestjs/common';
import { RoomService } from './services/room.service';
import { RoomController } from './controllers/room.controller';
import { ConfigModule } from '@nestjs/config';
import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module';
import { SpaceRepository } from '@app/common/modules/space/repositories';
import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module';
import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories';
@Module({
imports: [ConfigModule],
imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule],
controllers: [RoomController],
providers: [RoomService],
providers: [RoomService, SpaceRepository, SpaceTypeRepository],
exports: [RoomService],
})
export class RoomModule {}

View File

@ -1,115 +1,144 @@
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { TuyaContext } from '@tuya/tuya-connector-nodejs';
import { ConfigService } from '@nestjs/config';
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 { AddRoomDto } from '../dtos';
import {
GetRoomDetailsInterface,
GetRoomsIdsInterface,
} from '../interfaces/get.room.interface';
RoomParentInterface,
GetRoomByUuidInterface,
RenameRoomByUuidInterface,
} from '../interface/room.interface';
import { UpdateRoomNameDto } from '../dtos/update.room.dto';
@Injectable()
export class RoomService {
private tuya: TuyaContext;
constructor(private readonly configService: ConfigService) {
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
const secretKey = this.configService.get<string>('auth-config.SECRET_KEY');
// const clientId = this.configService.get<string>('auth-config.CLIENT_ID');
this.tuya = new TuyaContext({
baseUrl: 'https://openapi.tuyaeu.com',
accessKey,
secretKey,
});
}
constructor(
private readonly spaceRepository: SpaceRepository,
private readonly spaceTypeRepository: SpaceTypeRepository,
) {}
async getRoomsByHomeId(homeId: string) {
try {
const roomsIds = await this.getRoomsIds(homeId);
const roomsDetails = await Promise.all(
roomsIds.result.data.map(async (roomId) => {
const roomData = await this.getRoomDetails(roomId);
return {
roomId: roomData?.result?.id,
roomName: roomData ? roomData.result.name : null,
};
}),
);
return roomsDetails;
} catch (error) {
throw new HttpException(
'Error fetching rooms',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async getRoomsIds(homeId: string): Promise<GetRoomsIdsInterface> {
try {
const path = `/v2.0/cloud/space/child`;
const response = await this.tuya.request({
method: 'GET',
path,
query: { space_id: homeId },
});
return response as GetRoomsIdsInterface;
} catch (error) {
throw new HttpException(
'Error fetching rooms ids',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async getRoomDetails(roomId: string): Promise<GetRoomDetailsInterface> {
// Added return type
try {
const path = `/v2.0/cloud/space/${roomId}`;
const response = await this.tuya.request({
method: 'GET',
path,
});
return response as GetRoomDetailsInterface; // Cast response to RoomData
} catch (error) {
throw new HttpException(
'Error fetching rooms details',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async addRoom(addRoomDto: AddRoomDto) {
try {
const path = `/v2.0/cloud/space/creation`;
const data = await this.tuya.request({
method: 'POST',
path,
body: { name: addRoomDto.roomName, parent_id: addRoomDto.homeId },
const spaceType = await this.spaceTypeRepository.findOne({
where: {
type: 'room',
},
});
await this.spaceRepository.save({
spaceName: addRoomDto.roomName,
parent: { uuid: addRoomDto.unitUuid },
spaceType: { uuid: spaceType.uuid },
});
} catch (err) {
throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
async getRoomByUuid(roomUuid: string): Promise<GetRoomByUuidInterface> {
try {
const room = await this.spaceRepository.findOne({
where: {
uuid: roomUuid,
spaceType: {
type: 'room',
},
},
relations: ['spaceType'],
});
if (!room || !room.spaceType || room.spaceType.type !== 'room') {
throw new BadRequestException('Invalid room UUID');
}
return {
uuid: room.uuid,
createdAt: room.createdAt,
updatedAt: room.updatedAt,
name: room.spaceName,
type: room.spaceType.type,
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Room not found', HttpStatus.NOT_FOUND);
}
}
}
async getRoomParentByUuid(roomUuid: string): Promise<RoomParentInterface> {
try {
const room = await this.spaceRepository.findOne({
where: {
uuid: roomUuid,
spaceType: {
type: 'room',
},
},
relations: ['spaceType', 'parent', 'parent.spaceType'],
});
if (!room || !room.spaceType || room.spaceType.type !== 'room') {
throw new BadRequestException('Invalid room UUID');
}
return {
uuid: room.uuid,
name: room.spaceName,
type: room.spaceType.type,
parent: {
uuid: room.parent.uuid,
name: room.parent.spaceName,
type: room.parent.spaceType.type,
},
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Room not found', HttpStatus.NOT_FOUND);
}
}
}
async renameRoomByUuid(
roomUuid: string,
updateRoomNameDto: UpdateRoomNameDto,
): Promise<RenameRoomByUuidInterface> {
try {
const room = await this.spaceRepository.findOneOrFail({
where: { uuid: roomUuid },
relations: ['spaceType'],
});
if (!room || !room.spaceType || room.spaceType.type !== 'room') {
throw new BadRequestException('Invalid room UUID');
}
await this.spaceRepository.update(
{ uuid: roomUuid },
{ spaceName: updateRoomNameDto.roomName },
);
// Fetch the updated room
const updateRoom = await this.spaceRepository.findOneOrFail({
where: { uuid: roomUuid },
relations: ['spaceType'],
});
return {
success: data.success,
roomId: data.result,
uuid: updateRoom.uuid,
name: updateRoom.spaceName,
type: updateRoom.spaceType.type,
};
} catch (error) {
throw new HttpException(
'Error adding room',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
async getRoomsByRoomId(roomId: string) {
try {
const response = await this.getRoomDetails(roomId);
return {
homeId: response.result.root_id,
roomId: response.result.id,
roomName: response.result.name,
};
} catch (error) {
throw new HttpException(
'Error fetching rooms',
HttpStatus.INTERNAL_SERVER_ERROR,
);
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Room not found', HttpStatus.NOT_FOUND);
}
}
}
}

View File

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

View File

@ -0,0 +1,111 @@
import { UnitService } from '../services/unit.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 { AddUnitDto } from '../dtos/add.unit.dto';
import { GetUnitChildDto } from '../dtos/get.unit.dto';
import { UpdateUnitNameDto } from '../dtos/update.unit.dto';
import { CheckFloorTypeGuard } from 'src/guards/floor.type.guard';
@ApiTags('Unit Module')
@Controller({
version: '1',
path: 'unit',
})
export class UnitController {
constructor(private readonly unitService: UnitService) {}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard, CheckFloorTypeGuard)
@Post()
async addUnit(@Body() addUnitDto: AddUnitDto) {
try {
await this.unitService.addUnit(addUnitDto);
return { message: 'Unit added successfully' };
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get(':unitUuid')
async getUnitByUuid(@Param('unitUuid') unitUuid: string) {
try {
const unit = await this.unitService.getUnitByUuid(unitUuid);
return unit;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get('child/:unitUuid')
async getUnitChildByUuid(
@Param('unitUuid') unitUuid: string,
@Query() query: GetUnitChildDto,
) {
try {
const unit = await this.unitService.getUnitChildByUuid(unitUuid, query);
return unit;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get('parent/:unitUuid')
async getUnitParentByUuid(@Param('unitUuid') unitUuid: string) {
try {
const unit = await this.unitService.getUnitParentByUuid(unitUuid);
return unit;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Put('rename/:unitUuid')
async renameUnitByUuid(
@Param('unitUuid') unitUuid: string,
@Body() updateUnitNameDto: UpdateUnitNameDto,
) {
try {
const unit = await this.unitService.renameUnitByUuid(
unitUuid,
updateUnitNameDto,
);
return unit;
} catch (error) {
throw new HttpException(
error.message || 'Internal server error',
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}

View File

@ -0,0 +1,23 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';
export class AddUnitDto {
@ApiProperty({
description: 'unitName',
required: true,
})
@IsString()
@IsNotEmpty()
public unitName: string;
@ApiProperty({
description: 'floorUuid',
required: true,
})
@IsString()
@IsNotEmpty()
public floorUuid: string;
constructor(dto: Partial<AddUnitDto>) {
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 GetUnitDto {
@ApiProperty({
description: 'unitUuid',
required: true,
})
@IsString()
@IsNotEmpty()
public unitUuid: string;
}
export class GetUnitChildDto {
@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;
}

1
src/unit/dtos/index.ts Normal file
View File

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

View File

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

View File

@ -0,0 +1,26 @@
export interface GetUnitByUuidInterface {
uuid: string;
createdAt: Date;
updatedAt: Date;
name: string;
type: string;
}
export interface UnitChildInterface {
uuid: string;
name: string;
type: string;
totalCount?: number;
children?: UnitChildInterface[];
}
export interface UnitParentInterface {
uuid: string;
name: string;
type: string;
parent?: UnitParentInterface;
}
export interface RenameUnitByUuidInterface {
uuid: string;
name: string;
type: string;
}

View File

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

View File

@ -0,0 +1,237 @@
import { GetUnitChildDto } from '../dtos/get.unit.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 { AddUnitDto } from '../dtos';
import {
UnitChildInterface,
UnitParentInterface,
GetUnitByUuidInterface,
RenameUnitByUuidInterface,
} from '../interface/unit.interface';
import { SpaceEntity } from '@app/common/modules/space/entities';
import { UpdateUnitNameDto } from '../dtos/update.unit.dto';
@Injectable()
export class UnitService {
constructor(
private readonly spaceRepository: SpaceRepository,
private readonly spaceTypeRepository: SpaceTypeRepository,
) {}
async addUnit(addUnitDto: AddUnitDto) {
try {
const spaceType = await this.spaceTypeRepository.findOne({
where: {
type: 'unit',
},
});
await this.spaceRepository.save({
spaceName: addUnitDto.unitName,
parent: { uuid: addUnitDto.floorUuid },
spaceType: { uuid: spaceType.uuid },
});
} catch (err) {
throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
async getUnitByUuid(unitUuid: string): Promise<GetUnitByUuidInterface> {
try {
const unit = await this.spaceRepository.findOne({
where: {
uuid: unitUuid,
spaceType: {
type: 'unit',
},
},
relations: ['spaceType'],
});
if (!unit || !unit.spaceType || unit.spaceType.type !== 'unit') {
throw new BadRequestException('Invalid unit UUID');
}
return {
uuid: unit.uuid,
createdAt: unit.createdAt,
updatedAt: unit.updatedAt,
name: unit.spaceName,
type: unit.spaceType.type,
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Unit not found', HttpStatus.NOT_FOUND);
}
}
}
async getUnitChildByUuid(
unitUuid: string,
getUnitChildDto: GetUnitChildDto,
): Promise<UnitChildInterface> {
try {
const { includeSubSpaces, page, pageSize } = getUnitChildDto;
const space = await this.spaceRepository.findOneOrFail({
where: { uuid: unitUuid },
relations: ['children', 'spaceType'],
});
if (!space || !space.spaceType || space.spaceType.type !== 'unit') {
throw new BadRequestException('Invalid unit 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('Unit not found', HttpStatus.NOT_FOUND);
}
}
}
private async buildHierarchy(
space: SpaceEntity,
includeSubSpaces: boolean,
page: number,
pageSize: number,
): Promise<UnitChildInterface[]> {
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 !== 'unit' &&
child.spaceType.type !== 'floor' &&
child.spaceType.type !== 'community' &&
child.spaceType.type !== 'unit',
) // Filter remaining unit and floor and community and unit types
.map((child) => ({
uuid: child.uuid,
name: child.spaceName,
type: child.spaceType.type,
}));
}
const childHierarchies = await Promise.all(
children
.filter(
(child) =>
child.spaceType.type !== 'unit' &&
child.spaceType.type !== 'floor' &&
child.spaceType.type !== 'community' &&
child.spaceType.type !== 'unit',
) // Filter remaining unit and floor and community and unit 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 getUnitParentByUuid(unitUuid: string): Promise<UnitParentInterface> {
try {
const unit = await this.spaceRepository.findOne({
where: {
uuid: unitUuid,
spaceType: {
type: 'unit',
},
},
relations: ['spaceType', 'parent', 'parent.spaceType'],
});
if (!unit || !unit.spaceType || unit.spaceType.type !== 'unit') {
throw new BadRequestException('Invalid unit UUID');
}
return {
uuid: unit.uuid,
name: unit.spaceName,
type: unit.spaceType.type,
parent: {
uuid: unit.parent.uuid,
name: unit.parent.spaceName,
type: unit.parent.spaceType.type,
},
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Unit not found', HttpStatus.NOT_FOUND);
}
}
}
async renameUnitByUuid(
unitUuid: string,
updateUnitNameDto: UpdateUnitNameDto,
): Promise<RenameUnitByUuidInterface> {
try {
const unit = await this.spaceRepository.findOneOrFail({
where: { uuid: unitUuid },
relations: ['spaceType'],
});
if (!unit || !unit.spaceType || unit.spaceType.type !== 'unit') {
throw new BadRequestException('Invalid unit UUID');
}
await this.spaceRepository.update(
{ uuid: unitUuid },
{ spaceName: updateUnitNameDto.unitName },
);
// Fetch the updated unit
const updatedUnit = await this.spaceRepository.findOneOrFail({
where: { uuid: unitUuid },
relations: ['spaceType'],
});
return {
uuid: updatedUnit.uuid,
name: updatedUnit.spaceName,
type: updatedUnit.spaceType.type,
};
} catch (err) {
if (err instanceof BadRequestException) {
throw err; // Re-throw BadRequestException
} else {
throw new HttpException('Unit not found', HttpStatus.NOT_FOUND);
}
}
}
}

16
src/unit/unit.module.ts Normal file
View File

@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { UnitService } from './services/unit.service';
import { UnitController } from './controllers/unit.controller';
import { ConfigModule } from '@nestjs/config';
import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module';
import { SpaceRepository } from '@app/common/modules/space/repositories';
import { SpaceTypeRepositoryModule } from '@app/common/modules/space-type/space.type.repository.module';
import { SpaceTypeRepository } from '@app/common/modules/space-type/repositories';
@Module({
imports: [ConfigModule, SpaceRepositoryModule, SpaceTypeRepositoryModule],
controllers: [UnitController],
providers: [UnitService, SpaceRepository, SpaceTypeRepository],
exports: [UnitService],
})
export class UnitModule {}