mirror of
https://github.com/SyncrowIOT/backend.git
synced 2026-10-23 15:17:55 +00:00
Merge pull request #151 from SyncrowIOT/feat/add-product-space
Feat/add product space
This commit is contained in:
@@ -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,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 {}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './product.module';
|
||||
@@ -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 {}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './product.service';
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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,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';
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user