Merge pull request #151 from SyncrowIOT/feat/add-product-space

Feat/add product space
This commit is contained in:
hannathkadher
2024-11-21 11:49:19 +04:00
committed by GitHub
29 changed files with 542 additions and 255 deletions
@@ -235,4 +235,13 @@ export class ControllerRoute {
'Removes the association of a device from a specific subspace, making it no longer managed within that subspace.';
};
};
static PRODUCT = class {
public static readonly ROUTE = 'products';
static ACTIONS = class {
public static readonly LIST_PRODUCT_SUMMARY = 'Retrieve all products';
public static readonly LIST_PRODUCT_DESCRIPTION =
'Fetches a list of all products along with their associated device details';
};
};
}
@@ -0,0 +1,5 @@
export enum Direction {
LEFT = 'left',
RIGHT = 'right',
DOWN = 'down',
}
+8 -3
View File
@@ -8,8 +8,11 @@ import { UserOtpEntity } from '../modules/user/entities';
import { ProductEntity } from '../modules/product/entities';
import { DeviceEntity } from '../modules/device/entities';
import { PermissionTypeEntity } from '../modules/permission/entities';
import { SpaceEntity, SubspaceEntity } from '../modules/space/entities';
import { SpaceTypeEntity } from '../modules/space/entities';
import {
SpaceEntity,
SpaceLinkEntity,
SubspaceEntity,
} from '../modules/space/entities';
import { UserSpaceEntity } from '../modules/user/entities';
import { DeviceUserPermissionEntity } from '../modules/device/entities';
import { UserRoleEntity } from '../modules/user/entities';
@@ -23,6 +26,7 @@ import { CommunityEntity } from '../modules/community/entities';
import { DeviceStatusLogEntity } from '../modules/device-status-log/entities';
import { SceneEntity, SceneIconEntity } from '../modules/scene/entities';
import { SceneDeviceEntity } from '../modules/scene-device/entities';
import { SpaceProductEntity } from '../modules/space/entities/space-product.entity';
@Module({
imports: [
@@ -47,8 +51,9 @@ import { SceneDeviceEntity } from '../modules/scene-device/entities';
PermissionTypeEntity,
CommunityEntity,
SpaceEntity,
SpaceLinkEntity,
SubspaceEntity,
SpaceTypeEntity,
SpaceProductEntity,
UserSpaceEntity,
DeviceUserPermissionEntity,
UserRoleEntity,
@@ -2,6 +2,7 @@ import { Column, Entity, OneToMany } from 'typeorm';
import { ProductDto } from '../dtos';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { DeviceEntity } from '../../device/entities';
import { SpaceProductEntity } from '../../space/entities/space-product.entity';
@Entity({ name: 'product' })
export class ProductEntity extends AbstractEntity<ProductDto> {
@@ -16,11 +17,19 @@ export class ProductEntity extends AbstractEntity<ProductDto> {
})
public prodId: string;
@Column({
nullable: true,
})
public name: string;
@Column({
nullable: false,
})
public prodType: string;
@OneToMany(() => SpaceProductEntity, (spaceProduct) => spaceProduct.product)
spaceProducts: SpaceProductEntity[];
@OneToMany(
() => DeviceEntity,
(devicesProductEntity) => devicesProductEntity.productDevice,
@@ -21,13 +21,3 @@ export class SpaceDto {
@IsNotEmpty()
public invitationCode: string;
}
export class SpaceTypeDto {
@IsString()
@IsNotEmpty()
public uuid: string;
@IsString()
@IsNotEmpty()
public type: string;
}
@@ -1,2 +1,3 @@
export * from './space.entity';
export * from './subspace.entity';
export * from './space-link.entity';
@@ -0,0 +1,26 @@
import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { SpaceEntity } from './space.entity';
import { Direction } from '@app/common/constants/direction.enum';
@Entity({ name: 'space-link' })
export class SpaceLinkEntity extends AbstractEntity {
@ManyToOne(() => SpaceEntity, { nullable: false, onDelete: 'CASCADE' })
@JoinColumn({ name: 'start_space_id' })
public startSpace: SpaceEntity;
@ManyToOne(() => SpaceEntity, { nullable: false, onDelete: 'CASCADE' })
@JoinColumn({ name: 'end_space_id' })
public endSpace: SpaceEntity;
@Column({
nullable: false,
enum: Object.values(Direction),
})
direction: string;
constructor(partial: Partial<SpaceLinkEntity>) {
super();
Object.assign(this, partial);
}
}
@@ -0,0 +1,32 @@
import { Column, Entity, ManyToOne, JoinColumn } from 'typeorm';
import { SpaceEntity } from './space.entity';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { ProductEntity } from '../../product/entities';
@Entity({ name: 'space-product' })
export class SpaceProductEntity extends AbstractEntity<SpaceProductEntity> {
@ManyToOne(() => SpaceEntity, (space) => space.spaceProducts, {
nullable: false,
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'space_uuid' })
space: SpaceEntity;
@ManyToOne(() => ProductEntity, (product) => product.spaceProducts, {
nullable: false,
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'product_uuid' })
product: ProductEntity;
@Column({
nullable: false,
type: 'int',
})
productCount: number;
constructor(partial: Partial<SpaceProductEntity>) {
super();
Object.assign(this, partial);
}
}
@@ -6,32 +6,14 @@ import {
OneToMany,
Unique,
} from 'typeorm';
import { SpaceDto, SpaceTypeDto } from '../dtos';
import { SpaceDto } from '../dtos';
import { AbstractEntity } from '../../abstract/entities/abstract.entity';
import { UserSpaceEntity } from '../../user/entities';
import { DeviceEntity } from '../../device/entities';
import { CommunityEntity } from '../../community/entities';
import { SubspaceEntity } from './subspace.entity';
@Entity({ name: 'space-type' })
export class SpaceTypeEntity extends AbstractEntity<SpaceTypeDto> {
@Column({
type: 'uuid',
default: () => 'gen_random_uuid()',
nullable: false,
})
public uuid: string;
@Column({
nullable: false,
})
type: string;
constructor(partial: Partial<SpaceTypeEntity>) {
super();
Object.assign(this, partial);
}
}
import { SpaceLinkEntity } from './space-link.entity';
import { SpaceProductEntity } from './space-product.entity';
@Entity({ name: 'space' })
@Unique(['invitationCode'])
@@ -77,12 +59,38 @@ export class SpaceEntity extends AbstractEntity<SpaceDto> {
})
subspaces?: SubspaceEntity[];
// Position columns
@Column({ type: 'float', nullable: false, default: 0 })
public x: number; // X coordinate for position
@Column({ type: 'float', nullable: false, default: 0 })
public y: number; // Y coordinate for position
@OneToMany(
() => DeviceEntity,
(devicesSpaceEntity) => devicesSpaceEntity.spaceDevice,
)
devices: DeviceEntity[];
@OneToMany(() => SpaceLinkEntity, (connection) => connection.startSpace, {
nullable: true,
})
public outgoingConnections: SpaceLinkEntity[];
@OneToMany(() => SpaceLinkEntity, (connection) => connection.endSpace, {
nullable: true,
})
public incomingConnections: SpaceLinkEntity[];
@Column({
nullable: true,
type: 'text',
})
public icon: string;
@OneToMany(() => SpaceProductEntity, (spaceProduct) => spaceProduct.space)
spaceProducts: SpaceProductEntity[];
constructor(partial: Partial<SpaceEntity>) {
super();
Object.assign(this, partial);
@@ -1,6 +1,7 @@
import { DataSource, Repository } from 'typeorm';
import { Injectable } from '@nestjs/common';
import { SpaceEntity, SpaceTypeEntity, SubspaceEntity } from '../entities';
import { SpaceProductEntity } from '../entities/space-product.entity';
import { SpaceEntity, SpaceLinkEntity, SubspaceEntity } from '../entities';
@Injectable()
export class SpaceRepository extends Repository<SpaceEntity> {
@@ -8,17 +9,22 @@ export class SpaceRepository extends Repository<SpaceEntity> {
super(SpaceEntity, dataSource.createEntityManager());
}
}
@Injectable()
export class SpaceTypeRepository extends Repository<SpaceTypeEntity> {
constructor(private dataSource: DataSource) {
super(SpaceTypeEntity, dataSource.createEntityManager());
}
}
@Injectable()
export class SubspaceRepository extends Repository<SubspaceEntity> {
constructor(private dataSource: DataSource) {
super(SubspaceEntity, dataSource.createEntityManager());
}
}
@Injectable()
export class SpaceLinkRepository extends Repository<SpaceLinkEntity> {
constructor(private dataSource: DataSource) {
super(SpaceLinkEntity, dataSource.createEntityManager());
}
}
@Injectable()
export class SpaceProductRepository extends Repository<SpaceProductEntity> {
constructor(private dataSource: DataSource) {
super(SpaceProductEntity, dataSource.createEntityManager());
}
}
@@ -1,13 +1,11 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { SpaceEntity, SpaceTypeEntity, SubspaceEntity } from './entities';
import { SpaceEntity, SubspaceEntity } from './entities';
@Module({
providers: [],
exports: [],
controllers: [],
imports: [
TypeOrmModule.forFeature([SpaceEntity, SpaceTypeEntity, SubspaceEntity]),
],
imports: [TypeOrmModule.forFeature([SpaceEntity, SubspaceEntity])],
})
export class SpaceRepositoryModule {}
-4
View File
@@ -7,8 +7,6 @@ import { ConfigModule } from '@nestjs/config';
import { RoleTypeRepositoryModule } from '../modules/role-type/role.type.repository.module';
import { RoleTypeRepository } from '../modules/role-type/repositories';
import { RoleTypeSeeder } from './services/role.type.seeder';
import { SpaceTypeRepository } from '../modules/space/repositories';
import { SpaceTypeSeeder } from './services/space.type.seeder';
import { SpaceRepositoryModule } from '../modules/space/space.repository.module';
import { SuperAdminSeeder } from './services/supper.admin.seeder';
import { UserRepository } from '../modules/user/repositories';
@@ -25,11 +23,9 @@ import { SceneIconRepository } from '../modules/scene/repositories';
providers: [
PermissionTypeSeeder,
RoleTypeSeeder,
SpaceTypeSeeder,
SeederService,
PermissionTypeRepository,
RoleTypeRepository,
SpaceTypeRepository,
SuperAdminSeeder,
UserRepository,
UserRoleRepository,
@@ -1,7 +1,6 @@
import { Injectable } from '@nestjs/common';
import { PermissionTypeSeeder } from './permission.type.seeder';
import { RoleTypeSeeder } from './role.type.seeder';
import { SpaceTypeSeeder } from './space.type.seeder';
import { SuperAdminSeeder } from './supper.admin.seeder';
import { RegionSeeder } from './regions.seeder';
import { TimeZoneSeeder } from './timezone.seeder';
@@ -11,7 +10,6 @@ export class SeederService {
constructor(
private readonly permissionTypeSeeder: PermissionTypeSeeder,
private readonly roleTypeSeeder: RoleTypeSeeder,
private readonly spaceTypeSeeder: SpaceTypeSeeder,
private readonly regionSeeder: RegionSeeder,
private readonly timeZoneSeeder: TimeZoneSeeder,
private readonly superAdminSeeder: SuperAdminSeeder,
@@ -21,7 +19,6 @@ export class SeederService {
async seed() {
await this.permissionTypeSeeder.addPermissionTypeDataIfNotFound();
await this.roleTypeSeeder.addRoleTypeDataIfNotFound();
await this.spaceTypeSeeder.addSpaceTypeDataIfNotFound();
await this.regionSeeder.addRegionDataIfNotFound();
await this.timeZoneSeeder.addTimeZoneDataIfNotFound();
await this.superAdminSeeder.createSuperAdminIfNotFound();
@@ -1,52 +0,0 @@
import { Injectable } from '@nestjs/common';
import { SpaceType } from '../../constants/space-type.enum';
import { SpaceTypeRepository } from '../../modules/space/repositories';
@Injectable()
export class SpaceTypeSeeder {
constructor(private readonly spaceTypeRepository: SpaceTypeRepository) {}
async addSpaceTypeDataIfNotFound(): Promise<void> {
try {
const existingSpaceTypes = await this.spaceTypeRepository.find();
const spaceTypeNames = existingSpaceTypes.map((pt) => pt.type);
const missingSpaceTypes = [];
if (!spaceTypeNames.includes(SpaceType.COMMUNITY)) {
missingSpaceTypes.push(SpaceType.COMMUNITY);
}
if (!spaceTypeNames.includes(SpaceType.BUILDING)) {
missingSpaceTypes.push(SpaceType.BUILDING);
}
if (!spaceTypeNames.includes(SpaceType.FLOOR)) {
missingSpaceTypes.push(SpaceType.FLOOR);
}
if (!spaceTypeNames.includes(SpaceType.UNIT)) {
missingSpaceTypes.push(SpaceType.UNIT);
}
if (!spaceTypeNames.includes(SpaceType.ROOM)) {
missingSpaceTypes.push(SpaceType.ROOM);
}
if (missingSpaceTypes.length > 0) {
await this.addSpaceTypeData(missingSpaceTypes);
}
} catch (err) {
console.error('Error while checking space type data:', err);
throw err;
}
}
private async addSpaceTypeData(spaceTypes: string[]): Promise<void> {
try {
const spaceTypeEntities = spaceTypes.map((type) => ({
type,
}));
await this.spaceTypeRepository.save(spaceTypeEntities);
} catch (err) {
console.error('Error while adding space type data:', err);
throw err;
}
}
}
+2
View File
@@ -21,6 +21,7 @@ import { TimeZoneModule } from './timezone/timezone.module';
import { VisitorPasswordModule } from './vistor-password/visitor-password.module';
import { ScheduleModule } from './schedule/schedule.module';
import { SpaceModule } from './space/space.module';
import { ProductModule } from './product';
@Module({
imports: [
ConfigModule.forRoot({
@@ -46,6 +47,7 @@ import { SpaceModule } from './space/space.module';
TimeZoneModule,
VisitorPasswordModule,
ScheduleModule,
ProductModule,
],
providers: [
{
+1
View File
@@ -0,0 +1 @@
export * from './product.controller';
@@ -0,0 +1,27 @@
import { Controller, Get, UseGuards } from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard';
import { EnableDisableStatusEnum } from '@app/common/constants/days.enum';
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { ControllerRoute } from '@app/common/constants/controller-route';
import { ProductService } from '../services';
@ApiTags('Product Module')
@Controller({
version: EnableDisableStatusEnum.ENABLED,
path: ControllerRoute.PRODUCT.ROUTE,
})
export class ProductController {
constructor(private readonly productService: ProductService) {}
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Get()
@ApiOperation({
summary: ControllerRoute.PRODUCT.ACTIONS.LIST_PRODUCT_SUMMARY,
description: ControllerRoute.PRODUCT.ACTIONS.LIST_PRODUCT_DESCRIPTION,
})
async getProducts(): Promise<BaseResponseDto> {
return await this.productService.list();
}
}
+1
View File
@@ -0,0 +1 @@
export * from './product.module';
+10
View File
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { ProductService } from './services';
import { ProductRepository } from '@app/common/modules/product/repositories';
import { ProductController } from './controllers';
@Module({
controllers: [ProductController],
providers: [ProductService, ProductRepository],
})
export class ProductModule {}
+1
View File
@@ -0,0 +1 @@
export * from './product.service';
+24
View File
@@ -0,0 +1,24 @@
import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { SuccessResponseDto } from '@app/common/dto/success.response.dto';
import { ProductRepository } from '@app/common/modules/product/repositories';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
@Injectable()
export class ProductService {
constructor(private readonly productRepository: ProductRepository) {}
async list(): Promise<BaseResponseDto> {
const products = await this.productRepository.find();
if (!products) {
throw new HttpException(
`No products found in the system`,
HttpStatus.NOT_FOUND,
);
}
return new SuccessResponseDto({
data: products,
message: 'List of products retrieved successfully',
});
}
}
+35
View File
@@ -1,10 +1,14 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import {
IsArray,
IsBoolean,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
IsUUID,
ValidateNested,
} from 'class-validator';
export class AddSpaceDto {
@@ -25,6 +29,10 @@ export class AddSpaceDto {
@IsOptional()
parentUuid?: string;
@IsString()
@IsNotEmpty()
public icon: string;
@ApiProperty({
description: 'Indicates whether the space is private or public',
example: false,
@@ -32,6 +40,24 @@ export class AddSpaceDto {
})
@IsBoolean()
isPrivate: boolean;
@ApiProperty({ description: 'X position on canvas', example: 120 })
@IsNumber()
x: number;
@ApiProperty({ description: 'Y position on canvas', example: 200 })
@IsNumber()
y: number;
@ApiProperty({ description: 'Y position on canvas', example: 200 })
@IsString()
@IsOptional()
direction: string;
@IsArray()
@ValidateNested({ each: true })
@Type(() => ProductAssignmentDto)
products: ProductAssignmentDto[];
}
export class AddUserSpaceDto {
@@ -69,7 +95,16 @@ export class AddUserSpaceUsingCodeDto {
@IsString()
@IsNotEmpty()
public inviteCode: string;
constructor(dto: Partial<AddUserSpaceDto>) {
Object.assign(this, dto);
}
}
class ProductAssignmentDto {
@IsNotEmpty()
productId: string;
@IsNotEmpty()
count: number;
}
+2
View File
@@ -2,4 +2,6 @@ export * from './space.service';
export * from './space-user.service';
export * from './space-device.service';
export * from './subspace';
export * from './space-link';
export * from './space-scene.service';
export * from './space-products';
+1
View File
@@ -0,0 +1 @@
export * from './space-link.service';
@@ -0,0 +1,88 @@
import {
SpaceLinkRepository,
SpaceRepository,
} from '@app/common/modules/space/repositories';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
@Injectable()
export class SpaceLinkService {
constructor(
private readonly spaceRepository: SpaceRepository,
private readonly spaceLinkRepository: SpaceLinkRepository,
) {}
async saveSpaceLink(
startSpaceId: string,
endSpaceId: string,
direction: string,
): Promise<void> {
try {
// Check if a link between the startSpace and endSpace already exists
const existingLink = await this.spaceLinkRepository.findOne({
where: {
startSpace: { uuid: startSpaceId },
endSpace: { uuid: endSpaceId },
},
});
if (existingLink) {
// Update the direction if the link exists
existingLink.direction = direction;
await this.spaceLinkRepository.save(existingLink);
return;
}
const existingEndSpaceLink = await this.spaceLinkRepository.findOne({
where: { endSpace: { uuid: endSpaceId } },
});
if (
existingEndSpaceLink &&
existingEndSpaceLink.startSpace.uuid !== startSpaceId
) {
throw new Error(
`Space with ID ${endSpaceId} is already an endSpace in another link and cannot be reused.`,
);
}
// Find start space
const startSpace = await this.spaceRepository.findOne({
where: { uuid: startSpaceId },
});
if (!startSpace) {
throw new HttpException(
`Start space with ID ${startSpaceId} not found.`,
HttpStatus.NOT_FOUND,
);
}
// Find end space
const endSpace = await this.spaceRepository.findOne({
where: { uuid: endSpaceId },
});
if (!endSpace) {
throw new HttpException(
`End space with ID ${endSpaceId} not found.`,
HttpStatus.NOT_FOUND,
);
}
// Create and save the space link
const spaceLink = this.spaceLinkRepository.create({
startSpace,
endSpace,
direction,
});
await this.spaceLinkRepository.save(spaceLink);
} catch (error) {
throw new HttpException(
error.message ||
`Failed to save space link. Internal Server Error: ${error.message}`,
error.status || HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}
@@ -0,0 +1 @@
export * from './space-products.service';
@@ -0,0 +1,131 @@
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { ProductRepository } from '@app/common/modules/product/repositories';
import { SpaceEntity } from '@app/common/modules/space/entities';
import { SpaceProductEntity } from '@app/common/modules/space/entities/space-product.entity';
import { SpaceProductRepository } from '@app/common/modules/space/repositories';
import { In } from 'typeorm';
@Injectable()
export class SpaceProductService {
constructor(
private readonly productRepository: ProductRepository,
private readonly spaceProductRepository: SpaceProductRepository,
) {}
async assignProductsToSpace(
space: SpaceEntity,
products: { productId: string; count: number }[],
): Promise<SpaceProductEntity[]> {
try {
const uniqueProducts = this.validateUniqueProducts(products);
const productEntities = await this.getProductEntities(uniqueProducts);
// Fetch existing space products
const existingSpaceProducts = await this.spaceProductRepository.find({
where: {
space: {
uuid: space.uuid,
},
},
relations: ['product'],
});
const updatedProducts = [];
const newProducts = [];
for (const { productId, count } of uniqueProducts) {
const product = productEntities.get(productId);
if (!product) {
throw new HttpException(
`Product with ID ${productId} not found`,
HttpStatus.NOT_FOUND,
);
}
// Check if product already exists in the space
const existingProduct = existingSpaceProducts.find(
(spaceProduct) => spaceProduct.product.uuid === productId,
);
if (existingProduct) {
// If count is different, update the existing record
if (existingProduct.productCount !== count) {
existingProduct.productCount = count;
updatedProducts.push(existingProduct);
}
} else {
// Add new product if it doesn't exist
newProducts.push(
this.spaceProductRepository.create({
space,
product,
productCount: count,
}),
);
}
}
// Save updates and new records
if (updatedProducts.length > 0) {
await this.spaceProductRepository.save(updatedProducts);
}
if (newProducts.length > 0) {
await this.spaceProductRepository.save(newProducts);
}
return [...updatedProducts, ...newProducts];
} catch (error) {
console.error('Error assigning products to space:', error);
if (!(error instanceof HttpException)) {
throw new HttpException(
'An error occurred while assigning products to the space',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
throw error;
}
}
private validateUniqueProducts(
products: { productId: string; count: number }[],
): { productId: string; count: number }[] {
const productIds = new Set();
const uniqueProducts = [];
for (const product of products) {
if (productIds.has(product.productId)) {
throw new HttpException(
`Duplicate product ID found: ${product.productId}`,
HttpStatus.BAD_REQUEST,
);
}
productIds.add(product.productId);
uniqueProducts.push(product);
}
return uniqueProducts;
}
private async getProductEntities(
products: { productId: string; count: number }[],
): Promise<Map<string, any>> {
try {
const productIds = products.map((p) => p.productId);
const productEntities = await this.productRepository.find({
where: { uuid: In(productIds) },
});
return new Map(productEntities.map((p) => [p.uuid, p]));
} catch (error) {
console.error('Error fetching product entities:', error);
throw new HttpException(
'Failed to fetch product entities',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}
+74 -149
View File
@@ -11,46 +11,27 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { CommunityRepository } from '@app/common/modules/community/repositories';
import { SpaceEntity } from '@app/common/modules/space/entities';
import { generateRandomString } from '@app/common/helper/randomString';
import { SpaceLinkService } from './space-link';
import { SpaceProductService } from './space-products';
@Injectable()
export class SpaceService {
constructor(
private readonly spaceRepository: SpaceRepository,
private readonly communityRepository: CommunityRepository,
private readonly spaceLinkService: SpaceLinkService,
private readonly spaceProductService: SpaceProductService,
) {}
async createSpace(
addSpaceDto: AddSpaceDto,
communityId: string,
): Promise<BaseResponseDto> {
let parent: SpaceEntity | null = null;
const { parentUuid, direction, products } = addSpaceDto;
const { parentUuid } = addSpaceDto;
const community = await this.communityRepository.findOne({
where: { uuid: communityId },
});
const community = await this.validateCommunity(communityId);
// If the community doesn't exist, throw a 404 error
if (!community) {
throw new HttpException(
`Community with ID ${communityId} not found`,
HttpStatus.NOT_FOUND,
);
}
if (parentUuid) {
parent = await this.spaceRepository.findOne({
where: { uuid: parentUuid },
});
// If the community doesn't exist, throw a 404 error
if (!parent) {
throw new HttpException(
`Parent with ID ${parentUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
}
const parent = parentUuid ? await this.validateSpace(parentUuid) : null;
try {
const newSpace = this.spaceRepository.create({
...addSpaceDto,
@@ -60,6 +41,21 @@ export class SpaceService {
await this.spaceRepository.save(newSpace);
if (direction && parent) {
await this.spaceLinkService.saveSpaceLink(
parent.uuid,
newSpace.uuid,
direction,
);
}
if (products && products.length > 0) {
await this.spaceProductService.assignProductsToSpace(
newSpace,
products,
);
}
return new SuccessResponseDto({
statusCode: HttpStatus.CREATED,
data: newSpace,
@@ -73,22 +69,18 @@ export class SpaceService {
async getSpacesHierarchyForCommunity(
communityUuid: string,
): Promise<BaseResponseDto> {
const community = await this.communityRepository.findOne({
where: { uuid: communityUuid },
});
// If the community doesn't exist, throw a 404 error
if (!community) {
throw new HttpException(
`Community with ID ${communityUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
await this.validateCommunity(communityUuid);
try {
// Get all spaces related to the community, including the parent-child relations
const spaces = await this.spaceRepository.find({
where: { community: { uuid: communityUuid } },
relations: ['parent', 'children'], // Include parent and children relations
relations: [
'parent',
'children',
'incomingConnections',
'spaceProducts',
'spaceProducts.product',
], // Include parent and children relations
});
// Organize spaces into a hierarchical structure
@@ -109,19 +101,8 @@ export class SpaceService {
async findOne(spaceUuid: string): Promise<BaseResponseDto> {
try {
const space = await this.spaceRepository.findOne({
where: {
uuid: spaceUuid,
},
});
const space = await this.validateSpace(spaceUuid);
// If space is not found, throw a NotFoundException
if (!space) {
throw new HttpException(
`Space with UUID ${spaceUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
return new SuccessResponseDto({
message: `Space with ID ${spaceUuid} successfully fetched`,
data: space,
@@ -144,28 +125,10 @@ export class SpaceService {
): Promise<BaseResponseDto> {
try {
// First, check if the community exists
const community = await this.communityRepository.findOne({
where: { uuid: communityUuid },
});
if (!community) {
throw new HttpException(
`Community with ID ${communityUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
// Check if the space exists
const space = await this.spaceRepository.findOne({
where: { uuid: spaceUuid, community: { uuid: communityUuid } },
});
if (!space) {
throw new HttpException(
`Space with ID ${spaceUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
const space = await this.validateCommunityAndSpace(
spaceUuid,
communityUuid,
);
// Delete the space
await this.spaceRepository.remove(space);
@@ -191,57 +154,27 @@ export class SpaceService {
updateSpaceDto: AddSpaceDto,
): Promise<BaseResponseDto> {
try {
// First, check if the community exists
const community = await this.communityRepository.findOne({
where: { uuid: communityId },
});
if (!community) {
throw new HttpException(
`Community with ID ${communityId} not found`,
HttpStatus.NOT_FOUND,
);
}
// Check if the space exists
const space = await this.spaceRepository.findOne({
where: { uuid: spaceUuid, community: { uuid: communityId } },
});
if (!space) {
throw new HttpException(
`Space with ID ${spaceUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
const space = await this.validateCommunityAndSpace(
communityId,
spaceUuid,
);
// If a parentId is provided, check if the parent exists
const { parentUuid } = updateSpaceDto;
let parent: SpaceEntity | null = null;
if (parentUuid) {
parent = await this.spaceRepository.findOne({
where: { uuid: parentUuid, community: { uuid: communityId } },
});
// If the parent doesn't exist, throw a 404 error
if (!parent) {
throw new HttpException(
`Parent space with ID ${parentUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
// Set the parent of the current space
space.parent = parent;
} else {
space.parent = null; // If no parent is provided, clear the parent
}
const { parentUuid, products } = updateSpaceDto;
const parent = parentUuid ? await this.validateSpace(parentUuid) : null;
// Update other space properties from updateSpaceDto
Object.assign(space, updateSpaceDto);
Object.assign(space, updateSpaceDto, { parent });
// Save the updated space
await this.spaceRepository.save(space);
const updatedSpace = await this.spaceRepository.save(space);
if (products && products.length > 0) {
await this.spaceProductService.assignProductsToSpace(
updatedSpace,
products,
);
}
return new SuccessResponseDto({
message: `Space with ID ${spaceUuid} successfully updated`,
@@ -262,17 +195,7 @@ export class SpaceService {
async getSpacesHierarchyForSpace(
spaceUuid: string,
): Promise<BaseResponseDto> {
const space = await this.spaceRepository.findOne({
where: { uuid: spaceUuid },
});
// If the space doesn't exist, throw a 404 error
if (!space) {
throw new HttpException(
`Space with ID ${spaceUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
await this.validateSpace(spaceUuid);
try {
// Get all spaces that are children of the provided space, including the parent-child relations
@@ -301,18 +224,7 @@ export class SpaceService {
try {
const invitationCode = generateRandomString(6);
const space = await this.spaceRepository.findOne({
where: {
uuid: spaceUuid,
},
});
if (!space) {
throw new HttpException(
`Space with ID ${spaceUuid} not found`,
HttpStatus.NOT_FOUND,
);
}
const space = await this.validateSpace(spaceUuid);
space.invitationCode = invitationCode;
await this.spaceRepository.save(space);
@@ -355,24 +267,37 @@ export class SpaceService {
}
});
return rootSpaces; // Return the root spaces with children nested within them
return rootSpaces;
}
private async validateCommunity(communityId: string) {
const community = await this.communityRepository.findOne({
where: { uuid: communityId },
});
if (!community) {
throw new HttpException(
`Community with ID ${communityId} not found`,
HttpStatus.NOT_FOUND,
);
}
return community;
}
async validateCommunityAndSpace(communityUuid: string, spaceUuid: string) {
const community = await this.communityRepository.findOne({
where: { uuid: communityUuid },
});
const community = await this.validateCommunity(communityUuid);
if (!community) {
this.throwNotFound('Community', communityUuid);
}
const space = await this.validateSpace(spaceUuid);
return space;
}
private async validateSpace(spaceUuid: string) {
const space = await this.spaceRepository.findOne({
where: { uuid: spaceUuid, community: { uuid: communityUuid } },
relations: ['devices', 'devices.productDevice'],
where: { uuid: spaceUuid },
});
if (!space) {
this.throwNotFound('Space', spaceUuid);
}
if (!space) this.throwNotFound('Space', spaceUuid);
return space;
}
+8
View File
@@ -11,6 +11,8 @@ import {
} from './controllers';
import {
SpaceDeviceService,
SpaceLinkService,
SpaceProductService,
SpaceSceneService,
SpaceService,
SpaceUserService,
@@ -18,8 +20,10 @@ import {
SubSpaceService,
} from './services';
import {
SpaceProductRepository,
SpaceRepository,
SubspaceRepository,
SpaceLinkRepository,
} from '@app/common/modules/space/repositories';
import { CommunityRepository } from '@app/common/modules/community/repositories';
import {
@@ -55,11 +59,13 @@ import { SceneDeviceRepository } from '@app/common/modules/scene-device/reposito
ProductRepository,
SubSpaceService,
SpaceDeviceService,
SpaceLinkService,
SubspaceDeviceService,
SpaceRepository,
DeviceRepository,
CommunityRepository,
SubspaceRepository,
SpaceLinkRepository,
UserSpaceRepository,
UserRepository,
SpaceUserService,
@@ -71,6 +77,8 @@ import { SceneDeviceRepository } from '@app/common/modules/scene-device/reposito
DeviceStatusFirebaseService,
DeviceStatusLogRepository,
SceneDeviceRepository,
SpaceProductService,
SpaceProductRepository,
],
exports: [SpaceService],
})