From df80928b68ec128450698ce8c4927cefb064995f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 10:51:58 +0400 Subject: [PATCH 001/247] created project entity --- libs/common/src/database/database.module.ts | 3 ++- libs/common/src/modules/project/dtos/index.ts | 1 + .../src/modules/project/dtos/project.dto.ts | 15 ++++++++++++ .../src/modules/project/entities/index.ts | 1 + .../project/entities/project.entity.ts | 23 +++++++++++++++++++ libs/common/src/modules/project/index.ts | 1 + .../project/project.repository.module.ts | 11 +++++++++ .../modules/project/repositiories/index.ts | 1 + .../repositiories/project.repository.ts | 10 ++++++++ 9 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 libs/common/src/modules/project/dtos/index.ts create mode 100644 libs/common/src/modules/project/dtos/project.dto.ts create mode 100644 libs/common/src/modules/project/entities/index.ts create mode 100644 libs/common/src/modules/project/entities/project.entity.ts create mode 100644 libs/common/src/modules/project/index.ts create mode 100644 libs/common/src/modules/project/project.repository.module.ts create mode 100644 libs/common/src/modules/project/repositiories/index.ts create mode 100644 libs/common/src/modules/project/repositiories/project.repository.ts diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 8b4acf4..34e4bb6 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -27,7 +27,7 @@ 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'; - +import { ProjectEntity } from '../modules/project/entities'; @Module({ imports: [ TypeOrmModule.forRootAsync({ @@ -42,6 +42,7 @@ import { SpaceProductEntity } from '../modules/space/entities/space-product.enti password: configService.get('DB_PASSWORD'), database: configService.get('DB_NAME'), entities: [ + ProjectEntity, UserEntity, UserSessionEntity, UserOtpEntity, diff --git a/libs/common/src/modules/project/dtos/index.ts b/libs/common/src/modules/project/dtos/index.ts new file mode 100644 index 0000000..9d264cb --- /dev/null +++ b/libs/common/src/modules/project/dtos/index.ts @@ -0,0 +1 @@ +export * from './project.dto'; diff --git a/libs/common/src/modules/project/dtos/project.dto.ts b/libs/common/src/modules/project/dtos/project.dto.ts new file mode 100644 index 0000000..a5931af --- /dev/null +++ b/libs/common/src/modules/project/dtos/project.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class ProjectDto { + @ApiProperty({ + example: 'd7a44e8a-32d5-4f39-ae2e-013f1245aead', + description: 'UUID of the project', + }) + uuid: string; + + @ApiProperty({ + example: 'Project 1', + description: 'Name of the project', + }) + name: string; +} diff --git a/libs/common/src/modules/project/entities/index.ts b/libs/common/src/modules/project/entities/index.ts new file mode 100644 index 0000000..bd8d161 --- /dev/null +++ b/libs/common/src/modules/project/entities/index.ts @@ -0,0 +1 @@ +export * from './project.entity'; diff --git a/libs/common/src/modules/project/entities/project.entity.ts b/libs/common/src/modules/project/entities/project.entity.ts new file mode 100644 index 0000000..f614b50 --- /dev/null +++ b/libs/common/src/modules/project/entities/project.entity.ts @@ -0,0 +1,23 @@ +import { Entity, Column } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { ProjectDto } from '../dtos'; + +@Entity({ name: 'project' }) +export class ProjectEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', + nullable: false, + }) + public project_id: string; + + @Column({ + nullable: false, + }) + public name: string; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/project/index.ts b/libs/common/src/modules/project/index.ts new file mode 100644 index 0000000..cacdb88 --- /dev/null +++ b/libs/common/src/modules/project/index.ts @@ -0,0 +1 @@ +export * from './project.repository.module'; diff --git a/libs/common/src/modules/project/project.repository.module.ts b/libs/common/src/modules/project/project.repository.module.ts new file mode 100644 index 0000000..0b744f8 --- /dev/null +++ b/libs/common/src/modules/project/project.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ProjectEntity } from './entities'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [TypeOrmModule.forFeature([ProjectEntity])], +}) +export class ProjectEntityModule {} diff --git a/libs/common/src/modules/project/repositiories/index.ts b/libs/common/src/modules/project/repositiories/index.ts new file mode 100644 index 0000000..3e35c16 --- /dev/null +++ b/libs/common/src/modules/project/repositiories/index.ts @@ -0,0 +1 @@ +export * from './project.repository'; diff --git a/libs/common/src/modules/project/repositiories/project.repository.ts b/libs/common/src/modules/project/repositiories/project.repository.ts new file mode 100644 index 0000000..b4602e7 --- /dev/null +++ b/libs/common/src/modules/project/repositiories/project.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { ProjectEntity } from '../entities'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class ProjectRepository extends Repository { + constructor(private dataSource: DataSource) { + super(ProjectEntity, dataSource.createEntityManager()); + } +} From acbd5d53f13b4277292a2a7f8ad8235240df88ea Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 11:20:40 +0400 Subject: [PATCH 002/247] name should be unique --- libs/common/src/modules/project/entities/project.entity.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/common/src/modules/project/entities/project.entity.ts b/libs/common/src/modules/project/entities/project.entity.ts index f614b50..7eb1956 100644 --- a/libs/common/src/modules/project/entities/project.entity.ts +++ b/libs/common/src/modules/project/entities/project.entity.ts @@ -1,8 +1,9 @@ -import { Entity, Column } from 'typeorm'; +import { Entity, Column, Unique } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { ProjectDto } from '../dtos'; @Entity({ name: 'project' }) +@Unique(['name']) export class ProjectEntity extends AbstractEntity { @Column({ type: 'uuid', From 54228c24eb62e922627e74d2f164a7621bc13556 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 11:24:28 +0400 Subject: [PATCH 003/247] added description --- .../src/modules/project/dtos/project.dto.ts | 18 +++++++++--------- .../modules/project/entities/project.entity.ts | 3 +++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/libs/common/src/modules/project/dtos/project.dto.ts b/libs/common/src/modules/project/dtos/project.dto.ts index a5931af..ef08e55 100644 --- a/libs/common/src/modules/project/dtos/project.dto.ts +++ b/libs/common/src/modules/project/dtos/project.dto.ts @@ -1,15 +1,15 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class ProjectDto { - @ApiProperty({ - example: 'd7a44e8a-32d5-4f39-ae2e-013f1245aead', - description: 'UUID of the project', - }) + @IsString() + @IsNotEmpty() uuid: string; - @ApiProperty({ - example: 'Project 1', - description: 'Name of the project', - }) + @IsString() + @IsNotEmpty() name: string; + + @IsString() + @IsOptional() + public description?: string; } diff --git a/libs/common/src/modules/project/entities/project.entity.ts b/libs/common/src/modules/project/entities/project.entity.ts index 7eb1956..ff324b2 100644 --- a/libs/common/src/modules/project/entities/project.entity.ts +++ b/libs/common/src/modules/project/entities/project.entity.ts @@ -17,6 +17,9 @@ export class ProjectEntity extends AbstractEntity { }) public name: string; + @Column({ length: 255, nullable: true }) + description: string; + constructor(partial: Partial) { super(); Object.assign(this, partial); From 7f739b88d8bbf012c12c1be6c989cb9dff355c13 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 11:48:53 +0400 Subject: [PATCH 004/247] Project service to CRUD project entity --- .../src/util/buildTypeORMIncludeQuery.ts | 3 + src/project/services/index.ts | 1 + src/project/services/project.service.ts | 140 ++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 src/project/services/index.ts create mode 100644 src/project/services/project.service.ts diff --git a/libs/common/src/util/buildTypeORMIncludeQuery.ts b/libs/common/src/util/buildTypeORMIncludeQuery.ts index c2d401e..029bd67 100644 --- a/libs/common/src/util/buildTypeORMIncludeQuery.ts +++ b/libs/common/src/util/buildTypeORMIncludeQuery.ts @@ -16,6 +16,9 @@ const mappingInclude: { [key: string]: any } = { subspace: { subspace: true, }, + project: { + project: true, + }, }; export function buildTypeORMIncludeQuery( diff --git a/src/project/services/index.ts b/src/project/services/index.ts new file mode 100644 index 0000000..82e8c13 --- /dev/null +++ b/src/project/services/index.ts @@ -0,0 +1 @@ +export * from './project.service'; diff --git a/src/project/services/project.service.ts b/src/project/services/project.service.ts new file mode 100644 index 0000000..62070a2 --- /dev/null +++ b/src/project/services/project.service.ts @@ -0,0 +1,140 @@ +import { ProjectRepository } from '@app/common/modules/project/repositiories'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { CreateProjectDto } from '../dto'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { ProjectEntity } from '@app/common/modules/project/entities'; +import { + TypeORMCustomModel, + TypeORMCustomModelFindAllQuery, +} from '@app/common/models/typeOrmCustom.model'; +import { ProjectDto } from '@app/common/modules/project/dtos'; +import { PageResponse } from '@app/common/dto/pagination.response.dto'; + +@Injectable() +export class ProjectService { + constructor(private readonly projectRepository: ProjectRepository) {} + + async createProject( + createProjectDto: CreateProjectDto, + ): Promise { + const { name } = createProjectDto; + + try { + // Check if the project name already exists + const isNameExist = await this.validate(name); + if (isNameExist) { + throw new HttpException( + `Project with name ${name} already exists`, + HttpStatus.CONFLICT, + ); + } + + const newProject = this.projectRepository.create(createProjectDto); + const savedProject = await this.projectRepository.save(newProject); + + return new SuccessResponseDto({ + message: `Project with ID ${savedProject.uuid} successfully created`, + data: savedProject, + statusCode: HttpStatus.CREATED, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + 'An error occurred while creating the project. Please try again later.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async deleteProject(uuid: string): Promise { + try { + // Find the project by UUID + const project = await this.findOne(uuid); + + if (!project) { + throw new HttpException( + `Project with ID ${uuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + // Delete the project + await this.projectRepository.delete({ uuid }); + + return new SuccessResponseDto({ + message: `Project with ID ${uuid} successfully deleted`, + statusCode: HttpStatus.OK, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + `An error occurred while deleting the project ${uuid}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async listProjects( + pageable: Partial, + ): Promise { + try { + pageable.modelName = 'project'; + const customModel = TypeORMCustomModel(this.projectRepository); + + const { baseResponseDto, paginationResponseDto } = + await customModel.findAll(pageable); + return new PageResponse( + baseResponseDto, + paginationResponseDto, + ); + } catch (error) { + throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + + async getProject(uuid: string): Promise { + try { + // Find the project by UUID + const project = await this.findOne(uuid); + + if (!project) { + throw new HttpException( + `Project with ID ${uuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + return new SuccessResponseDto({ + message: `Project with ID ${uuid} retrieved successfully`, + data: project, + statusCode: HttpStatus.OK, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + `An error occurred while retrieving the project with id ${uuid}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async findOne(uuid: string): Promise { + const project = await this.projectRepository.findOne({ where: { uuid } }); + return project; + } + + async validate(name: string): Promise { + const project = await this.projectRepository.findOne({ where: { name } }); + return !project; + } +} From 768ea7cad8b709cab5979d2ae0e68f9791b333d8 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 12:26:53 +0400 Subject: [PATCH 005/247] add update project --- src/project/services/project.service.ts | 46 +++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/project/services/project.service.ts b/src/project/services/project.service.ts index 62070a2..6ba7e0c 100644 --- a/src/project/services/project.service.ts +++ b/src/project/services/project.service.ts @@ -128,6 +128,52 @@ export class ProjectService { } } + async updateProject( + uuid: string, + updateProjectDto: CreateProjectDto, + ): Promise { + try { + // Find the project by UUID + const project = await this.findOne(uuid); + + if (!project) { + throw new HttpException( + `Project with ID ${uuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + if (updateProjectDto.name && updateProjectDto.name !== project.name) { + const isNameExist = await this.validate(updateProjectDto.name); + if (isNameExist) { + throw new HttpException( + `Project with name ${updateProjectDto.name} already exists`, + HttpStatus.CONFLICT, + ); + } + } + + // Update the project details + Object.assign(project, updateProjectDto); + const updatedProject = await this.projectRepository.save(project); + + return new SuccessResponseDto({ + message: `Project with ID ${uuid} successfully updated`, + data: updatedProject, + statusCode: HttpStatus.OK, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + `An error occurred while updating the project with ID ${uuid}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async findOne(uuid: string): Promise { const project = await this.projectRepository.findOne({ where: { uuid } }); return project; From bab57ff5eb719934cc0c26fc5f8810a0f57b5866 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 12:27:40 +0400 Subject: [PATCH 006/247] add route and api summary and details for project --- libs/common/src/constants/controller-route.ts | 81 ++++++++++++------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index eeacd0f..9966637 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -1,4 +1,29 @@ export class ControllerRoute { + static PROJECT = class { + public static readonly ROUTE = 'projects'; + static ACTIONS = class { + public static readonly CREATE_PROJECT_SUMMARY = 'Create a new project'; + public static readonly CREATE_PROJECT_DESCRIPTION = + 'This endpoint allows you to create a new project by providing the required project details.'; + + public static readonly GET_PROJECT_SUMMARY = 'Retrieve project details'; + public static readonly GET_PROJECT_DESCRIPTION = + 'This endpoint retrieves the details of a project by its unique identifier (UUID).'; + + public static readonly UPDATE_PROJECT_SUMMARY = 'Update project details'; + public static readonly UPDATE_PROJECT_DESCRIPTION = + 'This endpoint updates the details of an existing project using its unique identifier (UUID).'; + + public static readonly LIST_PROJECTS_SUMMARY = 'List all projects'; + public static readonly LIST_PROJECTS_DESCRIPTION = + 'This endpoint retrieves a list of all existing projects, including their details.'; + + public static readonly DELETE_PROJECT_SUMMARY = 'Delete a project'; + public static readonly DELETE_PROJECT_DESCRIPTION = + 'This endpoint deletes an existing project by its unique identifier (UUID).'; + }; + }; + static REGION = class { public static readonly ROUTE = 'region'; static ACTIONS = class { @@ -649,40 +674,40 @@ export class ControllerRoute { public static readonly UPDATE_DEVICE_SCHEDULE_DESCRIPTION = 'This endpoint updates the schedule for a specific device.'; }; - }; - static DEVICE_STATUS_FIREBASE = class { - public static readonly ROUTE = 'device-status-firebase'; + static DEVICE_STATUS_FIREBASE = class { + public static readonly ROUTE = 'device-status-firebase'; - static ACTIONS = class { - public static readonly ADD_DEVICE_STATUS_SUMMARY = - 'Add device status to Firebase'; - public static readonly ADD_DEVICE_STATUS_DESCRIPTION = - 'This endpoint adds a device status in Firebase based on the provided device UUID.'; + static ACTIONS = class { + public static readonly ADD_DEVICE_STATUS_SUMMARY = + 'Add device status to Firebase'; + public static readonly ADD_DEVICE_STATUS_DESCRIPTION = + 'This endpoint adds a device status in Firebase based on the provided device UUID.'; - public static readonly GET_DEVICE_STATUS_SUMMARY = - 'Get device status from Firebase'; - public static readonly GET_DEVICE_STATUS_DESCRIPTION = - 'This endpoint retrieves a device status from Firebase using the device UUID.'; + public static readonly GET_DEVICE_STATUS_SUMMARY = + 'Get device status from Firebase'; + public static readonly GET_DEVICE_STATUS_DESCRIPTION = + 'This endpoint retrieves a device status from Firebase using the device UUID.'; + }; }; - }; - static DEVICE_MESSAGES_SUBSCRIPTION = class { - public static readonly ROUTE = 'device-messages/subscription'; + static DEVICE_MESSAGES_SUBSCRIPTION = class { + public static readonly ROUTE = 'device-messages/subscription'; - static ACTIONS = class { - public static readonly ADD_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY = - 'Add device messages subscription'; - public static readonly ADD_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION = - 'This endpoint adds a subscription for device messages.'; + static ACTIONS = class { + public static readonly ADD_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY = + 'Add device messages subscription'; + public static readonly ADD_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION = + 'This endpoint adds a subscription for device messages.'; - public static readonly GET_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY = - 'Get device messages subscription'; - public static readonly GET_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION = - 'This endpoint fetches a user’s subscription for a specific device.'; + public static readonly GET_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY = + 'Get device messages subscription'; + public static readonly GET_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION = + 'This endpoint fetches a user’s subscription for a specific device.'; - public static readonly DELETE_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY = - 'Delete device messages subscription'; - public static readonly DELETE_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION = - 'This endpoint deletes a user’s subscription for device messages.'; + public static readonly DELETE_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY = + 'Delete device messages subscription'; + public static readonly DELETE_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION = + 'This endpoint deletes a user’s subscription for device messages.'; + }; }; }; } From 459e97149828b14fce58a626e6fd4d0bd3c8dfbb Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 12:44:57 +0400 Subject: [PATCH 007/247] added dto --- src/project/dto/create-project.dto.ts | 21 +++++++++++++++++++++ src/project/dto/get-project.param.ts | 11 +++++++++++ src/project/dto/index.ts | 2 ++ 3 files changed, 34 insertions(+) create mode 100644 src/project/dto/create-project.dto.ts create mode 100644 src/project/dto/get-project.param.ts create mode 100644 src/project/dto/index.ts diff --git a/src/project/dto/create-project.dto.ts b/src/project/dto/create-project.dto.ts new file mode 100644 index 0000000..9637856 --- /dev/null +++ b/src/project/dto/create-project.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNotEmpty, IsOptional } from 'class-validator'; + +export class CreateProjectDto { + @ApiProperty({ + example: 'Project 1', + description: 'Name of the project', + }) + @IsString() + @IsNotEmpty() + name: string; + + @ApiProperty({ + example: 'Optional description of the project', + description: 'Description of the project', + required: false, + }) + @IsString() + @IsOptional() + description?: string; +} diff --git a/src/project/dto/get-project.param.ts b/src/project/dto/get-project.param.ts new file mode 100644 index 0000000..2ec5825 --- /dev/null +++ b/src/project/dto/get-project.param.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; + +export class GetProjectParam { + @ApiProperty({ + description: 'UUID of the Project', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + projectUuid: string; +} diff --git a/src/project/dto/index.ts b/src/project/dto/index.ts new file mode 100644 index 0000000..568bd4f --- /dev/null +++ b/src/project/dto/index.ts @@ -0,0 +1,2 @@ +export * from './create-project.dto'; +export * from './get-project.param'; From bc2987295fe7b7c03b77f65863a432666a611389 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 12:49:45 +0400 Subject: [PATCH 008/247] fixed typo in controller route --- libs/common/src/constants/controller-route.ts | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 9966637..2aff604 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -674,40 +674,40 @@ export class ControllerRoute { public static readonly UPDATE_DEVICE_SCHEDULE_DESCRIPTION = 'This endpoint updates the schedule for a specific device.'; }; - static DEVICE_STATUS_FIREBASE = class { - public static readonly ROUTE = 'device-status-firebase'; + }; + static DEVICE_STATUS_FIREBASE = class { + public static readonly ROUTE = 'device-status-firebase'; - static ACTIONS = class { - public static readonly ADD_DEVICE_STATUS_SUMMARY = - 'Add device status to Firebase'; - public static readonly ADD_DEVICE_STATUS_DESCRIPTION = - 'This endpoint adds a device status in Firebase based on the provided device UUID.'; + static ACTIONS = class { + public static readonly ADD_DEVICE_STATUS_SUMMARY = + 'Add device status to Firebase'; + public static readonly ADD_DEVICE_STATUS_DESCRIPTION = + 'This endpoint adds a device status in Firebase based on the provided device UUID.'; - public static readonly GET_DEVICE_STATUS_SUMMARY = - 'Get device status from Firebase'; - public static readonly GET_DEVICE_STATUS_DESCRIPTION = - 'This endpoint retrieves a device status from Firebase using the device UUID.'; - }; + public static readonly GET_DEVICE_STATUS_SUMMARY = + 'Get device status from Firebase'; + public static readonly GET_DEVICE_STATUS_DESCRIPTION = + 'This endpoint retrieves a device status from Firebase using the device UUID.'; }; - static DEVICE_MESSAGES_SUBSCRIPTION = class { - public static readonly ROUTE = 'device-messages/subscription'; + }; + static DEVICE_MESSAGES_SUBSCRIPTION = class { + public static readonly ROUTE = 'device-messages/subscription'; - static ACTIONS = class { - public static readonly ADD_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY = - 'Add device messages subscription'; - public static readonly ADD_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION = - 'This endpoint adds a subscription for device messages.'; + static ACTIONS = class { + public static readonly ADD_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY = + 'Add device messages subscription'; + public static readonly ADD_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION = + 'This endpoint adds a subscription for device messages.'; - public static readonly GET_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY = - 'Get device messages subscription'; - public static readonly GET_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION = - 'This endpoint fetches a user’s subscription for a specific device.'; + public static readonly GET_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY = + 'Get device messages subscription'; + public static readonly GET_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION = + 'This endpoint fetches a user’s subscription for a specific device.'; - public static readonly DELETE_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY = - 'Delete device messages subscription'; - public static readonly DELETE_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION = - 'This endpoint deletes a user’s subscription for device messages.'; - }; + public static readonly DELETE_DEVICE_MESSAGES_SUBSCRIPTION_SUMMARY = + 'Delete device messages subscription'; + public static readonly DELETE_DEVICE_MESSAGES_SUBSCRIPTION_DESCRIPTION = + 'This endpoint deletes a user’s subscription for device messages.'; }; }; } From 5b0494b41572a1c260ecac96e60fec20cd7c7016 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 12:59:46 +0400 Subject: [PATCH 009/247] fixed uuid --- libs/common/src/modules/project/entities/project.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/modules/project/entities/project.entity.ts b/libs/common/src/modules/project/entities/project.entity.ts index ff324b2..6c18b6c 100644 --- a/libs/common/src/modules/project/entities/project.entity.ts +++ b/libs/common/src/modules/project/entities/project.entity.ts @@ -10,7 +10,7 @@ export class ProjectEntity extends AbstractEntity { default: () => 'gen_random_uuid()', nullable: false, }) - public project_id: string; + public uuid: string; @Column({ nullable: false, From 1e5b61e3e05931fd0ef5c2389bd6d9b75f586ea0 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 13:26:17 +0400 Subject: [PATCH 010/247] added project endpoints --- src/app.module.ts | 2 + src/project/controllers/index.ts | 1 + src/project/controllers/project.controller.ts | 89 +++++++++++++++++++ src/project/index.ts | 1 + src/project/project.module.ts | 12 +++ src/project/services/project.service.ts | 3 +- 6 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 src/project/controllers/index.ts create mode 100644 src/project/controllers/project.controller.ts create mode 100644 src/project/index.ts create mode 100644 src/project/project.module.ts diff --git a/src/app.module.ts b/src/app.module.ts index 609147c..bb5986b 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -22,6 +22,7 @@ import { VisitorPasswordModule } from './vistor-password/visitor-password.module import { ScheduleModule } from './schedule/schedule.module'; import { SpaceModule } from './space/space.module'; import { ProductModule } from './product'; +import { ProjectModule } from './project'; @Module({ imports: [ ConfigModule.forRoot({ @@ -48,6 +49,7 @@ import { ProductModule } from './product'; VisitorPasswordModule, ScheduleModule, ProductModule, + ProjectModule, ], providers: [ { diff --git a/src/project/controllers/index.ts b/src/project/controllers/index.ts new file mode 100644 index 0000000..2b606fd --- /dev/null +++ b/src/project/controllers/index.ts @@ -0,0 +1 @@ +export * from './project.controller'; diff --git a/src/project/controllers/project.controller.ts b/src/project/controllers/project.controller.ts new file mode 100644 index 0000000..a39ced1 --- /dev/null +++ b/src/project/controllers/project.controller.ts @@ -0,0 +1,89 @@ +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { + Body, + Controller, + Delete, + Get, + Param, + Post, + Put, + Query, + UseGuards, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { ProjectService } from '../services'; +import { CreateProjectDto, GetProjectParam } from '../dto'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; + +@ApiTags('Project Module') +@Controller({ + version: '1', + path: ControllerRoute.PROJECT.ROUTE, +}) +export class ProjectController { + constructor(private readonly projectService: ProjectService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.PROJECT.ACTIONS.CREATE_PROJECT_SUMMARY, + description: ControllerRoute.PROJECT.ACTIONS.CREATE_PROJECT_DESCRIPTION, + }) + @Post() + async createProject(@Body() dto: CreateProjectDto): Promise { + return this.projectService.createProject(dto); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.PROJECT.ACTIONS.UPDATE_PROJECT_SUMMARY, + description: ControllerRoute.PROJECT.ACTIONS.UPDATE_PROJECT_DESCRIPTION, + }) + @Put(':projectUuid') + async updateProject( + @Param() params: GetProjectParam, + @Body() dto: CreateProjectDto, + ): Promise { + return this.projectService.updateProject(params.projectUuid, dto); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.PROJECT.ACTIONS.DELETE_PROJECT_SUMMARY, + description: ControllerRoute.PROJECT.ACTIONS.DELETE_PROJECT_DESCRIPTION, + }) + @Delete(':projectUuid') + async deleteProject( + @Param() params: GetProjectParam, + ): Promise { + return this.projectService.deleteProject(params.projectUuid); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.PROJECT.ACTIONS.LIST_PROJECTS_SUMMARY, + description: ControllerRoute.PROJECT.ACTIONS.LIST_PROJECTS_DESCRIPTION, + }) + @Get() + async list( + @Query() query: PaginationRequestGetListDto, + ): Promise { + return this.projectService.listProjects(query); + } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.PROJECT.ACTIONS.GET_PROJECT_SUMMARY, + description: ControllerRoute.PROJECT.ACTIONS.GET_PROJECT_DESCRIPTION, + }) + @Get(':projectUuid') + async findOne(@Param() params: GetProjectParam): Promise { + return this.projectService.getProject(params.projectUuid); + } +} diff --git a/src/project/index.ts b/src/project/index.ts new file mode 100644 index 0000000..c3c63b7 --- /dev/null +++ b/src/project/index.ts @@ -0,0 +1 @@ +export * from './project.module'; diff --git a/src/project/project.module.ts b/src/project/project.module.ts new file mode 100644 index 0000000..9d82ba9 --- /dev/null +++ b/src/project/project.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { ProjectController } from './controllers'; +import { ProjectService } from './services'; +import { ProjectRepository } from '@app/common/modules/project/repositiories'; + +@Module({ + imports: [], + controllers: [ProjectController], + providers: [ProjectService, ProjectRepository], + exports: [ProjectService], +}) +export class ProjectModule {} diff --git a/src/project/services/project.service.ts b/src/project/services/project.service.ts index 6ba7e0c..63ea252 100644 --- a/src/project/services/project.service.ts +++ b/src/project/services/project.service.ts @@ -180,7 +180,6 @@ export class ProjectService { } async validate(name: string): Promise { - const project = await this.projectRepository.findOne({ where: { name } }); - return !project; + return await this.projectRepository.exists({ where: { name } }); } } From ab8466bf3287ee67dcd902fd33409f71396cb97a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 15:41:45 +0400 Subject: [PATCH 011/247] added project community relation --- .../src/modules/community/entities/community.entity.ts | 8 +++++++- .../common/src/modules/project/entities/project.entity.ts | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/libs/common/src/modules/community/entities/community.entity.ts b/libs/common/src/modules/community/entities/community.entity.ts index 3b6f122..a94e586 100644 --- a/libs/common/src/modules/community/entities/community.entity.ts +++ b/libs/common/src/modules/community/entities/community.entity.ts @@ -1,7 +1,8 @@ -import { Column, Entity, OneToMany, Unique } from 'typeorm'; +import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { CommunityDto } from '../dtos'; import { SpaceEntity } from '../../space/entities'; +import { ProjectEntity } from '../../project/entities'; @Entity({ name: 'community' }) @Unique(['name']) @@ -31,4 +32,9 @@ export class CommunityEntity extends AbstractEntity { nullable: true, }) externalId: string; + + @ManyToOne(() => ProjectEntity, (project) => project.communities, { + nullable: false, + }) + project: ProjectEntity; } diff --git a/libs/common/src/modules/project/entities/project.entity.ts b/libs/common/src/modules/project/entities/project.entity.ts index 6c18b6c..04e3f11 100644 --- a/libs/common/src/modules/project/entities/project.entity.ts +++ b/libs/common/src/modules/project/entities/project.entity.ts @@ -1,6 +1,7 @@ -import { Entity, Column, Unique } from 'typeorm'; +import { Entity, Column, Unique, OneToMany } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { ProjectDto } from '../dtos'; +import { CommunityEntity } from '../../community/entities'; @Entity({ name: 'project' }) @Unique(['name']) @@ -19,6 +20,9 @@ export class ProjectEntity extends AbstractEntity { @Column({ length: 255, nullable: true }) description: string; + + @OneToMany(() => CommunityEntity, (community) => community.project) + communities: CommunityEntity[]; constructor(partial: Partial) { super(); From 760ca019ccb8b59e11f62330232dc2cedfffe05e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 16:21:41 +0400 Subject: [PATCH 012/247] Fixed typo --- libs/common/src/modules/project/project.repository.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/modules/project/project.repository.module.ts b/libs/common/src/modules/project/project.repository.module.ts index 0b744f8..d067af1 100644 --- a/libs/common/src/modules/project/project.repository.module.ts +++ b/libs/common/src/modules/project/project.repository.module.ts @@ -8,4 +8,4 @@ import { ProjectEntity } from './entities'; controllers: [], imports: [TypeOrmModule.forFeature([ProjectEntity])], }) -export class ProjectEntityModule {} +export class ProjectRepositoryModule {} From fc28a91b8202b6a95790c5a4adca94174484d8b1 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 16:22:22 +0400 Subject: [PATCH 013/247] updated api endpoint for community to address it through project --- libs/common/src/constants/controller-route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 2aff604..94baf8e 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -35,7 +35,7 @@ export class ControllerRoute { }; static COMMUNITY = class { - public static readonly ROUTE = 'communities'; + public static readonly ROUTE = '/projects/:projectUuid/communities'; static ACTIONS = class { public static readonly GET_COMMUNITY_BY_ID_SUMMARY = 'Get community by community community uuid'; From 19825540ddfc521ae835462f8723875008104d17 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 16:22:54 +0400 Subject: [PATCH 014/247] updated endpoint and validation to manage project entity in communty CRUD --- src/community/community.module.ts | 2 + .../controllers/community.controller.ts | 20 ++++---- src/community/dtos/get.community.dto.ts | 3 +- src/community/dtos/index.ts | 2 + src/community/dtos/project.param.dto.ts | 11 +++++ src/community/services/community.service.ts | 47 +++++++++++++++++-- 6 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 src/community/dtos/project.param.dto.ts diff --git a/src/community/community.module.ts b/src/community/community.module.ts index 106d9d1..a87ef85 100644 --- a/src/community/community.module.ts +++ b/src/community/community.module.ts @@ -9,6 +9,7 @@ import { UserRepositoryModule } from '@app/common/modules/user/user.repository.m import { SpacePermissionService } from '@app/common/helper/services'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; +import { ProjectRepository } from '@app/common/modules/project/repositiories'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], @@ -20,6 +21,7 @@ import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service TuyaService, CommunityRepository, SpacePermissionService, + ProjectRepository, ], exports: [CommunityService, SpacePermissionService], }) diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index 86bad74..5548e84 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -19,6 +19,7 @@ import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { ControllerRoute } from '@app/common/constants/controller-route'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; +import { ProjectParam } from '../dtos'; @ApiTags('Community Module') @Controller({ @@ -36,9 +37,10 @@ export class CommunityController { description: ControllerRoute.COMMUNITY.ACTIONS.CREATE_COMMUNITY_DESCRIPTION, }) async createCommunity( + @Param() param: ProjectParam, @Body() addCommunityDto: AddCommunityDto, ): Promise { - return await this.communityService.createCommunity(addCommunityDto); + return await this.communityService.createCommunity(param, addCommunityDto); } @ApiBearerAuth() @@ -52,7 +54,7 @@ export class CommunityController { async getCommunityByUuid( @Param() params: GetCommunityParams, ): Promise { - return await this.communityService.getCommunityById(params.communityUuid); + return await this.communityService.getCommunityById(params); } @ApiBearerAuth() @@ -63,9 +65,10 @@ export class CommunityController { }) @Get() async getCommunities( + @Param() param: ProjectParam, @Query() query: PaginationRequestGetListDto, ): Promise { - return this.communityService.getCommunities(query); + return this.communityService.getCommunities(param, query); } @ApiBearerAuth() @@ -76,13 +79,10 @@ export class CommunityController { }) @Put('/:communityUuid') async updateCommunity( - @Param() param: GetCommunityParams, + @Param() params: GetCommunityParams, @Body() updateCommunityDto: UpdateCommunityNameDto, ) { - return this.communityService.updateCommunity( - param.communityUuid, - updateCommunityDto, - ); + return this.communityService.updateCommunity(params, updateCommunityDto); } @ApiBearerAuth() @@ -93,8 +93,8 @@ export class CommunityController { description: ControllerRoute.COMMUNITY.ACTIONS.DELETE_COMMUNITY_DESCRIPTION, }) async deleteCommunity( - @Param() param: GetCommunityParams, + @Param() params: GetCommunityParams, ): Promise { - return this.communityService.deleteCommunity(param.communityUuid); + return this.communityService.deleteCommunity(params); } } diff --git a/src/community/dtos/get.community.dto.ts b/src/community/dtos/get.community.dto.ts index fe2cf46..d6da53a 100644 --- a/src/community/dtos/get.community.dto.ts +++ b/src/community/dtos/get.community.dto.ts @@ -10,6 +10,7 @@ import { IsUUID, Min, } from 'class-validator'; +import { ProjectParam } from './project.param.dto'; export class GetCommunityDto { @ApiProperty({ @@ -21,7 +22,7 @@ export class GetCommunityDto { public communityUuid: string; } -export class GetCommunityParams { +export class GetCommunityParams extends ProjectParam { @ApiProperty({ description: 'Community id of the specific community', required: true, diff --git a/src/community/dtos/index.ts b/src/community/dtos/index.ts index 7119b23..34d8bbb 100644 --- a/src/community/dtos/index.ts +++ b/src/community/dtos/index.ts @@ -1 +1,3 @@ export * from './add.community.dto'; +export * from './project.param.dto'; +export * from './get.community.dto'; diff --git a/src/community/dtos/project.param.dto.ts b/src/community/dtos/project.param.dto.ts new file mode 100644 index 0000000..8bb2929 --- /dev/null +++ b/src/community/dtos/project.param.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; + +export class ProjectParam { + @ApiProperty({ + description: 'UUID of the project this community belongs to', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + projectUuid: string; +} diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index e833416..c21d20a 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -1,5 +1,5 @@ import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; -import { AddCommunityDto } from '../dtos'; +import { AddCommunityDto, GetCommunityParams, ProjectParam } from '../dtos'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { @@ -11,17 +11,24 @@ import { CommunityRepository } from '@app/common/modules/community/repositories' import { CommunityDto } from '@app/common/modules/community/dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; +import { ProjectRepository } from '@app/common/modules/project/repositiories'; @Injectable() export class CommunityService { constructor( private readonly communityRepository: CommunityRepository, + private readonly projectRepository: ProjectRepository, private readonly tuyaService: TuyaService, ) {} - async createCommunity(dto: AddCommunityDto): Promise { + async createCommunity( + param: ProjectParam, + dto: AddCommunityDto, + ): Promise { const { name, description } = dto; + const project = await this.validateProject(param.projectUuid); + const existingCommunity = await this.communityRepository.findOneBy({ name, }); @@ -36,6 +43,7 @@ export class CommunityService { const community = this.communityRepository.create({ name: name, description: description, + project: project, }); // Save the community to the database @@ -54,7 +62,11 @@ export class CommunityService { } } - async getCommunityById(communityUuid: string): Promise { + async getCommunityById(params: GetCommunityParams): Promise { + const { communityUuid, projectUuid } = params; + + await this.validateProject(projectUuid); + const community = await this.communityRepository.findOneBy({ uuid: communityUuid, }); @@ -75,9 +87,13 @@ export class CommunityService { } async getCommunities( + param: ProjectParam, pageable: Partial, ): Promise { + await this.validateProject(param.projectUuid); + pageable.modelName = 'community'; + pageable.where = { project: { uuid: param.projectUuid } }; const customModel = TypeORMCustomModel(this.communityRepository); @@ -91,9 +107,13 @@ export class CommunityService { } async updateCommunity( - communityUuid: string, + params: GetCommunityParams, updateCommunityDto: UpdateCommunityNameDto, ): Promise { + const { communityUuid, projectUuid } = params; + + await this.validateProject(projectUuid); + const community = await this.communityRepository.findOne({ where: { uuid: communityUuid }, }); @@ -128,7 +148,11 @@ export class CommunityService { } } - async deleteCommunity(communityUuid: string): Promise { + async deleteCommunity(params: GetCommunityParams): Promise { + const { communityUuid, projectUuid } = params; + + await this.validateProject(projectUuid); + const community = await this.communityRepository.findOne({ where: { uuid: communityUuid }, }); @@ -169,4 +193,17 @@ export class CommunityService { ); } } + + private async validateProject(uuid: string) { + const project = await this.projectRepository.findOne({ + where: { uuid }, + }); + if (!project) { + throw new HttpException( + `A project with the uuid '${uuid}' doesn't exists.`, + HttpStatus.BAD_REQUEST, + ); + } + return project; + } } From cdc95056ae7e53fe78d4142a769635332bec358e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 17:33:19 +0400 Subject: [PATCH 015/247] updated space endpoint to follow project --- libs/common/src/constants/controller-route.ts | 3 +- src/space/controllers/space.controller.ts | 26 +++---- src/space/dtos/add.space.dto.ts | 3 +- src/space/dtos/community-space.param.ts | 3 +- src/space/dtos/index.ts | 2 + src/space/dtos/project.param.dto.ts | 11 +++ src/space/dtos/update.space.dto.ts | 4 ++ src/space/services/space.service.ts | 72 +++++++++++++------ src/space/space.module.ts | 2 + 9 files changed, 87 insertions(+), 39 deletions(-) create mode 100644 src/space/dtos/project.param.dto.ts create mode 100644 src/space/dtos/update.space.dto.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 94baf8e..7cb25ce 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -140,7 +140,8 @@ export class ControllerRoute { }; static SPACE = class { - public static readonly ROUTE = '/communities/:communityUuid/spaces'; + public static readonly ROUTE = + '/projects/:projectUuid/communities/:communityUuid/spaces'; static ACTIONS = class { public static readonly CREATE_SPACE_SUMMARY = 'Create a new space'; public static readonly CREATE_SPACE_DESCRIPTION = diff --git a/src/space/controllers/space.controller.ts b/src/space/controllers/space.controller.ts index e0615eb..d31bb23 100644 --- a/src/space/controllers/space.controller.ts +++ b/src/space/controllers/space.controller.ts @@ -12,7 +12,7 @@ import { UseGuards, } from '@nestjs/common'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; -import { AddSpaceDto, CommunitySpaceParam } from '../dtos'; +import { AddSpaceDto, CommunitySpaceParam, UpdateSpaceDto } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { GetSpaceParam } from '../dtos/get.space.param'; @@ -37,7 +37,7 @@ export class SpaceController { ): Promise { return await this.spaceService.createSpace( addSpaceDto, - communitySpaceParam.communityUuid, + communitySpaceParam, ); } @@ -51,11 +51,9 @@ export class SpaceController { }) @Get() async getHierarchy( - @Param() param: CommunitySpaceParam, + @Param() params: CommunitySpaceParam, ): Promise { - return this.spaceService.getSpacesHierarchyForCommunity( - param.communityUuid, - ); + return this.spaceService.getSpacesHierarchyForCommunity(params); } @ApiBearerAuth() @@ -66,7 +64,7 @@ export class SpaceController { }) @Delete('/:spaceUuid') async deleteSpace(@Param() params: GetSpaceParam): Promise { - return this.spaceService.delete(params.spaceUuid, params.communityUuid); + return this.spaceService.delete(params); } @ApiBearerAuth() @@ -78,13 +76,9 @@ export class SpaceController { }) async updateSpace( @Param() params: GetSpaceParam, - @Body() updateSpaceDto: AddSpaceDto, + @Body() updateSpaceDto: UpdateSpaceDto, ): Promise { - return this.spaceService.updateSpace( - params.spaceUuid, - params.communityUuid, - updateSpaceDto, - ); + return this.spaceService.updateSpace(params, updateSpaceDto); } @ApiBearerAuth() @@ -95,7 +89,7 @@ export class SpaceController { }) @Get('/:spaceUuid') async get(@Param() params: GetSpaceParam): Promise { - return this.spaceService.findOne(params.spaceUuid); + return this.spaceService.findOne(params); } @ApiBearerAuth() @@ -108,7 +102,7 @@ export class SpaceController { async getHierarchyUnderSpace( @Param() params: GetSpaceParam, ): Promise { - return this.spaceService.getSpacesHierarchyForSpace(params.spaceUuid); + return this.spaceService.getSpacesHierarchyForSpace(params); } //should it be post? @@ -123,6 +117,6 @@ export class SpaceController { async generateSpaceInvitationCode( @Param() params: GetSpaceParam, ): Promise { - return this.spaceService.getSpaceInvitationCode(params.spaceUuid); + return this.spaceService.getSpaceInvitationCode(params); } } diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index 1f369b1..554f17b 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -30,7 +30,7 @@ export class AddSpaceDto { parentUuid?: string; @IsString() - @IsNotEmpty() + @IsOptional() public icon: string; @ApiProperty({ @@ -56,6 +56,7 @@ export class AddSpaceDto { @IsArray() @ValidateNested({ each: true }) + @IsOptional() @Type(() => ProductAssignmentDto) products: ProductAssignmentDto[]; } diff --git a/src/space/dtos/community-space.param.ts b/src/space/dtos/community-space.param.ts index ab35e4e..caf05b0 100644 --- a/src/space/dtos/community-space.param.ts +++ b/src/space/dtos/community-space.param.ts @@ -1,7 +1,8 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsUUID } from 'class-validator'; +import { ProjectParam } from './project.param.dto'; -export class CommunitySpaceParam { +export class CommunitySpaceParam extends ProjectParam { @ApiProperty({ description: 'UUID of the community this space belongs to', example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', diff --git a/src/space/dtos/index.ts b/src/space/dtos/index.ts index 2cebe7b..3c85266 100644 --- a/src/space/dtos/index.ts +++ b/src/space/dtos/index.ts @@ -3,3 +3,5 @@ export * from './community-space.param'; export * from './get.space.param'; export * from './user-space.param'; export * from './subspace'; +export * from './project.param.dto'; +export * from './update.space.dto'; diff --git a/src/space/dtos/project.param.dto.ts b/src/space/dtos/project.param.dto.ts new file mode 100644 index 0000000..8bb2929 --- /dev/null +++ b/src/space/dtos/project.param.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; + +export class ProjectParam { + @ApiProperty({ + description: 'UUID of the project this community belongs to', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + projectUuid: string; +} diff --git a/src/space/dtos/update.space.dto.ts b/src/space/dtos/update.space.dto.ts new file mode 100644 index 0000000..d40476b --- /dev/null +++ b/src/space/dtos/update.space.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { AddSpaceDto } from './add.space.dto'; + +export class UpdateSpaceDto extends PartialType(AddSpaceDto) {} diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 6e3e600..0055ddf 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -5,7 +5,7 @@ import { HttpStatus, Injectable, } from '@nestjs/common'; -import { AddSpaceDto } from '../dtos'; +import { AddSpaceDto, CommunitySpaceParam, GetSpaceParam, UpdateSpaceDto } from '../dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { CommunityRepository } from '@app/common/modules/community/repositories'; @@ -13,6 +13,7 @@ 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'; +import { ProjectRepository } from '@app/common/modules/project/repositiories'; @Injectable() export class SpaceService { @@ -21,15 +22,19 @@ export class SpaceService { private readonly communityRepository: CommunityRepository, private readonly spaceLinkService: SpaceLinkService, private readonly spaceProductService: SpaceProductService, + private readonly projectRepository: ProjectRepository, ) {} async createSpace( addSpaceDto: AddSpaceDto, - communityId: string, + params: CommunitySpaceParam, ): Promise { const { parentUuid, direction, products } = addSpaceDto; + const { communityUuid, projectUuid } = params; - const community = await this.validateCommunity(communityId); + await this.validateProject(projectUuid); + + const community = await this.validateCommunity(communityUuid); const parent = parentUuid ? await this.validateSpace(parentUuid) : null; try { @@ -67,9 +72,11 @@ export class SpaceService { } async getSpacesHierarchyForCommunity( - communityUuid: string, + params: CommunitySpaceParam, ): Promise { + const { communityUuid, projectUuid } = params; await this.validateCommunity(communityUuid); + await this.validateProject(projectUuid); try { // Get all spaces related to the community, including the parent-child relations const spaces = await this.spaceRepository.find({ @@ -80,7 +87,7 @@ export class SpaceService { 'incomingConnections', 'spaceProducts', 'spaceProducts.product', - ], // Include parent and children relations + ], }); // Organize spaces into a hierarchical structure @@ -99,9 +106,14 @@ export class SpaceService { } } - async findOne(spaceUuid: string): Promise { + async findOne(params: GetSpaceParam): Promise { + const { communityUuid, spaceUuid, projectUuid } = params; try { - const space = await this.validateSpace(spaceUuid); + const space = await this.validateCommunityAndSpace( + communityUuid, + spaceUuid, + projectUuid, + ); return new SuccessResponseDto({ message: `Space with ID ${spaceUuid} successfully fetched`, @@ -119,15 +131,14 @@ export class SpaceService { } } - async delete( - spaceUuid: string, - communityUuid: string, - ): Promise { + async delete(params: GetSpaceParam): Promise { try { + const { communityUuid, spaceUuid, projectUuid } = params; // First, check if the community exists const space = await this.validateCommunityAndSpace( communityUuid, spaceUuid, + projectUuid, ); // Delete the space @@ -149,14 +160,15 @@ export class SpaceService { } async updateSpace( - spaceUuid: string, - communityId: string, - updateSpaceDto: AddSpaceDto, + params: GetSpaceParam, + updateSpaceDto: UpdateSpaceDto, ): Promise { + const { communityUuid, spaceUuid, projectUuid } = params; try { const space = await this.validateCommunityAndSpace( - communityId, + communityUuid, spaceUuid, + projectUuid, ); // If a parentId is provided, check if the parent exists @@ -193,9 +205,10 @@ export class SpaceService { } async getSpacesHierarchyForSpace( - spaceUuid: string, + params: GetSpaceParam, ): Promise { - await this.validateSpace(spaceUuid); + const { spaceUuid, communityUuid, projectUuid } = params; + await this.validateCommunityAndSpace(communityUuid, spaceUuid, projectUuid); try { // Get all spaces that are children of the provided space, including the parent-child relations @@ -220,11 +233,16 @@ export class SpaceService { } } - async getSpaceInvitationCode(spaceUuid: string): Promise { + async getSpaceInvitationCode(params: GetSpaceParam): Promise { + const { communityUuid, spaceUuid, projectUuid } = params; try { const invitationCode = generateRandomString(6); - const space = await this.validateSpace(spaceUuid); + const space = await this.validateCommunityAndSpace( + communityUuid, + spaceUuid, + projectUuid, + ); space.invitationCode = invitationCode; await this.spaceRepository.save(space); @@ -283,7 +301,13 @@ export class SpaceService { return community; } - async validateCommunityAndSpace(communityUuid: string, spaceUuid: string) { + async validateCommunityAndSpace( + communityUuid: string, + spaceUuid: string, + projectUuid: string, + ) { + await this.validateProject(projectUuid); + const community = await this.validateCommunity(communityUuid); if (!community) { this.throwNotFound('Community', communityUuid); @@ -301,6 +325,14 @@ export class SpaceService { return space; } + private async validateProject(uuid: string) { + const project = await this.projectRepository.findOne({ + where: { uuid }, + }); + + if (!project) this.throwNotFound('Project', uuid); + } + private throwNotFound(entity: string, uuid: string) { throw new HttpException( `${entity} with ID ${uuid} not found`, diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 9c80fa1..002c2df 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -42,6 +42,7 @@ import { DeviceService } from 'src/device/services'; import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service'; import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories'; import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; +import { ProjectRepository } from '@app/common/modules/project/repositiories'; @Module({ imports: [ConfigModule, SpaceRepositoryModule], @@ -79,6 +80,7 @@ import { SceneDeviceRepository } from '@app/common/modules/scene-device/reposito SceneDeviceRepository, SpaceProductService, SpaceProductRepository, + ProjectRepository, ], exports: [SpaceService], }) From bb031dd15b1fc384f245f38582fe333741c25d8d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 17:37:22 +0400 Subject: [PATCH 016/247] updated space scee endpoint to match project entity --- libs/common/src/constants/controller-route.ts | 2 +- src/space/services/space-scene.service.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 7cb25ce..51c9b4e 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -182,7 +182,7 @@ export class ControllerRoute { static SPACE_SCENE = class { public static readonly ROUTE = - '/communities/:communityUuid/spaces/:spaceUuid/scenes'; + '/projects/:projectUuid/communities/:communityUuid/spaces/:spaceUuid/scenes'; static ACTIONS = class { public static readonly GET_TAP_TO_RUN_SCENE_BY_SPACE_SUMMARY = 'Retrieve Tap-to-Run Scenes by Space'; diff --git a/src/space/services/space-scene.service.ts b/src/space/services/space-scene.service.ts index ac26889..57d63a5 100644 --- a/src/space/services/space-scene.service.ts +++ b/src/space/services/space-scene.service.ts @@ -18,11 +18,12 @@ export class SpaceSceneService { getSceneDto: GetSceneDto, ): Promise { try { - const { spaceUuid, communityUuid } = params; + const { spaceUuid, communityUuid, projectUuid } = params; await this.spaceSevice.validateCommunityAndSpace( communityUuid, spaceUuid, + projectUuid, ); const scenes = await this.sceneSevice.findScenesBySpace( From dade2017523c6703b2fe52221a2c87e90ee6d74a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 17:44:22 +0400 Subject: [PATCH 017/247] api endpoints for space user controller base root has been mapped from project --- libs/common/src/constants/controller-route.ts | 2 +- .../controllers/space-user.controller.ts | 10 +---- src/space/services/space-user.service.ts | 41 ++++++++----------- 3 files changed, 20 insertions(+), 33 deletions(-) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 51c9b4e..caf7639 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -193,7 +193,7 @@ export class ControllerRoute { static SPACE_USER = class { public static readonly ROUTE = - '/communities/:communityUuid/spaces/:spaceUuid/user'; + '/projects/:projectUuid/communities/:communityUuid/spaces/:spaceUuid/user'; static ACTIONS = class { public static readonly ASSOCIATE_SPACE_USER_SUMMARY = 'Associate a user to a space'; diff --git a/src/space/controllers/space-user.controller.ts b/src/space/controllers/space-user.controller.ts index 89fa3a0..9efdac8 100644 --- a/src/space/controllers/space-user.controller.ts +++ b/src/space/controllers/space-user.controller.ts @@ -26,10 +26,7 @@ export class SpaceUserController { async associateUserToSpace( @Param() params: UserSpaceParam, ): Promise { - return this.spaceUserService.associateUserToSpace( - params.userUuid, - params.spaceUuid, - ); + return this.spaceUserService.associateUserToSpace(params); } @ApiBearerAuth() @@ -43,9 +40,6 @@ export class SpaceUserController { async disassociateUserFromSpace( @Param() params: UserSpaceParam, ): Promise { - return this.spaceUserService.disassociateUserFromSpace( - params.userUuid, - params.spaceUuid, - ); + return this.spaceUserService.disassociateUserFromSpace(params); } } diff --git a/src/space/services/space-user.service.ts b/src/space/services/space-user.service.ts index d4f2e9d..0e10e1a 100644 --- a/src/space/services/space-user.service.ts +++ b/src/space/services/space-user.service.ts @@ -6,6 +6,8 @@ import { UserSpaceRepository, } from '@app/common/modules/user/repositories'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { SpaceService } from './space.service'; +import { UserSpaceParam } from '../dtos'; @Injectable() export class SpaceUserService { @@ -13,11 +15,10 @@ export class SpaceUserService { private readonly spaceRepository: SpaceRepository, private readonly userRepository: UserRepository, private readonly userSpaceRepository: UserSpaceRepository, + private readonly spaceService: SpaceService, ) {} - async associateUserToSpace( - userUuid: string, - spaceUuid: string, - ): Promise { + async associateUserToSpace(params: UserSpaceParam): Promise { + const { communityUuid, spaceUuid, userUuid, projectUuid } = params; // Find the user by ID const user = await this.userRepository.findOne({ where: { uuid: userUuid }, @@ -30,15 +31,11 @@ export class SpaceUserService { } // Find the space by ID - 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.spaceService.validateCommunityAndSpace( + communityUuid, + spaceUuid, + projectUuid, + ); // Check if the association already exists const existingAssociation = await this.userSpaceRepository.findOne({ @@ -61,9 +58,9 @@ export class SpaceUserService { } async disassociateUserFromSpace( - userUuid: string, - spaceUuid: string, + params: UserSpaceParam, ): Promise { + const { userUuid, spaceUuid, communityUuid, projectUuid } = params; // Find the user by ID const user = await this.userRepository.findOne({ where: { uuid: userUuid }, @@ -76,15 +73,11 @@ export class SpaceUserService { } // Find the space by ID - const space = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid }, - }); - if (!space) { - throw new HttpException( - `Space with ID ${spaceUuid} not found`, - HttpStatus.NOT_FOUND, - ); - } + await this.spaceService.validateCommunityAndSpace( + communityUuid, + spaceUuid, + projectUuid, + ); // Find the existing association const existingAssociation = await this.userSpaceRepository.findOne({ From dc9c67b6c04b0ff0c83304198080f35a3514356f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 17:47:26 +0400 Subject: [PATCH 018/247] updated root of endoint of space device from project --- libs/common/src/constants/controller-route.ts | 2 +- src/space/services/space-device.service.ts | 34 ++++--------------- 2 files changed, 7 insertions(+), 29 deletions(-) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index caf7639..13a0dd3 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -209,7 +209,7 @@ export class ControllerRoute { static SPACE_DEVICES = class { public static readonly ROUTE = - '/communities/:communityUuid/spaces/:spaceUuid/devices'; + '/projects/:projectUuid/communities/:communityUuid/spaces/:spaceUuid/devices'; static ACTIONS = class { public static readonly LIST_SPACE_DEVICE_SUMMARY = 'List devices in a space'; diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index 4dc24ba..0a262a1 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -8,6 +8,7 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { ProductRepository } from '@app/common/modules/product/repositories'; +import { SpaceService } from './space.service'; @Injectable() export class SpaceDeviceService { @@ -16,14 +17,16 @@ export class SpaceDeviceService { private readonly tuyaService: TuyaService, private readonly productRepository: ProductRepository, private readonly communityRepository: CommunityRepository, + private readonly spaceService: SpaceService, ) {} async listDevicesInSpace(params: GetSpaceParam): Promise { - const { spaceUuid, communityUuid } = params; + const { spaceUuid, communityUuid, projectUuid } = params; try { - const space = await this.validateCommunityAndSpace( + const space = await this.spaceService.validateCommunityAndSpace( communityUuid, spaceUuid, + projectUuid, ); const safeFetch = async (device: any) => { @@ -52,7 +55,7 @@ export class SpaceDeviceService { const detailedDevices = await Promise.all(space.devices.map(safeFetch)); return new SuccessResponseDto({ - data: detailedDevices.filter(Boolean), // Remove null or undefined values + data: detailedDevices.filter(Boolean), message: 'Successfully retrieved list of devices', }); } catch (error) { @@ -64,31 +67,6 @@ export class SpaceDeviceService { } } - async validateCommunityAndSpace(communityUuid: string, spaceUuid: string) { - const community = await this.communityRepository.findOne({ - where: { uuid: communityUuid }, - }); - if (!community) { - this.throwNotFound('Community', communityUuid); - } - - const space = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid, community: { uuid: communityUuid } }, - relations: ['devices', 'devices.productDevice'], - }); - if (!space) { - this.throwNotFound('Space', spaceUuid); - } - return space; - } - - private throwNotFound(entity: string, uuid: string) { - throw new HttpException( - `${entity} with ID ${uuid} not found`, - HttpStatus.NOT_FOUND, - ); - } - private async getDeviceDetailsByDeviceIdTuya( deviceId: string, ): Promise { From 30c2294224830349a99817ac6838d6c5a8c5e203 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 17:52:15 +0400 Subject: [PATCH 019/247] updated endpoint of subspaces --- libs/common/src/constants/controller-route.ts | 2 +- .../services/subspace/subspace.service.ts | 65 +++++++++---------- 2 files changed, 30 insertions(+), 37 deletions(-) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 13a0dd3..be342a9 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -220,7 +220,7 @@ export class ControllerRoute { static SUBSPACE = class { public static readonly ROUTE = - '/communities/:communityUuid/spaces/:spaceUuid/subspaces'; + '/projects/:projectUuid/communities/:communityUuid/spaces/:spaceUuid/subspaces'; static ACTIONS = class { public static readonly CREATE_SUBSPACE_SUMMARY = 'Create Subspace'; public static readonly CREATE_SUBSPACE_DESCRIPTION = diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 22c60ee..e99c928 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -13,6 +13,7 @@ import { } from '@app/common/models/typeOrmCustom.model'; import { PageResponse } from '@app/common/dto/pagination.response.dto'; import { SubspaceDto } from '@app/common/modules/space/dtos'; +import { SpaceService } from '../space.service'; @Injectable() export class SubSpaceService { @@ -20,16 +21,18 @@ export class SubSpaceService { private readonly spaceRepository: SpaceRepository, private readonly communityRepository: CommunityRepository, private readonly subspaceRepository: SubspaceRepository, + private readonly spaceService: SpaceService, ) {} async createSubspace( addSubspaceDto: AddSubspaceDto, params: GetSpaceParam, ): Promise { - const { communityUuid, spaceUuid } = params; - const space = await this.validateCommunityAndSpace( + const { communityUuid, spaceUuid, projectUuid } = params; + const space = await this.spaceService.validateCommunityAndSpace( communityUuid, spaceUuid, + projectUuid, ); try { @@ -54,8 +57,12 @@ export class SubSpaceService { params: GetSpaceParam, pageable: Partial, ): Promise { - const { communityUuid, spaceUuid } = params; - await this.validateCommunityAndSpace(communityUuid, spaceUuid); + const { communityUuid, spaceUuid, projectUuid } = params; + await this.spaceService.validateCommunityAndSpace( + communityUuid, + spaceUuid, + projectUuid, + ); try { pageable.modelName = 'subspace'; @@ -74,8 +81,12 @@ export class SubSpaceService { } async findOne(params: GetSubSpaceParam): Promise { - const { communityUuid, subSpaceUuid, spaceUuid } = params; - await this.validateCommunityAndSpace(communityUuid, spaceUuid); + const { communityUuid, subSpaceUuid, spaceUuid, projectUuid } = params; + await this.spaceService.validateCommunityAndSpace( + communityUuid, + spaceUuid, + projectUuid, + ); try { const subSpace = await this.subspaceRepository.findOne({ where: { @@ -110,8 +121,12 @@ export class SubSpaceService { params: GetSubSpaceParam, updateSubSpaceDto: AddSubspaceDto, ): Promise { - const { spaceUuid, communityUuid, subSpaceUuid } = params; - await this.validateCommunityAndSpace(communityUuid, spaceUuid); + const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params; + await this.spaceService.validateCommunityAndSpace( + communityUuid, + spaceUuid, + projectUuid, + ); const subSpace = await this.subspaceRepository.findOne({ where: { uuid: subSpaceUuid }, @@ -146,8 +161,12 @@ export class SubSpaceService { } async delete(params: GetSubSpaceParam): Promise { - const { spaceUuid, communityUuid, subSpaceUuid } = params; - await this.validateCommunityAndSpace(communityUuid, spaceUuid); + const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params; + await this.spaceService.validateCommunityAndSpace( + communityUuid, + spaceUuid, + projectUuid, + ); const subspace = await this.subspaceRepository.findOne({ where: { uuid: subSpaceUuid }, @@ -174,30 +193,4 @@ export class SubSpaceService { ); } } - - private async validateCommunityAndSpace( - communityUuid: string, - spaceUuid: string, - ) { - const community = await this.communityRepository.findOne({ - where: { uuid: communityUuid }, - }); - if (!community) { - throw new HttpException( - `Community with ID ${communityUuid} not found`, - HttpStatus.NOT_FOUND, - ); - } - - 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, - ); - } - return space; - } } From e61132561bd14c19c0ab9624cab887446720e951 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 17:56:18 +0400 Subject: [PATCH 020/247] changed endpoint for subspace device --- libs/common/src/constants/controller-route.ts | 2 +- .../subspace/subspace-device.service.ts | 48 +++++++++---------- .../services/subspace/subspace.service.ts | 2 - 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index be342a9..bd37317 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -246,7 +246,7 @@ export class ControllerRoute { static SUBSPACE_DEVICE = class { public static readonly ROUTE = - '/communities/:communityUuid/spaces/:spaceUuid/subspaces/:subSpaceUuid/devices'; + '/projects/:projectUuid/communities/:communityUuid/spaces/:spaceUuid/subspaces/:subSpaceUuid/devices'; static ACTIONS = class { public static readonly LIST_SUBSPACE_DEVICE_SUMMARY = diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index 74f6610..e773ea4 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -12,6 +12,7 @@ import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { ProductRepository } from '@app/common/modules/product/repositories'; import { GetDeviceDetailsInterface } from '../../../device/interfaces/get.device.interface'; +import { SpaceService } from '../space.service'; @Injectable() export class SubspaceDeviceService { @@ -22,14 +23,19 @@ export class SubspaceDeviceService { private readonly deviceRepository: DeviceRepository, private readonly tuyaService: TuyaService, private readonly productRepository: ProductRepository, + private readonly spaceService: SpaceService, ) {} async listDevicesInSubspace( params: GetSubSpaceParam, ): Promise { - const { subSpaceUuid, spaceUuid, communityUuid } = params; + const { subSpaceUuid, spaceUuid, communityUuid, projectUuid } = params; - await this.validateCommunityAndSpace(communityUuid, spaceUuid); + await this.spaceService.validateCommunityAndSpace( + communityUuid, + spaceUuid, + projectUuid, + ); const subspace = await this.findSubspaceWithDevices(subSpaceUuid); @@ -67,9 +73,14 @@ export class SubspaceDeviceService { async associateDeviceToSubspace( params: DeviceSubSpaceParam, ): Promise { - const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid } = params; + const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid, projectUuid } = + params; try { - await this.validateCommunityAndSpace(communityUuid, spaceUuid); + await this.spaceService.validateCommunityAndSpace( + communityUuid, + spaceUuid, + projectUuid, + ); const subspace = await this.findSubspace(subSpaceUuid); const device = await this.findDevice(deviceUuid); @@ -97,9 +108,14 @@ export class SubspaceDeviceService { async disassociateDeviceFromSubspace( params: DeviceSubSpaceParam, ): Promise { - const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid } = params; + const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid, projectUuid } = + params; try { - await this.validateCommunityAndSpace(communityUuid, spaceUuid); + await this.spaceService.validateCommunityAndSpace( + communityUuid, + spaceUuid, + projectUuid, + ); const subspace = await this.findSubspace(subSpaceUuid); const device = await this.findDevice(deviceUuid); @@ -129,26 +145,6 @@ export class SubspaceDeviceService { } } } - // Helper method to validate community and space - private async validateCommunityAndSpace( - communityUuid: string, - spaceUuid: string, - ) { - const community = await this.communityRepository.findOne({ - where: { uuid: communityUuid }, - }); - if (!community) { - this.throwNotFound('Community', communityUuid); - } - - const space = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid, community: { uuid: communityUuid } }, - }); - if (!space) { - this.throwNotFound('Space', spaceUuid); - } - return space; - } // Helper method to find subspace with devices relation private async findSubspaceWithDevices(subSpaceUuid: string) { diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index e99c928..34f23d7 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -18,8 +18,6 @@ import { SpaceService } from '../space.service'; @Injectable() export class SubSpaceService { constructor( - private readonly spaceRepository: SpaceRepository, - private readonly communityRepository: CommunityRepository, private readonly subspaceRepository: SubspaceRepository, private readonly spaceService: SpaceService, ) {} From 476e5cf9acfff740b8d5b22c745730f40d3a7444 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 9 Dec 2024 17:59:52 +0400 Subject: [PATCH 021/247] remove unused --- src/space/services/subspace/subspace.service.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 34f23d7..c343512 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -1,9 +1,5 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { CommunityRepository } from '@app/common/modules/community/repositories'; -import { - SpaceRepository, - SubspaceRepository, -} from '@app/common/modules/space/repositories'; +import { SubspaceRepository } from '@app/common/modules/space/repositories'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../../dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; From c0ef1546ab39d038ea7a8c00658f41103162ef5b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 10 Dec 2024 09:49:58 +0400 Subject: [PATCH 022/247] make it nullable for now --- libs/common/src/modules/community/entities/community.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/modules/community/entities/community.entity.ts b/libs/common/src/modules/community/entities/community.entity.ts index a94e586..8809f94 100644 --- a/libs/common/src/modules/community/entities/community.entity.ts +++ b/libs/common/src/modules/community/entities/community.entity.ts @@ -34,7 +34,7 @@ export class CommunityEntity extends AbstractEntity { externalId: string; @ManyToOne(() => ProjectEntity, (project) => project.communities, { - nullable: false, + nullable: true, }) project: ProjectEntity; } From 783b37cd1f9dd1747d964141324a58b42825349d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 10 Dec 2024 15:12:51 +0400 Subject: [PATCH 023/247] Added space model, subspace model, space product model entities --- libs/common/src/database/database.module.ts | 10 +++++ .../product/entities/product.entity.ts | 7 ++++ .../src/modules/space-model/dtos/index.ts | 4 ++ .../space-model/dtos/space-model.dto.ts | 15 +++++++ .../dtos/space-product-item.dto.ts | 15 +++++++ .../dtos/space-product-model.dto.ts | 23 +++++++++++ .../space-model/dtos/subspace-model.dto.ts | 15 +++++++ .../src/modules/space-model/entities/index.ts | 4 ++ .../entities/space-model.entity.ts | 38 ++++++++++++++++++ .../entities/space-product-item.entity.ts | 29 ++++++++++++++ .../entities/space-product-model.entity.ts | 40 +++++++++++++++++++ .../entities/subspace-model.entity.ts | 29 ++++++++++++++ libs/common/src/modules/space-model/index.ts | 3 ++ .../modules/space-model/repositories/index.ts | 1 + .../repositories/space-model.repository.ts | 34 ++++++++++++++++ .../space-model.repository.module.ts | 23 +++++++++++ 16 files changed, 290 insertions(+) create mode 100644 libs/common/src/modules/space-model/dtos/index.ts create mode 100644 libs/common/src/modules/space-model/dtos/space-model.dto.ts create mode 100644 libs/common/src/modules/space-model/dtos/space-product-item.dto.ts create mode 100644 libs/common/src/modules/space-model/dtos/space-product-model.dto.ts create mode 100644 libs/common/src/modules/space-model/dtos/subspace-model.dto.ts create mode 100644 libs/common/src/modules/space-model/entities/index.ts create mode 100644 libs/common/src/modules/space-model/entities/space-model.entity.ts create mode 100644 libs/common/src/modules/space-model/entities/space-product-item.entity.ts create mode 100644 libs/common/src/modules/space-model/entities/space-product-model.entity.ts create mode 100644 libs/common/src/modules/space-model/entities/subspace-model.entity.ts create mode 100644 libs/common/src/modules/space-model/index.ts create mode 100644 libs/common/src/modules/space-model/repositories/index.ts create mode 100644 libs/common/src/modules/space-model/repositories/space-model.repository.ts create mode 100644 libs/common/src/modules/space-model/space-model.repository.module.ts diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 34e4bb6..44fd854 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -28,6 +28,12 @@ import { SceneEntity, SceneIconEntity } from '../modules/scene/entities'; import { SceneDeviceEntity } from '../modules/scene-device/entities'; import { SpaceProductEntity } from '../modules/space/entities/space-product.entity'; import { ProjectEntity } from '../modules/project/entities'; +import { + SpaceModelEntity, + SpaceProductItemModelEntity, + SpaceProductModelEntity, + SubspaceModelEntity, +} from '../modules/space-model/entities'; @Module({ imports: [ TypeOrmModule.forRootAsync({ @@ -68,6 +74,10 @@ import { ProjectEntity } from '../modules/project/entities'; SceneEntity, SceneIconEntity, SceneDeviceEntity, + SpaceModelEntity, + SpaceProductModelEntity, + SpaceProductItemModelEntity, + SubspaceModelEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), diff --git a/libs/common/src/modules/product/entities/product.entity.ts b/libs/common/src/modules/product/entities/product.entity.ts index 2c731a0..7b25470 100644 --- a/libs/common/src/modules/product/entities/product.entity.ts +++ b/libs/common/src/modules/product/entities/product.entity.ts @@ -3,6 +3,7 @@ import { ProductDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceEntity } from '../../device/entities'; import { SpaceProductEntity } from '../../space/entities/space-product.entity'; +import { SpaceProductModelEntity } from '../../space-model/entities'; @Entity({ name: 'product' }) export class ProductEntity extends AbstractEntity { @@ -30,6 +31,12 @@ export class ProductEntity extends AbstractEntity { @OneToMany(() => SpaceProductEntity, (spaceProduct) => spaceProduct.product) spaceProducts: SpaceProductEntity[]; + @OneToMany( + () => SpaceProductModelEntity, + (spaceProductModel) => spaceProductModel.product, + ) + spaceProductModels: SpaceProductModelEntity[]; + @OneToMany( () => DeviceEntity, (devicesProductEntity) => devicesProductEntity.productDevice, diff --git a/libs/common/src/modules/space-model/dtos/index.ts b/libs/common/src/modules/space-model/dtos/index.ts new file mode 100644 index 0000000..f775b4a --- /dev/null +++ b/libs/common/src/modules/space-model/dtos/index.ts @@ -0,0 +1,4 @@ +export * from './subspace-model.dto'; +export * from './space-model.dto'; +export * from './space-product-item.dto'; +export * from './space-product-model.dto'; diff --git a/libs/common/src/modules/space-model/dtos/space-model.dto.ts b/libs/common/src/modules/space-model/dtos/space-model.dto.ts new file mode 100644 index 0000000..f4d7cbf --- /dev/null +++ b/libs/common/src/modules/space-model/dtos/space-model.dto.ts @@ -0,0 +1,15 @@ +import { IsString, IsNotEmpty } from 'class-validator'; + +export class SpaceModelDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public spaceModelName: string; + + @IsString() + @IsNotEmpty() + projectUuid: string; +} diff --git a/libs/common/src/modules/space-model/dtos/space-product-item.dto.ts b/libs/common/src/modules/space-model/dtos/space-product-item.dto.ts new file mode 100644 index 0000000..fa68825 --- /dev/null +++ b/libs/common/src/modules/space-model/dtos/space-product-item.dto.ts @@ -0,0 +1,15 @@ +import { IsString, IsNotEmpty } from 'class-validator'; + +export class SpaceProductItemDto { + @IsString() + @IsNotEmpty() + uuid: string; + + @IsString() + @IsNotEmpty() + tag: string; + + @IsString() + @IsNotEmpty() + spaceProductModelUuid: string; +} diff --git a/libs/common/src/modules/space-model/dtos/space-product-model.dto.ts b/libs/common/src/modules/space-model/dtos/space-product-model.dto.ts new file mode 100644 index 0000000..eae8088 --- /dev/null +++ b/libs/common/src/modules/space-model/dtos/space-product-model.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; +import { SpaceProductItemDto } from './space-product-item.dto'; + +export class SpaceProductModelDto { + @IsString() + @IsNotEmpty() + uuid: string; + + @IsNumber() + @IsNotEmpty() + productCount: number; + + @IsString() + @IsNotEmpty() + productUuid: string; + + @ApiProperty({ + description: 'List of individual items with specific names for the product', + type: [SpaceProductItemDto], + }) + items: SpaceProductItemDto[]; +} diff --git a/libs/common/src/modules/space-model/dtos/subspace-model.dto.ts b/libs/common/src/modules/space-model/dtos/subspace-model.dto.ts new file mode 100644 index 0000000..8c9a64c --- /dev/null +++ b/libs/common/src/modules/space-model/dtos/subspace-model.dto.ts @@ -0,0 +1,15 @@ +import { IsString, IsNotEmpty } from 'class-validator'; + +export class SubSpaceModelDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public subSpaceModelName: string; + + @IsString() + @IsNotEmpty() + spaceModelUuid: string; +} diff --git a/libs/common/src/modules/space-model/entities/index.ts b/libs/common/src/modules/space-model/entities/index.ts new file mode 100644 index 0000000..3bf3f69 --- /dev/null +++ b/libs/common/src/modules/space-model/entities/index.ts @@ -0,0 +1,4 @@ +export * from './space-model.entity'; +export * from './space-product-item.entity'; +export * from './space-product-model.entity'; +export * from './subspace-model.entity'; \ No newline at end of file diff --git a/libs/common/src/modules/space-model/entities/space-model.entity.ts b/libs/common/src/modules/space-model/entities/space-model.entity.ts new file mode 100644 index 0000000..8df7e45 --- /dev/null +++ b/libs/common/src/modules/space-model/entities/space-model.entity.ts @@ -0,0 +1,38 @@ +import { Entity, Column, OneToMany } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { SpaceModelDto } from '../dtos'; +import { SubspaceModelEntity } from './subspace-model.entity'; +import { SpaceProductModelEntity } from './space-product-model.entity'; + +@Entity({ name: 'space-model' }) +export class SpaceModelEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', + nullable: false, + }) + public uuid: string; + + @Column({ + nullable: false, + }) + public modelName: string; + + @OneToMany( + () => SubspaceModelEntity, + (subspaceModel) => subspaceModel.spaceModel, + { + cascade: true, + }, + ) + public subspaceModels: SubspaceModelEntity[]; + + @OneToMany( + () => SpaceProductModelEntity, + (productModel) => productModel.spaceModel, + { + cascade: true, + }, + ) + public spaceProductModels: SpaceProductModelEntity[]; +} diff --git a/libs/common/src/modules/space-model/entities/space-product-item.entity.ts b/libs/common/src/modules/space-model/entities/space-product-item.entity.ts new file mode 100644 index 0000000..f754769 --- /dev/null +++ b/libs/common/src/modules/space-model/entities/space-product-item.entity.ts @@ -0,0 +1,29 @@ +import { Entity, Column, ManyToOne } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { SpaceEntity } from '../../space/entities'; +import { SpaceProductItemDto } from '../dtos'; +import { SpaceProductModelEntity } from './space-product-model.entity'; + +@Entity({ name: 'space-product-item' }) +export class SpaceProductItemModelEntity extends AbstractEntity { + @Column({ + nullable: false, + }) + public itemName: string; + + @ManyToOne( + () => SpaceProductModelEntity, + (spaceProductModel) => spaceProductModel.items, + { + nullable: false, + onDelete: 'CASCADE', + }, + ) + public spaceProductModel: SpaceProductModelEntity; + + @ManyToOne(() => SpaceEntity, (space) => space.spaceProducts, { + nullable: true, + onDelete: 'CASCADE', + }) + public space: SpaceEntity; // Optional for associating the item to a space directly +} diff --git a/libs/common/src/modules/space-model/entities/space-product-model.entity.ts b/libs/common/src/modules/space-model/entities/space-product-model.entity.ts new file mode 100644 index 0000000..d843d07 --- /dev/null +++ b/libs/common/src/modules/space-model/entities/space-product-model.entity.ts @@ -0,0 +1,40 @@ +import { Entity, Column, ManyToOne, OneToMany } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { ProductEntity } from '../../product/entities'; +import { SpaceModelEntity } from './space-model.entity'; +import { SpaceProductItemModelEntity } from './space-product-item.entity'; +import { SpaceProductModelDto } from '../dtos'; + +@Entity({ name: 'space-product-model' }) +export class SpaceProductModelEntity extends AbstractEntity { + @Column({ + nullable: false, + type: 'int', + }) + productCount: number; + + @ManyToOne( + () => SpaceModelEntity, + (spaceModel) => spaceModel.spaceProductModels, + { + nullable: false, + onDelete: 'CASCADE', + }, + ) + public spaceModel: SpaceModelEntity; + + @ManyToOne(() => ProductEntity, (product) => product.spaceProductModels, { + nullable: false, + onDelete: 'CASCADE', + }) + public product: ProductEntity; + + @OneToMany( + () => SpaceProductItemModelEntity, + (item) => item.spaceProductModel, + { + cascade: true, + }, + ) + public items: SpaceProductItemModelEntity[]; +} diff --git a/libs/common/src/modules/space-model/entities/subspace-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model.entity.ts new file mode 100644 index 0000000..abb1c64 --- /dev/null +++ b/libs/common/src/modules/space-model/entities/subspace-model.entity.ts @@ -0,0 +1,29 @@ +import { Column, Entity, ManyToOne } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { SpaceModelEntity } from './space-model.entity'; +import { SubSpaceModelDto } from '../dtos'; + +@Entity({ name: 'subspace-model' }) +export class SubspaceModelEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', + nullable: false, + }) + public uuid: string; + + @Column({ + nullable: false, + }) + public subspaceName: string; + + @ManyToOne( + () => SpaceModelEntity, + (spaceModel) => spaceModel.subspaceModels, + { + nullable: false, + onDelete: 'CASCADE', + }, + ) + public spaceModel: SpaceModelEntity; +} diff --git a/libs/common/src/modules/space-model/index.ts b/libs/common/src/modules/space-model/index.ts new file mode 100644 index 0000000..9d32775 --- /dev/null +++ b/libs/common/src/modules/space-model/index.ts @@ -0,0 +1,3 @@ +export * from './space-model.repository.module'; +export * from './entities'; +export * from './repositories'; diff --git a/libs/common/src/modules/space-model/repositories/index.ts b/libs/common/src/modules/space-model/repositories/index.ts new file mode 100644 index 0000000..d8fcff4 --- /dev/null +++ b/libs/common/src/modules/space-model/repositories/index.ts @@ -0,0 +1 @@ +export * from './space-model.repository'; diff --git a/libs/common/src/modules/space-model/repositories/space-model.repository.ts b/libs/common/src/modules/space-model/repositories/space-model.repository.ts new file mode 100644 index 0000000..0e37479 --- /dev/null +++ b/libs/common/src/modules/space-model/repositories/space-model.repository.ts @@ -0,0 +1,34 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { + SpaceModelEntity, + SpaceProductItemModelEntity, + SpaceProductModelEntity, + SubspaceModelEntity, +} from '../entities'; + +@Injectable() +export class SpaceModelRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SpaceModelEntity, dataSource.createEntityManager()); + } +} +@Injectable() +export class SubspaceModelRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SubspaceModelEntity, dataSource.createEntityManager()); + } +} + +@Injectable() +export class SpaceProductModelRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SpaceProductModelEntity, dataSource.createEntityManager()); + } +} +@Injectable() +export class SpaceProductItemModelRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SpaceProductItemModelEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/space-model/space-model.repository.module.ts b/libs/common/src/modules/space-model/space-model.repository.module.ts new file mode 100644 index 0000000..573bba0 --- /dev/null +++ b/libs/common/src/modules/space-model/space-model.repository.module.ts @@ -0,0 +1,23 @@ +import { TypeOrmModule } from '@nestjs/typeorm'; +import { + SpaceModelEntity, + SpaceProductItemModelEntity, + SpaceProductModelEntity, + SubspaceModelEntity, +} from './entities'; +import { Module } from '@nestjs/common'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [ + TypeOrmModule.forFeature([ + SpaceModelEntity, + SubspaceModelEntity, + SpaceProductModelEntity, + SpaceProductItemModelEntity, + ]), + ], +}) +export class SpaceModelRepositoryModule {} From 1ed7f4f2efab3fdffa2d9a9637dc4b80ab5b229c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 10 Dec 2024 15:16:35 +0400 Subject: [PATCH 024/247] changed name to tag --- .../modules/space-model/entities/space-product-item.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/modules/space-model/entities/space-product-item.entity.ts b/libs/common/src/modules/space-model/entities/space-product-item.entity.ts index f754769..8c0b9c7 100644 --- a/libs/common/src/modules/space-model/entities/space-product-item.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-product-item.entity.ts @@ -9,7 +9,7 @@ export class SpaceProductItemModelEntity extends AbstractEntity SpaceProductModelEntity, From 3ee3ff1fc3894346f43969813027f62911f797a2 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 10 Dec 2024 15:28:29 +0400 Subject: [PATCH 025/247] space model can have nullable space product model and subspace model --- .../src/modules/space-model/entities/space-model.entity.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/common/src/modules/space-model/entities/space-model.entity.ts b/libs/common/src/modules/space-model/entities/space-model.entity.ts index 8df7e45..517f273 100644 --- a/libs/common/src/modules/space-model/entities/space-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-model.entity.ts @@ -23,6 +23,7 @@ export class SpaceModelEntity extends AbstractEntity { (subspaceModel) => subspaceModel.spaceModel, { cascade: true, + nullable: true, }, ) public subspaceModels: SubspaceModelEntity[]; @@ -32,6 +33,7 @@ export class SpaceModelEntity extends AbstractEntity { (productModel) => productModel.spaceModel, { cascade: true, + nullable: true, }, ) public spaceProductModels: SpaceProductModelEntity[]; From dc00fdc554f03253303a1ed1e2221dafc12eaced Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 10 Dec 2024 17:33:50 +0400 Subject: [PATCH 026/247] added project space model relation --- .../modules/project/entities/project.entity.ts | 6 +++++- .../space-model/entities/space-model.entity.ts | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/libs/common/src/modules/project/entities/project.entity.ts b/libs/common/src/modules/project/entities/project.entity.ts index 04e3f11..f7213a2 100644 --- a/libs/common/src/modules/project/entities/project.entity.ts +++ b/libs/common/src/modules/project/entities/project.entity.ts @@ -2,6 +2,7 @@ import { Entity, Column, Unique, OneToMany } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { ProjectDto } from '../dtos'; import { CommunityEntity } from '../../community/entities'; +import { SpaceModelEntity } from '../../space-model'; @Entity({ name: 'project' }) @Unique(['name']) @@ -20,7 +21,10 @@ export class ProjectEntity extends AbstractEntity { @Column({ length: 255, nullable: true }) description: string; - + + @OneToMany(() => SpaceModelEntity, (spaceModel) => spaceModel.project) + public spaceModels: SpaceModelEntity[]; + @OneToMany(() => CommunityEntity, (community) => community.project) communities: CommunityEntity[]; diff --git a/libs/common/src/modules/space-model/entities/space-model.entity.ts b/libs/common/src/modules/space-model/entities/space-model.entity.ts index 517f273..9937a01 100644 --- a/libs/common/src/modules/space-model/entities/space-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-model.entity.ts @@ -1,10 +1,19 @@ -import { Entity, Column, OneToMany } from 'typeorm'; +import { + Entity, + Column, + OneToMany, + ManyToOne, + JoinColumn, + Unique, +} from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceModelDto } from '../dtos'; import { SubspaceModelEntity } from './subspace-model.entity'; import { SpaceProductModelEntity } from './space-product-model.entity'; +import { ProjectEntity } from '../../project/entities'; @Entity({ name: 'space-model' }) +@Unique(['modelName', 'project']) export class SpaceModelEntity extends AbstractEntity { @Column({ type: 'uuid', @@ -18,6 +27,13 @@ export class SpaceModelEntity extends AbstractEntity { }) public modelName: string; + @ManyToOne(() => ProjectEntity, (project) => project.spaceModels, { + nullable: false, + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'project_uuid' }) + public project: ProjectEntity; + @OneToMany( () => SubspaceModelEntity, (subspaceModel) => subspaceModel.spaceModel, From b945740fb8ae102a20bc22e594b5268b0efdbbf4 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 11 Dec 2024 10:37:21 +0400 Subject: [PATCH 027/247] ensure in uniqueness in tag --- .../entities/space-product-item.entity.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/libs/common/src/modules/space-model/entities/space-product-item.entity.ts b/libs/common/src/modules/space-model/entities/space-product-item.entity.ts index 8c0b9c7..d830fba 100644 --- a/libs/common/src/modules/space-model/entities/space-product-item.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-product-item.entity.ts @@ -1,10 +1,11 @@ -import { Entity, Column, ManyToOne } from 'typeorm'; +import { Entity, Column, ManyToOne, Unique } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { SpaceEntity } from '../../space/entities'; import { SpaceProductItemDto } from '../dtos'; import { SpaceProductModelEntity } from './space-product-model.entity'; +import { SpaceModelEntity } from './space-model.entity'; @Entity({ name: 'space-product-item' }) +@Unique(['tag', 'spaceProductModel', 'spaceModel']) export class SpaceProductItemModelEntity extends AbstractEntity { @Column({ nullable: false, @@ -16,14 +17,16 @@ export class SpaceProductItemModelEntity extends AbstractEntity spaceProductModel.items, { nullable: false, - onDelete: 'CASCADE', }, ) public spaceProductModel: SpaceProductModelEntity; - @ManyToOne(() => SpaceEntity, (space) => space.spaceProducts, { - nullable: true, - onDelete: 'CASCADE', - }) - public space: SpaceEntity; // Optional for associating the item to a space directly + @ManyToOne( + () => SpaceModelEntity, + (spaceModel) => spaceModel.spaceProductModels, + { + nullable: false, + }, + ) + public spaceModel: SpaceModelEntity; } From b2a422f71b7e27ad357713498ab0b5d2a1e79716 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 11 Dec 2024 10:51:17 +0400 Subject: [PATCH 028/247] created space model create service --- .../dtos/create-space-model.dto.ts | 33 +++++++ .../create-space-product-item-model.dto.ts | 12 +++ .../dtos/create-space-product-model.dto.ts | 30 ++++++ .../dtos/create-subspace-model.dto.ts | 12 +++ src/space-model/dtos/index.ts | 5 + src/space-model/dtos/project-param.dto.ts | 11 +++ src/space-model/services/index.ts | 4 + .../services/space-model.service.ts | 94 +++++++++++++++++++ .../space-product-item-model.service.ts | 79 ++++++++++++++++ .../services/space-product-model.service.ts | 61 ++++++++++++ .../services/subspace-model.service.ts | 33 +++++++ 11 files changed, 374 insertions(+) create mode 100644 src/space-model/dtos/create-space-model.dto.ts create mode 100644 src/space-model/dtos/create-space-product-item-model.dto.ts create mode 100644 src/space-model/dtos/create-space-product-model.dto.ts create mode 100644 src/space-model/dtos/create-subspace-model.dto.ts create mode 100644 src/space-model/dtos/index.ts create mode 100644 src/space-model/dtos/project-param.dto.ts create mode 100644 src/space-model/services/index.ts create mode 100644 src/space-model/services/space-model.service.ts create mode 100644 src/space-model/services/space-product-item-model.service.ts create mode 100644 src/space-model/services/space-product-model.service.ts create mode 100644 src/space-model/services/subspace-model.service.ts diff --git a/src/space-model/dtos/create-space-model.dto.ts b/src/space-model/dtos/create-space-model.dto.ts new file mode 100644 index 0000000..9506728 --- /dev/null +++ b/src/space-model/dtos/create-space-model.dto.ts @@ -0,0 +1,33 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString, IsArray, ValidateNested } from 'class-validator'; +import { Type } from 'class-transformer'; +import { CreateSubspaceModelDto } from './create-subspace-model.dto'; +import { CreateSpaceProductModelDto } from './create-space-product-model.dto'; + +export class CreateSpaceModelDto { + @ApiProperty({ + description: 'Name of the space model', + example: 'Apartment Model', + }) + @IsNotEmpty() + @IsString() + modelName: string; + + @ApiProperty({ + description: 'List of subspaces included in the model', + type: [CreateSubspaceModelDto], + }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CreateSubspaceModelDto) + subspaceModels?: CreateSubspaceModelDto[]; + + @ApiProperty({ + description: 'List of products included in the model', + type: [CreateSpaceProductModelDto], + }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CreateSpaceProductModelDto) + spaceProductModels?: CreateSpaceProductModelDto[]; +} diff --git a/src/space-model/dtos/create-space-product-item-model.dto.ts b/src/space-model/dtos/create-space-product-item-model.dto.ts new file mode 100644 index 0000000..3825219 --- /dev/null +++ b/src/space-model/dtos/create-space-product-item-model.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class CreateSpaceProductItemModelDto { + @ApiProperty({ + description: 'Specific name for the product item', + example: 'Light 1', + }) + @IsNotEmpty() + @IsString() + tag: string; +} diff --git a/src/space-model/dtos/create-space-product-model.dto.ts b/src/space-model/dtos/create-space-product-model.dto.ts new file mode 100644 index 0000000..63fb9dd --- /dev/null +++ b/src/space-model/dtos/create-space-product-model.dto.ts @@ -0,0 +1,30 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString, IsArray, ValidateNested } from 'class-validator'; +import { Type } from 'class-transformer'; +import { CreateSpaceProductItemModelDto } from './create-space-product-item-model.dto'; + +export class CreateSpaceProductModelDto { + @ApiProperty({ + description: 'ID of the product associated with the model', + example: 'product-uuid', + }) + @IsNotEmpty() + @IsString() + productId: string; + + @ApiProperty({ + description: 'Number of products in the model', + example: 3, + }) + @IsNotEmpty() + productCount: number; + + @ApiProperty({ + description: 'Specific names for each product item', + type: [CreateSpaceProductItemModelDto], + }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CreateSpaceProductItemModelDto) + items: CreateSpaceProductItemModelDto[]; +} diff --git a/src/space-model/dtos/create-subspace-model.dto.ts b/src/space-model/dtos/create-subspace-model.dto.ts new file mode 100644 index 0000000..a27ad3b --- /dev/null +++ b/src/space-model/dtos/create-subspace-model.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class CreateSubspaceModelDto { + @ApiProperty({ + description: 'Name of the subspace', + example: 'Living Room', + }) + @IsNotEmpty() + @IsString() + subspaceName: string; +} diff --git a/src/space-model/dtos/index.ts b/src/space-model/dtos/index.ts new file mode 100644 index 0000000..b4b1e07 --- /dev/null +++ b/src/space-model/dtos/index.ts @@ -0,0 +1,5 @@ +export * from './create-space-model.dto'; +export * from './create-space-product-item-model.dto'; +export * from './create-space-product-model.dto'; +export * from './create-subspace-model.dto'; +export * from './project-param.dto'; diff --git a/src/space-model/dtos/project-param.dto.ts b/src/space-model/dtos/project-param.dto.ts new file mode 100644 index 0000000..e7d9e97 --- /dev/null +++ b/src/space-model/dtos/project-param.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; + +export class projectParam { + @ApiProperty({ + description: 'UUID of the Project', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + projectUuid: string; +} diff --git a/src/space-model/services/index.ts b/src/space-model/services/index.ts new file mode 100644 index 0000000..88e2d41 --- /dev/null +++ b/src/space-model/services/index.ts @@ -0,0 +1,4 @@ +export * from './space-model.service'; +export * from './space-product-item-model.service'; +export * from './space-product-model.service'; +export * from './subspace-model.service'; diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts new file mode 100644 index 0000000..f7b8f28 --- /dev/null +++ b/src/space-model/services/space-model.service.ts @@ -0,0 +1,94 @@ +import { SpaceModelRepository } from '@app/common/modules/space-model'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { CreateSpaceModelDto } from '../dtos'; +import { ProjectRepository } from '@app/common/modules/project/repositiories'; +import { ProjectParam } from 'src/community/dtos'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { SubSpaceModelService } from './subspace-model.service'; +import { SpaceProductModelService } from './space-product-model.service'; + +@Injectable() +export class SpaceModelService { + constructor( + private readonly spaceModelRepository: SpaceModelRepository, + private readonly projectRepository: ProjectRepository, + private readonly subSpaceModelService: SubSpaceModelService, + private readonly spaceProductModelService: SpaceProductModelService, + ) {} + + async createSpaceModel( + createSpaceModelDto: CreateSpaceModelDto, + params: ProjectParam, + ) { + const { modelName, subspaceModels, spaceProductModels } = + createSpaceModelDto; + const project = await this.validateProject(params.projectUuid); + + try { + const isModelExist = await this.validateName( + modelName, + params.projectUuid, + ); + if (isModelExist) { + throw new HttpException( + `Model name "${modelName}" already exists in this project ${project.name}.`, + HttpStatus.CONFLICT, + ); + } + + const spaceModel = await this.spaceModelRepository.create({ + modelName, + project, + }); + const savedSpaceModel = await this.spaceModelRepository.save(spaceModel); + + await this.subSpaceModelService.createSubSpaceModels( + subspaceModels, + savedSpaceModel, + ); + + await this.spaceProductModelService.createSpaceProductModels( + spaceProductModels, + savedSpaceModel, + ); + + return new SuccessResponseDto({ + message: `Successfully created new space model with uuid ${savedSpaceModel.uuid}`, + data: savedSpaceModel, + statusCode: HttpStatus.CREATED, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + error.message || `An unexpected error occurred`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async validateProject(projectUuid: string) { + const project = await this.projectRepository.findOne({ + where: { + uuid: projectUuid, + }, + }); + + if (!project) { + throw new HttpException( + `Project with uuid ${projectUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + return project; + } + + async validateName(modelName: string, projectUuid: string): Promise { + const isModelExist = await this.spaceModelRepository.exists({ + where: { modelName, project: { uuid: projectUuid } }, + }); + return isModelExist; + } +} diff --git a/src/space-model/services/space-product-item-model.service.ts b/src/space-model/services/space-product-item-model.service.ts new file mode 100644 index 0000000..ebf7d2d --- /dev/null +++ b/src/space-model/services/space-product-item-model.service.ts @@ -0,0 +1,79 @@ +import { + SpaceModelEntity, + SpaceProductItemModelRepository, + SpaceProductModelEntity, +} from '@app/common/modules/space-model'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { CreateSpaceProductItemModelDto } from '../dtos'; + +@Injectable() +export class SpaceProductItemModelService { + constructor( + private readonly spaceProductItemRepository: SpaceProductItemModelRepository, + ) {} + + async createProdutItemModel( + itemModelDtos: CreateSpaceProductItemModelDto[], + spaceProductModel: SpaceProductModelEntity, + spaceModel: SpaceModelEntity, + ) { + try { + await this.validateTags(itemModelDtos, spaceModel); + + for (const itemModelDto of itemModelDtos) { + await this.create(itemModelDto, spaceProductModel, spaceModel); + } + } catch (error) { + throw new HttpException( + error.message || `An unexpected error occurred`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async create( + itemModelDto: CreateSpaceProductItemModelDto, + spaceProductModel: SpaceProductModelEntity, + spaceModel: SpaceModelEntity, + ) { + const productItem = this.spaceProductItemRepository.create({ + tag: itemModelDto.tag, + spaceProductModel, + spaceModel, + }); + await this.spaceProductItemRepository.save(productItem); + } + + async validateTags( + itemModelDtos: CreateSpaceProductItemModelDto[], + spaceModel: SpaceModelEntity, + ) { + const incomingTags = itemModelDtos.map((item) => item.tag); + + const duplicateTags = incomingTags.filter( + (tag, index) => incomingTags.indexOf(tag) !== index, + ); + if (duplicateTags.length > 0) { + throw new HttpException( + `Duplicate tags found in request: ${duplicateTags.join(', ')}`, + HttpStatus.CONFLICT, + ); + } + + const existingTags = await this.spaceProductItemRepository.find({ + where: { spaceModel }, + select: ['tag'], + }); + const existingTagSet = new Set(existingTags.map((item) => item.tag)); + + const conflictingTags = incomingTags.filter((tag) => + existingTagSet.has(tag), + ); + if (conflictingTags.length > 0) { + throw new HttpException( + `Tags already exist in the model: ${conflictingTags.join(', ')}`, + HttpStatus.CONFLICT, + ); + } + } +} diff --git a/src/space-model/services/space-product-model.service.ts b/src/space-model/services/space-product-model.service.ts new file mode 100644 index 0000000..10ee9f5 --- /dev/null +++ b/src/space-model/services/space-product-model.service.ts @@ -0,0 +1,61 @@ +import { + SpaceModelEntity, + SpaceProductModelRepository, +} from '@app/common/modules/space-model'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { CreateSpaceProductModelDto } from '../dtos'; +import { ProductRepository } from '@app/common/modules/product/repositories'; +import { SpaceProductItemModelService } from './space-product-item-model.service'; + +@Injectable() +export class SpaceProductModelService { + constructor( + private readonly spaceProductModelRepository: SpaceProductModelRepository, + private readonly productRepository: ProductRepository, + private readonly spaceProductItemModelService: SpaceProductItemModelService, + ) {} + + async createSpaceProductModels( + spaceProductModelDtos: CreateSpaceProductModelDto[], + spaceModel: SpaceModelEntity, + ) { + try { + for (const spaceProductModelDto of spaceProductModelDtos) { + await this.create(spaceProductModelDto, spaceModel); + } + } catch (error) { + throw new HttpException( + error.message || `An unexpected error occurred`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async create( + spaceProductModelDto: CreateSpaceProductModelDto, + spaceModel: SpaceModelEntity, + ) { + const product = await this.productRepository.findOneBy({ + uuid: spaceProductModelDto.productId, + }); + if (!product) { + throw new HttpException( + `Product with ID ${spaceProductModelDto.productId} not found`, + HttpStatus.NOT_FOUND, + ); + } + const spaceProductModel = this.spaceProductModelRepository.create({ + product, + productCount: spaceProductModelDto.productCount, + spaceModel: spaceModel, + }); + const newProductModel = + await this.spaceProductModelRepository.save(spaceProductModel); + + await this.spaceProductItemModelService.createProdutItemModel( + spaceProductModelDto.items, + newProductModel, + spaceModel, + ); + } +} diff --git a/src/space-model/services/subspace-model.service.ts b/src/space-model/services/subspace-model.service.ts new file mode 100644 index 0000000..45d33ef --- /dev/null +++ b/src/space-model/services/subspace-model.service.ts @@ -0,0 +1,33 @@ +import { + SpaceModelEntity, + SubspaceModelRepository, +} from '@app/common/modules/space-model'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { CreateSubspaceModelDto } from '../dtos'; + +@Injectable() +export class SubSpaceModelService { + constructor( + private readonly subspaceModelRepository: SubspaceModelRepository, + ) {} + + async createSubSpaceModels( + subSpaceModelDtos: CreateSubspaceModelDto[], + spaceModel: SpaceModelEntity, + ) { + try { + for (const subspaceDto of subSpaceModelDtos) { + const subspace = this.subspaceModelRepository.create({ + subspaceName: subspaceDto.subspaceName, + spaceModel: spaceModel, + }); + await this.subspaceModelRepository.save(subspace); + } + } catch (error) { + throw new HttpException( + error.message || `An unexpected error occurred`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} From 02faca33975d783ffa260215f53427d485b3c5c5 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 11 Dec 2024 11:06:41 +0400 Subject: [PATCH 029/247] added space model controller for create --- libs/common/src/constants/controller-route.ts | 10 +++++ src/app.module.ts | 3 +- src/space-model/controllers/index.ts | 1 + .../controllers/space-model.controller.ts | 35 ++++++++++++++++++ src/space-model/index.ts | 1 + src/space-model/space-model.module.ts | 37 +++++++++++++++++++ src/space/services/space.service.ts | 7 +++- 7 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 src/space-model/controllers/index.ts create mode 100644 src/space-model/controllers/space-model.controller.ts create mode 100644 src/space-model/index.ts create mode 100644 src/space-model/space-model.module.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index bd37317..10e20d9 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -266,6 +266,16 @@ export class ControllerRoute { }; }; + static SPACE_MODEL = class { + public static readonly ROUTE = '/projects/:projectUuid/space-models'; + static ACTIONS = class { + public static readonly CREATE_SPACE_MODEL_SUMMARY = + 'Create a New Space Model'; + public static readonly CREATE_SPACE_MODEL_DESCRIPTION = + 'This endpoint allows you to create a new space model within a specified project. A space model defines the structure of spaces, including subspaces, products, and product items, and is uniquely identifiable within the project.'; + }; + }; + static PRODUCT = class { public static readonly ROUTE = 'products'; static ACTIONS = class { diff --git a/src/app.module.ts b/src/app.module.ts index bb5986b..ac06b84 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -23,6 +23,7 @@ import { ScheduleModule } from './schedule/schedule.module'; import { SpaceModule } from './space/space.module'; import { ProductModule } from './product'; import { ProjectModule } from './project'; +import { SpaceModelModule } from './space-model'; @Module({ imports: [ ConfigModule.forRoot({ @@ -34,7 +35,7 @@ import { ProjectModule } from './project'; CommunityModule, SpaceModule, - + SpaceModelModule, GroupModule, DeviceModule, DeviceMessagesSubscriptionModule, diff --git a/src/space-model/controllers/index.ts b/src/space-model/controllers/index.ts new file mode 100644 index 0000000..c12699e --- /dev/null +++ b/src/space-model/controllers/index.ts @@ -0,0 +1 @@ +export * from './space-model.controller'; diff --git a/src/space-model/controllers/space-model.controller.ts b/src/space-model/controllers/space-model.controller.ts new file mode 100644 index 0000000..8475b6c --- /dev/null +++ b/src/space-model/controllers/space-model.controller.ts @@ -0,0 +1,35 @@ +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { Body, Controller, Param, Post, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { SpaceModelService } from '../services'; +import { CreateSpaceModelDto } from '../dtos'; +import { ProjectParam } from 'src/community/dtos'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; + +@ApiTags('Space Model Module') +@Controller({ + version: '1', + path: ControllerRoute.SPACE_MODEL.ROUTE, +}) +export class SpaceModelController { + constructor(private readonly spaceModelService: SpaceModelService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.SPACE_MODEL.ACTIONS.CREATE_SPACE_MODEL_SUMMARY, + description: + ControllerRoute.SPACE_MODEL.ACTIONS.CREATE_SPACE_MODEL_DESCRIPTION, + }) + @Post() + async createSpaceModel( + @Body() createSpaceModelDto: CreateSpaceModelDto, + @Param() projectParam: ProjectParam, + ): Promise { + return await this.spaceModelService.createSpaceModel( + createSpaceModelDto, + projectParam, + ); + } +} diff --git a/src/space-model/index.ts b/src/space-model/index.ts new file mode 100644 index 0000000..8885fc3 --- /dev/null +++ b/src/space-model/index.ts @@ -0,0 +1 @@ +export * from './space-model.module'; diff --git a/src/space-model/space-model.module.ts b/src/space-model/space-model.module.ts new file mode 100644 index 0000000..bbc6245 --- /dev/null +++ b/src/space-model/space-model.module.ts @@ -0,0 +1,37 @@ +import { SpaceRepositoryModule } from '@app/common/modules/space/space.repository.module'; +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { SpaceModelController } from './controllers'; +import { + SpaceModelService, + SpaceProductItemModelService, + SpaceProductModelService, + SubSpaceModelService, +} from './services'; +import { + SpaceModelRepository, + SpaceProductItemModelRepository, + SpaceProductModelRepository, + SubspaceModelRepository, +} from '@app/common/modules/space-model'; +import { ProjectRepository } from '@app/common/modules/project/repositiories'; +import { ProductRepository } from '@app/common/modules/product/repositories'; + +@Module({ + imports: [ConfigModule, SpaceRepositoryModule], + controllers: [SpaceModelController], + providers: [ + SpaceModelService, + SpaceModelRepository, + ProjectRepository, + SubSpaceModelService, + SpaceProductModelService, + SubspaceModelRepository, + SpaceProductModelRepository, + ProductRepository, + SpaceProductItemModelService, + SpaceProductItemModelRepository, + ], + exports: [], +}) +export class SpaceModelModule {} diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 0055ddf..8189f8c 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -5,7 +5,12 @@ import { HttpStatus, Injectable, } from '@nestjs/common'; -import { AddSpaceDto, CommunitySpaceParam, GetSpaceParam, UpdateSpaceDto } from '../dtos'; +import { + AddSpaceDto, + CommunitySpaceParam, + GetSpaceParam, + UpdateSpaceDto, +} from '../dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { CommunityRepository } from '@app/common/modules/community/repositories'; From 69d0065ee6c912711a24858a674cbb1f86b03a24 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 11 Dec 2024 11:46:30 +0400 Subject: [PATCH 030/247] added unique subspace name --- .../entities/subspace-model.entity.ts | 3 +- .../dtos/create-space-product-model.dto.ts | 9 ++++- .../services/space-model.service.ts | 2 +- .../space-product-item-model.service.ts | 4 ++ .../services/space-product-model.service.ts | 4 ++ .../services/subspace-model.service.ts | 37 +++++++++++++++++++ 6 files changed, 56 insertions(+), 3 deletions(-) diff --git a/libs/common/src/modules/space-model/entities/subspace-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model.entity.ts index abb1c64..776f831 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model.entity.ts @@ -1,9 +1,10 @@ -import { Column, Entity, ManyToOne } from 'typeorm'; +import { Column, Entity, ManyToOne, Unique } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceModelEntity } from './space-model.entity'; import { SubSpaceModelDto } from '../dtos'; @Entity({ name: 'subspace-model' }) +@Unique(['subspaceName', 'spaceModel']) export class SubspaceModelEntity extends AbstractEntity { @Column({ type: 'uuid', diff --git a/src/space-model/dtos/create-space-product-model.dto.ts b/src/space-model/dtos/create-space-product-model.dto.ts index 63fb9dd..8b98bc6 100644 --- a/src/space-model/dtos/create-space-product-model.dto.ts +++ b/src/space-model/dtos/create-space-product-model.dto.ts @@ -1,5 +1,11 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString, IsArray, ValidateNested } from 'class-validator'; +import { + IsNotEmpty, + IsString, + IsArray, + ValidateNested, + IsInt, +} from 'class-validator'; import { Type } from 'class-transformer'; import { CreateSpaceProductItemModelDto } from './create-space-product-item-model.dto'; @@ -17,6 +23,7 @@ export class CreateSpaceProductModelDto { example: 3, }) @IsNotEmpty() + @IsInt() productCount: number; @ApiProperty({ diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index f7b8f28..37b9ab1 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -36,7 +36,7 @@ export class SpaceModelService { ); } - const spaceModel = await this.spaceModelRepository.create({ + const spaceModel = this.spaceModelRepository.create({ modelName, project, }); diff --git a/src/space-model/services/space-product-item-model.service.ts b/src/space-model/services/space-product-item-model.service.ts index ebf7d2d..0a5b43d 100644 --- a/src/space-model/services/space-product-item-model.service.ts +++ b/src/space-model/services/space-product-item-model.service.ts @@ -24,6 +24,10 @@ export class SpaceProductItemModelService { await this.create(itemModelDto, spaceProductModel, spaceModel); } } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( error.message || `An unexpected error occurred`, HttpStatus.INTERNAL_SERVER_ERROR, diff --git a/src/space-model/services/space-product-model.service.ts b/src/space-model/services/space-product-model.service.ts index 10ee9f5..c478dca 100644 --- a/src/space-model/services/space-product-model.service.ts +++ b/src/space-model/services/space-product-model.service.ts @@ -24,6 +24,10 @@ export class SpaceProductModelService { await this.create(spaceProductModelDto, spaceModel); } } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( error.message || `An unexpected error occurred`, HttpStatus.INTERNAL_SERVER_ERROR, diff --git a/src/space-model/services/subspace-model.service.ts b/src/space-model/services/subspace-model.service.ts index 45d33ef..5d4d347 100644 --- a/src/space-model/services/subspace-model.service.ts +++ b/src/space-model/services/subspace-model.service.ts @@ -16,6 +16,7 @@ export class SubSpaceModelService { spaceModel: SpaceModelEntity, ) { try { + this.validateInputDtos(subSpaceModelDtos); for (const subspaceDto of subSpaceModelDtos) { const subspace = this.subspaceModelRepository.create({ subspaceName: subspaceDto.subspaceName, @@ -24,10 +25,46 @@ export class SubSpaceModelService { await this.subspaceModelRepository.save(subspace); } } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( error.message || `An unexpected error occurred`, HttpStatus.INTERNAL_SERVER_ERROR, ); } } + + private validateInputDtos(subSpaceModelDtos: CreateSubspaceModelDto[]) { + if (subSpaceModelDtos.length === 0) { + throw new HttpException( + 'Subspace models cannot be empty.', + HttpStatus.BAD_REQUEST, + ); + } + + const incomingNames = subSpaceModelDtos.map((dto) => dto.subspaceName); + this.validateName(incomingNames); + } + + private validateName(names: string[]) { + const seenNames = new Set(); + const duplicateNames = new Set(); + + for (const name of names) { + if (seenNames.has(name)) { + duplicateNames.add(name); + } else { + seenNames.add(name); + } + } + + if (duplicateNames.size > 0) { + throw new HttpException( + `Duplicate subspace names found in request: ${[...duplicateNames].join(', ')}`, + HttpStatus.CONFLICT, + ); + } + } } From 4f4fd7b73423ae766132419df26173f169091512 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 11 Dec 2024 12:03:09 +0400 Subject: [PATCH 031/247] added count validation --- .../dtos/create-space-product-model.dto.ts | 2 ++ .../services/space-product-model.service.ts | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/space-model/dtos/create-space-product-model.dto.ts b/src/space-model/dtos/create-space-product-model.dto.ts index 8b98bc6..11d8bb5 100644 --- a/src/space-model/dtos/create-space-product-model.dto.ts +++ b/src/space-model/dtos/create-space-product-model.dto.ts @@ -5,6 +5,7 @@ import { IsArray, ValidateNested, IsInt, + ArrayNotEmpty, } from 'class-validator'; import { Type } from 'class-transformer'; import { CreateSpaceProductItemModelDto } from './create-space-product-item-model.dto'; @@ -31,6 +32,7 @@ export class CreateSpaceProductModelDto { type: [CreateSpaceProductItemModelDto], }) @IsArray() + @ArrayNotEmpty() @ValidateNested({ each: true }) @Type(() => CreateSpaceProductItemModelDto) items: CreateSpaceProductItemModelDto[]; diff --git a/src/space-model/services/space-product-model.service.ts b/src/space-model/services/space-product-model.service.ts index c478dca..cf60203 100644 --- a/src/space-model/services/space-product-model.service.ts +++ b/src/space-model/services/space-product-model.service.ts @@ -39,6 +39,8 @@ export class SpaceProductModelService { spaceProductModelDto: CreateSpaceProductModelDto, spaceModel: SpaceModelEntity, ) { + this.validateCount(spaceProductModelDto); + const product = await this.productRepository.findOneBy({ uuid: spaceProductModelDto.productId, }); @@ -62,4 +64,14 @@ export class SpaceProductModelService { spaceModel, ); } + + private validateCount(spaceProductModelDto: CreateSpaceProductModelDto) { + const productItemCount = spaceProductModelDto.items.length; + if (spaceProductModelDto.productCount !== productItemCount) { + throw new HttpException( + `Product count (${spaceProductModelDto.productCount}) does not match the number of items (${productItemCount}) for product ID ${spaceProductModelDto.productId}.`, + HttpStatus.BAD_REQUEST, + ); + } + } } From 72c8445d24b6e82fc63bfa6a043e25a9c775c372 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 11 Dec 2024 13:23:37 +0400 Subject: [PATCH 032/247] updated create space model with query runner --- .../services/space-model.service.ts | 36 ++++++-- .../space-product-item-model.service.ts | 52 ++++++------ .../services/space-product-model.service.ts | 85 ++++++++++--------- .../services/subspace-model.service.ts | 16 ++-- 4 files changed, 109 insertions(+), 80 deletions(-) diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 37b9ab1..7359e25 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -6,10 +6,12 @@ import { ProjectParam } from 'src/community/dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SubSpaceModelService } from './subspace-model.service'; import { SpaceProductModelService } from './space-product-model.service'; +import { DataSource } from 'typeorm'; @Injectable() export class SpaceModelService { constructor( + private readonly dataSource: DataSource, private readonly spaceModelRepository: SpaceModelRepository, private readonly projectRepository: ProjectRepository, private readonly subSpaceModelService: SubSpaceModelService, @@ -24,6 +26,11 @@ export class SpaceModelService { createSpaceModelDto; const project = await this.validateProject(params.projectUuid); + const queryRunner = this.dataSource.createQueryRunner(); + + await queryRunner.connect(); + await queryRunner.startTransaction(); + try { const isModelExist = await this.validateName( modelName, @@ -40,17 +47,24 @@ export class SpaceModelService { modelName, project, }); - const savedSpaceModel = await this.spaceModelRepository.save(spaceModel); + const savedSpaceModel = await queryRunner.manager.save(spaceModel); - await this.subSpaceModelService.createSubSpaceModels( - subspaceModels, - savedSpaceModel, - ); + if (subspaceModels) { + await this.subSpaceModelService.createSubSpaceModels( + subspaceModels, + savedSpaceModel, + queryRunner, + ); + } - await this.spaceProductModelService.createSpaceProductModels( - spaceProductModels, - savedSpaceModel, - ); + if (spaceProductModels) { + await this.spaceProductModelService.createSpaceProductModels( + spaceProductModels, + savedSpaceModel, + queryRunner, + ); + } + await queryRunner.commitTransaction(); return new SuccessResponseDto({ message: `Successfully created new space model with uuid ${savedSpaceModel.uuid}`, @@ -58,6 +72,8 @@ export class SpaceModelService { statusCode: HttpStatus.CREATED, }); } catch (error) { + await queryRunner.rollbackTransaction(); + if (error instanceof HttpException) { throw error; } @@ -66,6 +82,8 @@ export class SpaceModelService { error.message || `An unexpected error occurred`, HttpStatus.INTERNAL_SERVER_ERROR, ); + } finally { + await queryRunner.release(); } } diff --git a/src/space-model/services/space-product-item-model.service.ts b/src/space-model/services/space-product-item-model.service.ts index 0a5b43d..b2f856c 100644 --- a/src/space-model/services/space-product-item-model.service.ts +++ b/src/space-model/services/space-product-item-model.service.ts @@ -5,6 +5,7 @@ import { } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { CreateSpaceProductItemModelDto } from '../dtos'; +import { QueryRunner } from 'typeorm'; @Injectable() export class SpaceProductItemModelService { @@ -16,41 +17,37 @@ export class SpaceProductItemModelService { itemModelDtos: CreateSpaceProductItemModelDto[], spaceProductModel: SpaceProductModelEntity, spaceModel: SpaceModelEntity, + queryRunner: QueryRunner, ) { - try { - await this.validateTags(itemModelDtos, spaceModel); + await this.validateTags(itemModelDtos, spaceModel, queryRunner); - for (const itemModelDto of itemModelDtos) { - await this.create(itemModelDto, spaceProductModel, spaceModel); - } + try { + const productItems = itemModelDtos.map((dto) => + queryRunner.manager.create(this.spaceProductItemRepository.target, { + tag: dto.tag, + spaceProductModel, + spaceModel, + }), + ); + + await queryRunner.manager.save(productItems); } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException( - error.message || `An unexpected error occurred`, + error.message || + 'An unexpected error occurred while creating product items.', HttpStatus.INTERNAL_SERVER_ERROR, ); } } - async create( - itemModelDto: CreateSpaceProductItemModelDto, - spaceProductModel: SpaceProductModelEntity, - spaceModel: SpaceModelEntity, - ) { - const productItem = this.spaceProductItemRepository.create({ - tag: itemModelDto.tag, - spaceProductModel, - spaceModel, - }); - await this.spaceProductItemRepository.save(productItem); - } - - async validateTags( + private async validateTags( itemModelDtos: CreateSpaceProductItemModelDto[], spaceModel: SpaceModelEntity, + queryRunner: QueryRunner, ) { const incomingTags = itemModelDtos.map((item) => item.tag); @@ -59,15 +56,18 @@ export class SpaceProductItemModelService { ); if (duplicateTags.length > 0) { throw new HttpException( - `Duplicate tags found in request: ${duplicateTags.join(', ')}`, - HttpStatus.CONFLICT, + `Duplicate tags found in the request: ${[...new Set(duplicateTags)].join(', ')}`, + HttpStatus.BAD_REQUEST, ); } - const existingTags = await this.spaceProductItemRepository.find({ - where: { spaceModel }, - select: ['tag'], - }); + const existingTags = await queryRunner.manager.find( + this.spaceProductItemRepository.target, + { + where: { spaceModel }, + select: ['tag'], + }, + ); const existingTagSet = new Set(existingTags.map((item) => item.tag)); const conflictingTags = incomingTags.filter((tag) => diff --git a/src/space-model/services/space-product-model.service.ts b/src/space-model/services/space-product-model.service.ts index cf60203..aa08a16 100644 --- a/src/space-model/services/space-product-model.service.ts +++ b/src/space-model/services/space-product-model.service.ts @@ -6,6 +6,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { CreateSpaceProductModelDto } from '../dtos'; import { ProductRepository } from '@app/common/modules/product/repositories'; import { SpaceProductItemModelService } from './space-product-item-model.service'; +import { QueryRunner } from 'typeorm'; @Injectable() export class SpaceProductModelService { @@ -18,60 +19,66 @@ export class SpaceProductModelService { async createSpaceProductModels( spaceProductModelDtos: CreateSpaceProductModelDto[], spaceModel: SpaceModelEntity, + queryRunner: QueryRunner, ) { try { - for (const spaceProductModelDto of spaceProductModelDtos) { - await this.create(spaceProductModelDto, spaceModel); - } + const productModels = await Promise.all( + spaceProductModelDtos.map(async (dto) => { + this.validateProductCount(dto); + const product = await this.getProduct(dto.productId); + return queryRunner.manager.create( + this.spaceProductModelRepository.target, + { + product, + productCount: dto.productCount, + spaceModel, + }, + ); + }), + ); + + const savedProductModels = await queryRunner.manager.save(productModels); + + await Promise.all( + spaceProductModelDtos.map((dto, index) => + this.spaceProductItemModelService.createProdutItemModel( + dto.items, + savedProductModels[index], + spaceModel, + queryRunner, + ), + ), + ); } catch (error) { if (error instanceof HttpException) { throw error; } - throw new HttpException( - error.message || `An unexpected error occurred`, + error.message || + 'An unexpected error occurred while creating product models.', HttpStatus.INTERNAL_SERVER_ERROR, ); } } - async create( - spaceProductModelDto: CreateSpaceProductModelDto, - spaceModel: SpaceModelEntity, - ) { - this.validateCount(spaceProductModelDto); - - const product = await this.productRepository.findOneBy({ - uuid: spaceProductModelDto.productId, - }); - if (!product) { + private validateProductCount(dto: CreateSpaceProductModelDto) { + const productItemCount = dto.items.length; + if (dto.productCount !== productItemCount) { throw new HttpException( - `Product with ID ${spaceProductModelDto.productId} not found`, - HttpStatus.NOT_FOUND, - ); - } - const spaceProductModel = this.spaceProductModelRepository.create({ - product, - productCount: spaceProductModelDto.productCount, - spaceModel: spaceModel, - }); - const newProductModel = - await this.spaceProductModelRepository.save(spaceProductModel); - - await this.spaceProductItemModelService.createProdutItemModel( - spaceProductModelDto.items, - newProductModel, - spaceModel, - ); - } - - private validateCount(spaceProductModelDto: CreateSpaceProductModelDto) { - const productItemCount = spaceProductModelDto.items.length; - if (spaceProductModelDto.productCount !== productItemCount) { - throw new HttpException( - `Product count (${spaceProductModelDto.productCount}) does not match the number of items (${productItemCount}) for product ID ${spaceProductModelDto.productId}.`, + `Product count (${dto.productCount}) does not match the number of items (${productItemCount}) for product ID ${dto.productId}.`, HttpStatus.BAD_REQUEST, ); } } + + private async getProduct(productId: string) { + const product = await this.productRepository.findOneBy({ uuid: productId }); + if (!product) { + throw new HttpException( + `Product with ID ${productId} not found.`, + HttpStatus.NOT_FOUND, + ); + } + return product; + } } diff --git a/src/space-model/services/subspace-model.service.ts b/src/space-model/services/subspace-model.service.ts index 5d4d347..a6a75aa 100644 --- a/src/space-model/services/subspace-model.service.ts +++ b/src/space-model/services/subspace-model.service.ts @@ -4,6 +4,7 @@ import { } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { CreateSubspaceModelDto } from '../dtos'; +import { QueryRunner } from 'typeorm'; @Injectable() export class SubSpaceModelService { @@ -14,16 +15,19 @@ export class SubSpaceModelService { async createSubSpaceModels( subSpaceModelDtos: CreateSubspaceModelDto[], spaceModel: SpaceModelEntity, + queryRunner: QueryRunner, ) { + this.validateInputDtos(subSpaceModelDtos); + try { - this.validateInputDtos(subSpaceModelDtos); - for (const subspaceDto of subSpaceModelDtos) { - const subspace = this.subspaceModelRepository.create({ + const subspaces = subSpaceModelDtos.map((subspaceDto) => + queryRunner.manager.create(this.subspaceModelRepository.target, { subspaceName: subspaceDto.subspaceName, spaceModel: spaceModel, - }); - await this.subspaceModelRepository.save(subspace); - } + }), + ); + + await queryRunner.manager.save(subspaces); } catch (error) { if (error instanceof HttpException) { throw error; From 708a1d9a92b1d3b496841702ab53af27b8fdfb7d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 11 Dec 2024 13:56:19 +0400 Subject: [PATCH 033/247] updated table name --- .../modules/space-model/entities/space-product-item.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/modules/space-model/entities/space-product-item.entity.ts b/libs/common/src/modules/space-model/entities/space-product-item.entity.ts index d830fba..3695831 100644 --- a/libs/common/src/modules/space-model/entities/space-product-item.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-product-item.entity.ts @@ -4,7 +4,7 @@ import { SpaceProductItemDto } from '../dtos'; import { SpaceProductModelEntity } from './space-product-model.entity'; import { SpaceModelEntity } from './space-model.entity'; -@Entity({ name: 'space-product-item' }) +@Entity({ name: 'space-product-item-model' }) @Unique(['tag', 'spaceProductModel', 'spaceModel']) export class SpaceProductItemModelEntity extends AbstractEntity { @Column({ From 88a4b26919fe0a9d09da18544f5fab9290e51eca Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 11 Dec 2024 15:27:06 +0400 Subject: [PATCH 034/247] added entities for space product item --- libs/common/src/database/database.module.ts | 3 +++ .../src/modules/space-model/dtos/index.ts | 2 +- .../dtos/space-product-item-model.dto.ts | 15 ++++++++++++ .../dtos/space-product-model.dto.ts | 6 ++--- .../entities/space-product-item.entity.ts | 17 +++----------- libs/common/src/modules/space/dtos/index.ts | 2 ++ .../dtos/space-product-item.dto.ts | 2 +- .../modules/space/dtos/space-product.dto.ts | 23 +++++++++++++++++++ .../src/modules/space/entities/index.ts | 2 ++ .../entities/space-product-item.entity.ts | 17 ++++++++++++++ .../space/entities/space-product.entity.ts | 8 ++++++- .../space-product-item-model.service.ts | 7 +++--- 12 files changed, 80 insertions(+), 24 deletions(-) create mode 100644 libs/common/src/modules/space-model/dtos/space-product-item-model.dto.ts rename libs/common/src/modules/{space-model => space}/dtos/space-product-item.dto.ts (86%) create mode 100644 libs/common/src/modules/space/dtos/space-product.dto.ts create mode 100644 libs/common/src/modules/space/entities/space-product-item.entity.ts diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 44fd854..90b287a 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -11,6 +11,7 @@ import { PermissionTypeEntity } from '../modules/permission/entities'; import { SpaceEntity, SpaceLinkEntity, + SpaceProductItemEntity, SubspaceEntity, } from '../modules/space/entities'; import { UserSpaceEntity } from '../modules/user/entities'; @@ -78,6 +79,8 @@ import { SpaceProductModelEntity, SpaceProductItemModelEntity, SubspaceModelEntity, + SpaceProductEntity, + SpaceProductItemEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), diff --git a/libs/common/src/modules/space-model/dtos/index.ts b/libs/common/src/modules/space-model/dtos/index.ts index f775b4a..3923af4 100644 --- a/libs/common/src/modules/space-model/dtos/index.ts +++ b/libs/common/src/modules/space-model/dtos/index.ts @@ -1,4 +1,4 @@ export * from './subspace-model.dto'; export * from './space-model.dto'; -export * from './space-product-item.dto'; +export * from './space-product-item-model.dto'; export * from './space-product-model.dto'; diff --git a/libs/common/src/modules/space-model/dtos/space-product-item-model.dto.ts b/libs/common/src/modules/space-model/dtos/space-product-item-model.dto.ts new file mode 100644 index 0000000..5eb9bca --- /dev/null +++ b/libs/common/src/modules/space-model/dtos/space-product-item-model.dto.ts @@ -0,0 +1,15 @@ +import { IsString, IsNotEmpty } from 'class-validator'; + +export class SpaceProductItemModelDto { + @IsString() + @IsNotEmpty() + uuid: string; + + @IsString() + @IsNotEmpty() + tag: string; + + @IsString() + @IsNotEmpty() + spaceProductModelUuid: string; +} diff --git a/libs/common/src/modules/space-model/dtos/space-product-model.dto.ts b/libs/common/src/modules/space-model/dtos/space-product-model.dto.ts index eae8088..7952a23 100644 --- a/libs/common/src/modules/space-model/dtos/space-product-model.dto.ts +++ b/libs/common/src/modules/space-model/dtos/space-product-model.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; -import { SpaceProductItemDto } from './space-product-item.dto'; +import { SpaceProductItemModelDto } from './space-product-item-model.dto'; export class SpaceProductModelDto { @IsString() @@ -17,7 +17,7 @@ export class SpaceProductModelDto { @ApiProperty({ description: 'List of individual items with specific names for the product', - type: [SpaceProductItemDto], + type: [SpaceProductItemModelDto], }) - items: SpaceProductItemDto[]; + items: SpaceProductItemModelDto[]; } diff --git a/libs/common/src/modules/space-model/entities/space-product-item.entity.ts b/libs/common/src/modules/space-model/entities/space-product-item.entity.ts index 3695831..7d39974 100644 --- a/libs/common/src/modules/space-model/entities/space-product-item.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-product-item.entity.ts @@ -1,12 +1,10 @@ -import { Entity, Column, ManyToOne, Unique } from 'typeorm'; +import { Entity, Column, ManyToOne } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { SpaceProductItemDto } from '../dtos'; +import { SpaceProductItemModelDto } from '../dtos'; import { SpaceProductModelEntity } from './space-product-model.entity'; -import { SpaceModelEntity } from './space-model.entity'; @Entity({ name: 'space-product-item-model' }) -@Unique(['tag', 'spaceProductModel', 'spaceModel']) -export class SpaceProductItemModelEntity extends AbstractEntity { +export class SpaceProductItemModelEntity extends AbstractEntity { @Column({ nullable: false, }) @@ -20,13 +18,4 @@ export class SpaceProductItemModelEntity extends AbstractEntity SpaceModelEntity, - (spaceModel) => spaceModel.spaceProductModels, - { - nullable: false, - }, - ) - public spaceModel: SpaceModelEntity; } diff --git a/libs/common/src/modules/space/dtos/index.ts b/libs/common/src/modules/space/dtos/index.ts index fcc0fdd..b470336 100644 --- a/libs/common/src/modules/space/dtos/index.ts +++ b/libs/common/src/modules/space/dtos/index.ts @@ -1,2 +1,4 @@ export * from './space.dto'; export * from './subspace.dto'; +export * from './space-product-item.dto'; +export * from './space-product.dto'; diff --git a/libs/common/src/modules/space-model/dtos/space-product-item.dto.ts b/libs/common/src/modules/space/dtos/space-product-item.dto.ts similarity index 86% rename from libs/common/src/modules/space-model/dtos/space-product-item.dto.ts rename to libs/common/src/modules/space/dtos/space-product-item.dto.ts index fa68825..8973c1a 100644 --- a/libs/common/src/modules/space-model/dtos/space-product-item.dto.ts +++ b/libs/common/src/modules/space/dtos/space-product-item.dto.ts @@ -11,5 +11,5 @@ export class SpaceProductItemDto { @IsString() @IsNotEmpty() - spaceProductModelUuid: string; + spaceProductUuid: string; } diff --git a/libs/common/src/modules/space/dtos/space-product.dto.ts b/libs/common/src/modules/space/dtos/space-product.dto.ts new file mode 100644 index 0000000..a57d29e --- /dev/null +++ b/libs/common/src/modules/space/dtos/space-product.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNotEmpty, IsNumber } from 'class-validator'; +import { SpaceProductItemDto } from './space-product-item.dto'; + +export class SpaceProductModelDto { + @IsString() + @IsNotEmpty() + uuid: string; + + @IsNumber() + @IsNotEmpty() + productCount: number; + + @IsString() + @IsNotEmpty() + productUuid: string; + + @ApiProperty({ + description: 'List of individual items with specific names for the product', + type: [SpaceProductItemDto], + }) + items: SpaceProductItemDto[]; +} diff --git a/libs/common/src/modules/space/entities/index.ts b/libs/common/src/modules/space/entities/index.ts index f720cf4..d09bdac 100644 --- a/libs/common/src/modules/space/entities/index.ts +++ b/libs/common/src/modules/space/entities/index.ts @@ -1,3 +1,5 @@ export * from './space.entity'; export * from './subspace.entity'; +export * from './space-product-item.entity'; +export * from './space-product.entity'; export * from './space-link.entity'; diff --git a/libs/common/src/modules/space/entities/space-product-item.entity.ts b/libs/common/src/modules/space/entities/space-product-item.entity.ts new file mode 100644 index 0000000..d45cfa0 --- /dev/null +++ b/libs/common/src/modules/space/entities/space-product-item.entity.ts @@ -0,0 +1,17 @@ +import { Column, Entity, ManyToOne } from 'typeorm'; +import { SpaceProductEntity } from './space-product.entity'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { SpaceProductItemDto } from '../dtos'; + +@Entity({ name: 'space-product-item-model' }) +export class SpaceProductItemEntity extends AbstractEntity { + @Column({ + nullable: false, + }) + public tag: string; + + @ManyToOne(() => SpaceProductEntity, (spaceProduct) => spaceProduct.items, { + nullable: false, + }) + public spaceProducts: SpaceProductEntity; +} diff --git a/libs/common/src/modules/space/entities/space-product.entity.ts b/libs/common/src/modules/space/entities/space-product.entity.ts index 92ad411..244a860 100644 --- a/libs/common/src/modules/space/entities/space-product.entity.ts +++ b/libs/common/src/modules/space/entities/space-product.entity.ts @@ -1,7 +1,8 @@ -import { Column, Entity, ManyToOne, JoinColumn } from 'typeorm'; +import { Column, Entity, ManyToOne, JoinColumn, OneToMany } from 'typeorm'; import { SpaceEntity } from './space.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { ProductEntity } from '../../product/entities'; +import { SpaceProductItemEntity } from './space-product-item.entity'; @Entity({ name: 'space-product' }) export class SpaceProductEntity extends AbstractEntity { @@ -25,6 +26,11 @@ export class SpaceProductEntity extends AbstractEntity { }) productCount: number; + @OneToMany(() => SpaceProductItemEntity, (item) => item.spaceProducts, { + cascade: true, + }) + public items: SpaceProductItemEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/src/space-model/services/space-product-item-model.service.ts b/src/space-model/services/space-product-item-model.service.ts index b2f856c..3ec0eb2 100644 --- a/src/space-model/services/space-product-item-model.service.ts +++ b/src/space-model/services/space-product-item-model.service.ts @@ -19,14 +19,13 @@ export class SpaceProductItemModelService { spaceModel: SpaceModelEntity, queryRunner: QueryRunner, ) { - await this.validateTags(itemModelDtos, spaceModel, queryRunner); + await this.validateTags(itemModelDtos, queryRunner, spaceModel); try { const productItems = itemModelDtos.map((dto) => queryRunner.manager.create(this.spaceProductItemRepository.target, { tag: dto.tag, spaceProductModel, - spaceModel, }), ); @@ -46,8 +45,8 @@ export class SpaceProductItemModelService { private async validateTags( itemModelDtos: CreateSpaceProductItemModelDto[], - spaceModel: SpaceModelEntity, queryRunner: QueryRunner, + spaceModel: SpaceModelEntity, ) { const incomingTags = itemModelDtos.map((item) => item.tag); @@ -64,7 +63,7 @@ export class SpaceProductItemModelService { const existingTags = await queryRunner.manager.find( this.spaceProductItemRepository.target, { - where: { spaceModel }, + where: { spaceProductModel: { spaceModel } }, select: ['tag'], }, ); From 6d8c9a0ec021e49fa1989ae3ea39717e1c1a3fa2 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 11 Dec 2024 16:06:51 +0400 Subject: [PATCH 035/247] added the relation between models and space --- .../space-model/entities/space-model.entity.ts | 6 ++++++ .../space-model/entities/space-product-item.entity.ts | 10 +++++++++- .../entities/space-product-model.entity.ts | 10 ++++++++++ .../space-model/entities/subspace-model.entity.ts | 8 +++++++- .../space/entities/space-product-item.entity.ts | 11 +++++++++++ .../modules/space/entities/space-product.entity.ts | 11 +++++++++++ .../common/src/modules/space/entities/space.entity.ts | 5 +++++ .../src/modules/space/entities/subspace.entity.ts | 5 +++++ 8 files changed, 64 insertions(+), 2 deletions(-) diff --git a/libs/common/src/modules/space-model/entities/space-model.entity.ts b/libs/common/src/modules/space-model/entities/space-model.entity.ts index 9937a01..b67c642 100644 --- a/libs/common/src/modules/space-model/entities/space-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-model.entity.ts @@ -11,6 +11,7 @@ import { SpaceModelDto } from '../dtos'; import { SubspaceModelEntity } from './subspace-model.entity'; import { SpaceProductModelEntity } from './space-product-model.entity'; import { ProjectEntity } from '../../project/entities'; +import { SpaceEntity } from '../../space/entities'; @Entity({ name: 'space-model' }) @Unique(['modelName', 'project']) @@ -53,4 +54,9 @@ export class SpaceModelEntity extends AbstractEntity { }, ) public spaceProductModels: SpaceProductModelEntity[]; + + @OneToMany(() => SpaceEntity, (space) => space.spaceModel, { + cascade: true, + }) + public spaces: SpaceEntity[]; } diff --git a/libs/common/src/modules/space-model/entities/space-product-item.entity.ts b/libs/common/src/modules/space-model/entities/space-product-item.entity.ts index 7d39974..062418f 100644 --- a/libs/common/src/modules/space-model/entities/space-product-item.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-product-item.entity.ts @@ -1,7 +1,8 @@ -import { Entity, Column, ManyToOne } from 'typeorm'; +import { Entity, Column, ManyToOne, OneToMany } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceProductItemModelDto } from '../dtos'; import { SpaceProductModelEntity } from './space-product-model.entity'; +import { SpaceProductItemEntity } from '../../space/entities'; @Entity({ name: 'space-product-item-model' }) export class SpaceProductItemModelEntity extends AbstractEntity { @@ -18,4 +19,11 @@ export class SpaceProductItemModelEntity extends AbstractEntity SpaceProductItemEntity, + (spaceProductItem) => spaceProductItem.spaceProductItemModel, + { cascade: true }, + ) + public items: SpaceProductItemEntity[]; } diff --git a/libs/common/src/modules/space-model/entities/space-product-model.entity.ts b/libs/common/src/modules/space-model/entities/space-product-model.entity.ts index d843d07..c48cf91 100644 --- a/libs/common/src/modules/space-model/entities/space-product-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-product-model.entity.ts @@ -4,6 +4,7 @@ import { ProductEntity } from '../../product/entities'; import { SpaceModelEntity } from './space-model.entity'; import { SpaceProductItemModelEntity } from './space-product-item.entity'; import { SpaceProductModelDto } from '../dtos'; +import { SpaceProductEntity } from '../../space/entities'; @Entity({ name: 'space-product-model' }) export class SpaceProductModelEntity extends AbstractEntity { @@ -37,4 +38,13 @@ export class SpaceProductModelEntity extends AbstractEntity SpaceProductEntity, + (spaceProduct) => spaceProduct.spaceProductModel, + { + cascade: true, + }, + ) + public spaceProducts: SpaceProductEntity[]; } diff --git a/libs/common/src/modules/space-model/entities/subspace-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model.entity.ts index 776f831..c1e335a 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model.entity.ts @@ -1,7 +1,8 @@ -import { Column, Entity, ManyToOne, Unique } from 'typeorm'; +import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceModelEntity } from './space-model.entity'; import { SubSpaceModelDto } from '../dtos'; +import { SubspaceEntity } from '../../space/entities'; @Entity({ name: 'subspace-model' }) @Unique(['subspaceName', 'spaceModel']) @@ -27,4 +28,9 @@ export class SubspaceModelEntity extends AbstractEntity { }, ) public spaceModel: SpaceModelEntity; + + @OneToMany(() => SubspaceEntity, (space) => space.subSpaceModel, { + cascade: true, + }) + public spaces: SubspaceEntity[]; } diff --git a/libs/common/src/modules/space/entities/space-product-item.entity.ts b/libs/common/src/modules/space/entities/space-product-item.entity.ts index d45cfa0..7c6260f 100644 --- a/libs/common/src/modules/space/entities/space-product-item.entity.ts +++ b/libs/common/src/modules/space/entities/space-product-item.entity.ts @@ -2,6 +2,7 @@ import { Column, Entity, ManyToOne } from 'typeorm'; import { SpaceProductEntity } from './space-product.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceProductItemDto } from '../dtos'; +import { SpaceProductItemModelEntity } from '../../space-model'; @Entity({ name: 'space-product-item-model' }) export class SpaceProductItemEntity extends AbstractEntity { @@ -14,4 +15,14 @@ export class SpaceProductItemEntity extends AbstractEntity nullable: false, }) public spaceProducts: SpaceProductEntity; + + @ManyToOne( + () => SpaceProductItemModelEntity, + (spaceProductItemModel) => spaceProductItemModel.items, + { + nullable: true, + onDelete: 'SET NULL', + }, + ) + public spaceProductItemModel?: SpaceProductItemModelEntity; } diff --git a/libs/common/src/modules/space/entities/space-product.entity.ts b/libs/common/src/modules/space/entities/space-product.entity.ts index 244a860..8fa0c4f 100644 --- a/libs/common/src/modules/space/entities/space-product.entity.ts +++ b/libs/common/src/modules/space/entities/space-product.entity.ts @@ -3,6 +3,7 @@ import { SpaceEntity } from './space.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { ProductEntity } from '../../product/entities'; import { SpaceProductItemEntity } from './space-product-item.entity'; +import { SpaceProductModelEntity } from '../../space-model'; @Entity({ name: 'space-product' }) export class SpaceProductEntity extends AbstractEntity { @@ -31,6 +32,16 @@ export class SpaceProductEntity extends AbstractEntity { }) public items: SpaceProductItemEntity[]; + @ManyToOne( + () => SpaceProductModelEntity, + (spaceProductModel) => spaceProductModel.spaceProducts, + { + nullable: true, + }, + ) + @JoinColumn({ name: 'space_product_model_uuid' }) + public spaceProductModel?: SpaceProductModelEntity; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 103f0a2..9d6e6fd 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -15,6 +15,7 @@ import { SubspaceEntity } from './subspace.entity'; import { SpaceLinkEntity } from './space-link.entity'; import { SpaceProductEntity } from './space-product.entity'; import { SceneEntity } from '../../scene/entities'; +import { SpaceModelEntity } from '../../space-model'; @Entity({ name: 'space' }) @Unique(['invitationCode']) @@ -98,6 +99,10 @@ export class SpaceEntity extends AbstractEntity { @OneToMany(() => SceneEntity, (scene) => scene.space) scenes: SceneEntity[]; + @ManyToOne(() => SpaceModelEntity, { nullable: true }) + @JoinColumn({ name: 'space_model_uuid' }) + spaceModel?: SpaceModelEntity; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/space/entities/subspace.entity.ts b/libs/common/src/modules/space/entities/subspace.entity.ts index ab38b00..ccb5118 100644 --- a/libs/common/src/modules/space/entities/subspace.entity.ts +++ b/libs/common/src/modules/space/entities/subspace.entity.ts @@ -3,6 +3,7 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceEntity } from '../../device/entities'; import { SpaceEntity } from './space.entity'; import { SubspaceDto } from '../dtos'; +import { SubspaceModelEntity } from '../../space-model'; @Entity({ name: 'subspace' }) export class SubspaceEntity extends AbstractEntity { @@ -30,6 +31,10 @@ export class SubspaceEntity extends AbstractEntity { }) devices: DeviceEntity[]; + @ManyToOne(() => SubspaceModelEntity, { nullable: true }) + @JoinColumn({ name: 'subspace_model_uuid' }) + subSpaceModel?: SubspaceModelEntity; + constructor(partial: Partial) { super(); Object.assign(this, partial); From bbd40253b84a784463c9192ee9c3978e2b1637c3 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 11 Dec 2024 16:42:39 +0400 Subject: [PATCH 036/247] enities typo fix --- libs/common/src/modules/space-model/entities/index.ts | 2 +- ...roduct-item.entity.ts => space-product-item-model.entity.ts} | 0 .../modules/space-model/entities/space-product-model.entity.ts | 2 +- .../src/modules/space/entities/space-product-item.entity.ts | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename libs/common/src/modules/space-model/entities/{space-product-item.entity.ts => space-product-item-model.entity.ts} (100%) diff --git a/libs/common/src/modules/space-model/entities/index.ts b/libs/common/src/modules/space-model/entities/index.ts index 3bf3f69..f1e4016 100644 --- a/libs/common/src/modules/space-model/entities/index.ts +++ b/libs/common/src/modules/space-model/entities/index.ts @@ -1,4 +1,4 @@ export * from './space-model.entity'; -export * from './space-product-item.entity'; +export * from './space-product-item-model.entity'; export * from './space-product-model.entity'; export * from './subspace-model.entity'; \ No newline at end of file diff --git a/libs/common/src/modules/space-model/entities/space-product-item.entity.ts b/libs/common/src/modules/space-model/entities/space-product-item-model.entity.ts similarity index 100% rename from libs/common/src/modules/space-model/entities/space-product-item.entity.ts rename to libs/common/src/modules/space-model/entities/space-product-item-model.entity.ts diff --git a/libs/common/src/modules/space-model/entities/space-product-model.entity.ts b/libs/common/src/modules/space-model/entities/space-product-model.entity.ts index c48cf91..13b2b33 100644 --- a/libs/common/src/modules/space-model/entities/space-product-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-product-model.entity.ts @@ -2,7 +2,7 @@ import { Entity, Column, ManyToOne, OneToMany } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { ProductEntity } from '../../product/entities'; import { SpaceModelEntity } from './space-model.entity'; -import { SpaceProductItemModelEntity } from './space-product-item.entity'; +import { SpaceProductItemModelEntity } from './space-product-item-model.entity'; import { SpaceProductModelDto } from '../dtos'; import { SpaceProductEntity } from '../../space/entities'; diff --git a/libs/common/src/modules/space/entities/space-product-item.entity.ts b/libs/common/src/modules/space/entities/space-product-item.entity.ts index 7c6260f..70e7c1f 100644 --- a/libs/common/src/modules/space/entities/space-product-item.entity.ts +++ b/libs/common/src/modules/space/entities/space-product-item.entity.ts @@ -4,7 +4,7 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceProductItemDto } from '../dtos'; import { SpaceProductItemModelEntity } from '../../space-model'; -@Entity({ name: 'space-product-item-model' }) +@Entity({ name: 'space-product-item' }) export class SpaceProductItemEntity extends AbstractEntity { @Column({ nullable: false, From a2ee7a000106b1d52fa578604bca4b4ceaa594c5 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 11 Dec 2024 16:45:09 +0400 Subject: [PATCH 037/247] added space model details to space --- .../dtos/create-space-product-model.dto.ts | 2 +- .../space-product-item-model.service.ts | 6 ++ .../services/space-product-model.service.ts | 15 ++-- src/space/dtos/add.space.dto.ts | 69 ++++++++++++++++--- 4 files changed, 73 insertions(+), 19 deletions(-) diff --git a/src/space-model/dtos/create-space-product-model.dto.ts b/src/space-model/dtos/create-space-product-model.dto.ts index 11d8bb5..9f251be 100644 --- a/src/space-model/dtos/create-space-product-model.dto.ts +++ b/src/space-model/dtos/create-space-product-model.dto.ts @@ -17,7 +17,7 @@ export class CreateSpaceProductModelDto { }) @IsNotEmpty() @IsString() - productId: string; + productUuid: string; @ApiProperty({ description: 'Number of products in the model', diff --git a/src/space-model/services/space-product-item-model.service.ts b/src/space-model/services/space-product-item-model.service.ts index 3ec0eb2..ac9c0d8 100644 --- a/src/space-model/services/space-product-item-model.service.ts +++ b/src/space-model/services/space-product-item-model.service.ts @@ -21,6 +21,12 @@ export class SpaceProductItemModelService { ) { await this.validateTags(itemModelDtos, queryRunner, spaceModel); + if (!spaceProductModel) { + throw new HttpException( + 'Space product model is required to create product items.', + HttpStatus.BAD_REQUEST, + ); + } try { const productItems = itemModelDtos.map((dto) => queryRunner.manager.create(this.spaceProductItemRepository.target, { diff --git a/src/space-model/services/space-product-model.service.ts b/src/space-model/services/space-product-model.service.ts index aa08a16..27bad29 100644 --- a/src/space-model/services/space-product-model.service.ts +++ b/src/space-model/services/space-product-model.service.ts @@ -25,7 +25,7 @@ export class SpaceProductModelService { const productModels = await Promise.all( spaceProductModelDtos.map(async (dto) => { this.validateProductCount(dto); - const product = await this.getProduct(dto.productId); + const product = await this.getProduct(dto.productUuid); return queryRunner.manager.create( this.spaceProductModelRepository.target, { @@ -40,14 +40,15 @@ export class SpaceProductModelService { const savedProductModels = await queryRunner.manager.save(productModels); await Promise.all( - spaceProductModelDtos.map((dto, index) => - this.spaceProductItemModelService.createProdutItemModel( + spaceProductModelDtos.map((dto, index) => { + const savedModel = savedProductModels[index]; + return this.spaceProductItemModelService.createProdutItemModel( dto.items, - savedProductModels[index], + savedModel, // Pass the saved model spaceModel, queryRunner, - ), - ), + ); + }), ); } catch (error) { if (error instanceof HttpException) { @@ -65,7 +66,7 @@ export class SpaceProductModelService { const productItemCount = dto.items.length; if (dto.productCount !== productItemCount) { throw new HttpException( - `Product count (${dto.productCount}) does not match the number of items (${productItemCount}) for product ID ${dto.productId}.`, + `Product count (${dto.productCount}) does not match the number of items (${productItemCount}) for product ID ${dto.productUuid}.`, HttpStatus.BAD_REQUEST, ); } diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index 554f17b..fe88c33 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -1,6 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { + ArrayNotEmpty, IsArray, IsBoolean, IsNotEmpty, @@ -11,6 +12,42 @@ import { ValidateNested, } from 'class-validator'; +export class CreateSpaceProductItemDto { + @ApiProperty({ + description: 'Specific name for the product item', + example: 'Light 1', + }) + @IsNotEmpty() + @IsString() + tag: string; +} + +class ProductAssignmentDto { + @ApiProperty({ + description: 'UUID of the product to be assigned', + example: 'prod-uuid-1234', + }) + @IsNotEmpty() + productId: string; + + @ApiProperty({ + description: 'Number of items to assign for the product', + example: 3, + }) + count: number; + + @ApiProperty({ + description: 'Specific names for each product item', + type: [CreateSpaceProductItemDto], + example: [{ tag: 'Light 1' }, { tag: 'Light 2' }, { tag: 'Light 3' }], + }) + @IsArray() + @ArrayNotEmpty() + @ValidateNested({ each: true }) + @Type(() => CreateSpaceProductItemDto) + items: CreateSpaceProductItemDto[]; +} + export class AddSpaceDto { @ApiProperty({ description: 'Name of the space (e.g., Floor 1, Unit 101)', @@ -29,9 +66,14 @@ export class AddSpaceDto { @IsOptional() parentUuid?: string; + @ApiProperty({ + description: 'Icon identifier for the space', + example: 'assets/location', + required: false, + }) @IsString() @IsOptional() - public icon: string; + public icon?: string; @ApiProperty({ description: 'Indicates whether the space is private or public', @@ -49,16 +91,29 @@ export class AddSpaceDto { @IsNumber() y: number; + @ApiProperty({ + description: 'UUID of the Space', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsString() + @IsOptional() + spaceModelUuid?: string; + @ApiProperty({ description: 'Y position on canvas', example: 200 }) @IsString() @IsOptional() - direction: string; + direction?: string; + @ApiProperty({ + description: 'List of products assigned to this space', + type: [ProductAssignmentDto], + required: false, + }) @IsArray() @ValidateNested({ each: true }) @IsOptional() @Type(() => ProductAssignmentDto) - products: ProductAssignmentDto[]; + products?: ProductAssignmentDto[]; } export class AddUserSpaceDto { @@ -101,11 +156,3 @@ export class AddUserSpaceUsingCodeDto { Object.assign(this, dto); } } - -class ProductAssignmentDto { - @IsNotEmpty() - productId: string; - - @IsNotEmpty() - count: number; -} From 8af29cddfd4d8651d2fc54d937e8fbcc6c4f2417 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 11 Dec 2024 17:01:23 +0400 Subject: [PATCH 038/247] add validation to check both space model and products, subspaces are not created --- src/space/dtos/add.space.dto.ts | 22 +++++++++++++++++++++- src/space/services/space.service.ts | 20 +++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index fe88c33..d66fa87 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -22,7 +22,17 @@ export class CreateSpaceProductItemDto { tag: string; } -class ProductAssignmentDto { +export class CreateSubspaceDto { + @ApiProperty({ + description: 'Name of the subspace', + example: 'Living Room', + }) + @IsNotEmpty() + @IsString() + subspaceName: string; +} + +export class ProductAssignmentDto { @ApiProperty({ description: 'UUID of the product to be assigned', example: 'prod-uuid-1234', @@ -114,6 +124,16 @@ export class AddSpaceDto { @IsOptional() @Type(() => ProductAssignmentDto) products?: ProductAssignmentDto[]; + + @ApiProperty({ + description: 'List of subspaces included in the model', + type: [CreateSubspaceDto], + }) + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CreateSubspaceDto) + subspaces?: CreateSubspaceDto[]; } export class AddUserSpaceDto { diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 8189f8c..6fc35eb 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -9,6 +9,7 @@ import { AddSpaceDto, CommunitySpaceParam, GetSpaceParam, + ProductAssignmentDto, UpdateSpaceDto, } from '../dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; @@ -19,6 +20,7 @@ import { generateRandomString } from '@app/common/helper/randomString'; import { SpaceLinkService } from './space-link'; import { SpaceProductService } from './space-products'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; +import { CreateSubspaceModelDto } from 'src/space-model/dtos'; @Injectable() export class SpaceService { @@ -34,11 +36,14 @@ export class SpaceService { addSpaceDto: AddSpaceDto, params: CommunitySpaceParam, ): Promise { - const { parentUuid, direction, products } = addSpaceDto; + const { parentUuid, direction, products, spaceModelUuid, subspaces } = + addSpaceDto; const { communityUuid, projectUuid } = params; await this.validateProject(projectUuid); + this.validateSpaceCreation(spaceModelUuid, products, subspaces); + const community = await this.validateCommunity(communityUuid); const parent = parentUuid ? await this.validateSpace(parentUuid) : null; @@ -344,4 +349,17 @@ export class SpaceService { HttpStatus.NOT_FOUND, ); } + + private validateSpaceCreation( + spaceModelUuid?: string, + products?: ProductAssignmentDto[], + subSpaces?: CreateSubspaceModelDto[], + ) { + if (spaceModelUuid && (products?.length || subSpaces?.length)) { + throw new HttpException( + 'Space model cannot be assigned with products or subspaces.', + HttpStatus.CONFLICT, + ); + } + } } From ba002ae474c8ce7e9ba9ad4ff03d05d4141a1dc0 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 12 Dec 2024 09:21:56 +0400 Subject: [PATCH 039/247] reduced code duplication --- src/project/project.module.ts | 3 +- src/space/dtos/add.space.dto.ts | 17 +- .../services/space-validation.service.ts | 74 ++++++++ src/space/services/space.service.ts | 163 +++++++++--------- .../services/subspace/subspace.service.ts | 89 ++++++++-- src/space/space.module.ts | 8 +- 6 files changed, 244 insertions(+), 110 deletions(-) create mode 100644 src/space/services/space-validation.service.ts diff --git a/src/project/project.module.ts b/src/project/project.module.ts index 9d82ba9..3cd906e 100644 --- a/src/project/project.module.ts +++ b/src/project/project.module.ts @@ -1,8 +1,9 @@ -import { Module } from '@nestjs/common'; +import { Global, Module } from '@nestjs/common'; import { ProjectController } from './controllers'; import { ProjectService } from './services'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; +@Global() @Module({ imports: [], controllers: [ProjectController], diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index d66fa87..402d37a 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -11,6 +11,7 @@ import { IsUUID, ValidateNested, } from 'class-validator'; +import { AddSubspaceDto } from './subspace'; export class CreateSpaceProductItemDto { @ApiProperty({ @@ -22,16 +23,6 @@ export class CreateSpaceProductItemDto { tag: string; } -export class CreateSubspaceDto { - @ApiProperty({ - description: 'Name of the subspace', - example: 'Living Room', - }) - @IsNotEmpty() - @IsString() - subspaceName: string; -} - export class ProductAssignmentDto { @ApiProperty({ description: 'UUID of the product to be assigned', @@ -127,13 +118,13 @@ export class AddSpaceDto { @ApiProperty({ description: 'List of subspaces included in the model', - type: [CreateSubspaceDto], + type: [AddSubspaceDto], }) @IsOptional() @IsArray() @ValidateNested({ each: true }) - @Type(() => CreateSubspaceDto) - subspaces?: CreateSubspaceDto[]; + @Type(() => AddSubspaceDto) + subspaces?: AddSubspaceDto[]; } export class AddUserSpaceDto { diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts new file mode 100644 index 0000000..56e6532 --- /dev/null +++ b/src/space/services/space-validation.service.ts @@ -0,0 +1,74 @@ +import { CommunityEntity } from '@app/common/modules/community/entities'; +import { SpaceEntity } from '@app/common/modules/space/entities'; +import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { CommunityService } from '../../community/services'; +import { ProjectService } from '../../project/services'; +import { + SpaceModelEntity, + SpaceModelRepository, +} from '@app/common/modules/space-model'; + +@Injectable() +export class ValidationService { + constructor( + private readonly projectService: ProjectService, + private readonly communityService: CommunityService, + private readonly spaceRepository: SpaceRepository, + private readonly spaceModelRepository: SpaceModelRepository, + ) {} + + async validateCommunityAndProject( + communityUuid: string, + projectUuid: string, + ): Promise { + await this.projectService.findOne(projectUuid); + const community = await this.communityService.getCommunityById({ + communityUuid, + projectUuid, + }); + + return community.data; + } + + async validateSpaceWithinCommunityAndProject( + communityUuid: string, + projectUuid: string, + spaceUuid?: string, + ): Promise { + await this.validateCommunityAndProject(communityUuid, projectUuid); + const space = await this.validateSpace(spaceUuid); + return space; + } + + async validateSpace(spaceUuid: string): Promise { + const space = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid }, + }); + + if (!space) { + throw new HttpException( + `Space with UUID ${spaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + return space; + } + + async validateSpaceModel(spaceModelUuid: string): Promise { + const spaceModel = await this.spaceModelRepository.findOne({ + where: { uuid: spaceModelUuid }, + relations: ['subspaceModels'], + }); + + if (!spaceModel) { + throw new HttpException( + `Space model with UUID ${spaceModelUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + return spaceModel; + } +} diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 6fc35eb..135f2e5 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -14,22 +14,24 @@ import { } from '../dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; 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'; -import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { CreateSubspaceModelDto } from 'src/space-model/dtos'; +import { SubSpaceService } from './subspace'; +import { DataSource } from 'typeorm'; +import { ValidationService } from './space-validation.service'; @Injectable() export class SpaceService { constructor( + private readonly dataSource: DataSource, private readonly spaceRepository: SpaceRepository, - private readonly communityRepository: CommunityRepository, private readonly spaceLinkService: SpaceLinkService, private readonly spaceProductService: SpaceProductService, - private readonly projectRepository: ProjectRepository, + private readonly subSpaceService: SubSpaceService, + private readonly validationService: ValidationService, ) {} async createSpace( @@ -40,21 +42,35 @@ export class SpaceService { addSpaceDto; const { communityUuid, projectUuid } = params; - await this.validateProject(projectUuid); + const queryRunner = this.dataSource.createQueryRunner(); + + await queryRunner.connect(); + await queryRunner.startTransaction(); + + const community = await this.validationService.validateCommunityAndProject( + communityUuid, + projectUuid, + ); this.validateSpaceCreation(spaceModelUuid, products, subspaces); - const community = await this.validateCommunity(communityUuid); + const parent = parentUuid + ? await this.validationService.validateSpace(parentUuid) + : null; + + const spaceModel = spaceModelUuid + ? await this.validationService.validateSpaceModel(spaceModelUuid) + : null; - const parent = parentUuid ? await this.validateSpace(parentUuid) : null; try { const newSpace = this.spaceRepository.create({ ...addSpaceDto, - community, + spaceModel, parent: parentUuid ? parent : null, + community, }); - await this.spaceRepository.save(newSpace); + await queryRunner.manager.save(newSpace); if (direction && parent) { await this.spaceLinkService.saveSpaceLink( @@ -64,12 +80,27 @@ export class SpaceService { ); } + if (subspaces) { + await this.subSpaceService.createSubspacesFromNames( + subspaces, + newSpace, + queryRunner, + ); + } else { + await this.subSpaceService.createSubSpaceFromModel( + spaceModel, + newSpace, + queryRunner, + ); + } + if (products && products.length > 0) { await this.spaceProductService.assignProductsToSpace( newSpace, products, ); } + await queryRunner.commitTransaction(); return new SuccessResponseDto({ statusCode: HttpStatus.CREATED, @@ -77,7 +108,14 @@ export class SpaceService { message: 'Space created successfully', }); } catch (error) { + await queryRunner.rollbackTransaction(); + + if (error instanceof HttpException) { + throw error; + } throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR); + } finally { + await queryRunner.release(); } } @@ -85,8 +123,10 @@ export class SpaceService { params: CommunitySpaceParam, ): Promise { const { communityUuid, projectUuid } = params; - await this.validateCommunity(communityUuid); - await this.validateProject(projectUuid); + await this.validationService.validateCommunityAndProject( + communityUuid, + projectUuid, + ); try { // Get all spaces related to the community, including the parent-child relations const spaces = await this.spaceRepository.find({ @@ -119,11 +159,12 @@ export class SpaceService { async findOne(params: GetSpaceParam): Promise { const { communityUuid, spaceUuid, projectUuid } = params; try { - const space = await this.validateCommunityAndSpace( - communityUuid, - spaceUuid, - projectUuid, - ); + const space = + await this.validationService.validateSpaceWithinCommunityAndProject( + communityUuid, + projectUuid, + spaceUuid, + ); return new SuccessResponseDto({ message: `Space with ID ${spaceUuid} successfully fetched`, @@ -144,12 +185,13 @@ export class SpaceService { async delete(params: GetSpaceParam): Promise { try { const { communityUuid, spaceUuid, projectUuid } = params; - // First, check if the community exists - const space = await this.validateCommunityAndSpace( - communityUuid, - spaceUuid, - projectUuid, - ); + + const space = + await this.validationService.validateSpaceWithinCommunityAndProject( + communityUuid, + projectUuid, + spaceUuid, + ); // Delete the space await this.spaceRepository.remove(space); @@ -175,15 +217,18 @@ export class SpaceService { ): Promise { const { communityUuid, spaceUuid, projectUuid } = params; try { - const space = await this.validateCommunityAndSpace( - communityUuid, - spaceUuid, - projectUuid, - ); + const space = + await this.validationService.validateSpaceWithinCommunityAndProject( + communityUuid, + projectUuid, + spaceUuid, + ); // If a parentId is provided, check if the parent exists const { parentUuid, products } = updateSpaceDto; - const parent = parentUuid ? await this.validateSpace(parentUuid) : null; + const parent = parentUuid + ? await this.validationService.validateSpace(parentUuid) + : null; // Update other space properties from updateSpaceDto Object.assign(space, updateSpaceDto, { parent }); @@ -218,7 +263,11 @@ export class SpaceService { params: GetSpaceParam, ): Promise { const { spaceUuid, communityUuid, projectUuid } = params; - await this.validateCommunityAndSpace(communityUuid, spaceUuid, projectUuid); + await this.validationService.validateSpaceWithinCommunityAndProject( + communityUuid, + projectUuid, + spaceUuid, + ); try { // Get all spaces that are children of the provided space, including the parent-child relations @@ -248,11 +297,12 @@ export class SpaceService { try { const invitationCode = generateRandomString(6); - const space = await this.validateCommunityAndSpace( - communityUuid, - spaceUuid, - projectUuid, - ); + const space = + await this.validationService.validateSpaceWithinCommunityAndProject( + communityUuid, + projectUuid, + spaceUuid, + ); space.invitationCode = invitationCode; await this.spaceRepository.save(space); @@ -298,51 +348,6 @@ export class SpaceService { 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, - projectUuid: string, - ) { - await this.validateProject(projectUuid); - - 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 }, - }); - if (!space) this.throwNotFound('Space', spaceUuid); - return space; - } - - private async validateProject(uuid: string) { - const project = await this.projectRepository.findOne({ - where: { uuid }, - }); - - if (!project) this.throwNotFound('Project', uuid); - } - private throwNotFound(entity: string, uuid: string) { throw new HttpException( `${entity} with ID ${uuid} not found`, diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index c343512..dae280b 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -9,25 +9,83 @@ import { } from '@app/common/models/typeOrmCustom.model'; import { PageResponse } from '@app/common/dto/pagination.response.dto'; import { SubspaceDto } from '@app/common/modules/space/dtos'; -import { SpaceService } from '../space.service'; +import { QueryRunner } from 'typeorm'; +import { + SpaceEntity, + SubspaceEntity, +} from '@app/common/modules/space/entities'; +import { SpaceModelEntity } from '@app/common/modules/space-model'; +import { ValidationService } from '../space-validation.service'; @Injectable() export class SubSpaceService { constructor( private readonly subspaceRepository: SubspaceRepository, - private readonly spaceService: SpaceService, + private readonly validationService: ValidationService, ) {} + async createSubspaces( + subspaceData: Array<{ subspaceName: string; space: SpaceEntity }>, + queryRunner: QueryRunner, + ): Promise { + try { + const subspaces = subspaceData.map((data) => + queryRunner.manager.create(this.subspaceRepository.target, data), + ); + + return await queryRunner.manager.save(subspaces); + } catch (error) { + throw new HttpException( + 'An unexpected error occurred while creating subspaces.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async createSubSpaceFromModel( + spaceModel: SpaceModelEntity, + space: SpaceEntity, + queryRunner: QueryRunner, + ): Promise { + const subSpaces = spaceModel.subspaceModels; + + if (!subSpaces || subSpaces.length === 0) { + return; + } + + const subspaceData = subSpaces.map((subSpaceModel) => ({ + subspaceName: subSpaceModel.subspaceName, + space, + subSpaceModel, + })); + + await this.createSubspaces(subspaceData, queryRunner); + } + + async createSubspacesFromNames( + addSubspaceDtos: AddSubspaceDto[], + space: SpaceEntity, + queryRunner: QueryRunner, + ): Promise { + const subspaceData = addSubspaceDtos.map((dto) => ({ + subspaceName: dto.subspaceName, + space, + })); + + return await this.createSubspaces(subspaceData, queryRunner); + } + async createSubspace( addSubspaceDto: AddSubspaceDto, params: GetSpaceParam, ): Promise { const { communityUuid, spaceUuid, projectUuid } = params; - const space = await this.spaceService.validateCommunityAndSpace( - communityUuid, - spaceUuid, - projectUuid, - ); + const space = + await this.validationService.validateSpaceWithinCommunityAndProject( + communityUuid, + projectUuid, + spaceUuid, + ); try { const newSubspace = this.subspaceRepository.create({ @@ -52,10 +110,10 @@ export class SubSpaceService { pageable: Partial, ): Promise { const { communityUuid, spaceUuid, projectUuid } = params; - await this.spaceService.validateCommunityAndSpace( + await this.validationService.validateSpaceWithinCommunityAndProject( communityUuid, - spaceUuid, projectUuid, + spaceUuid, ); try { @@ -76,10 +134,10 @@ export class SubSpaceService { async findOne(params: GetSubSpaceParam): Promise { const { communityUuid, subSpaceUuid, spaceUuid, projectUuid } = params; - await this.spaceService.validateCommunityAndSpace( + await this.validationService.validateSpaceWithinCommunityAndProject( communityUuid, - spaceUuid, projectUuid, + spaceUuid, ); try { const subSpace = await this.subspaceRepository.findOne({ @@ -116,12 +174,11 @@ export class SubSpaceService { updateSubSpaceDto: AddSubspaceDto, ): Promise { const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params; - await this.spaceService.validateCommunityAndSpace( + await this.validationService.validateSpaceWithinCommunityAndProject( communityUuid, - spaceUuid, projectUuid, + spaceUuid, ); - const subSpace = await this.subspaceRepository.findOne({ where: { uuid: subSpaceUuid }, }); @@ -156,10 +213,10 @@ export class SubSpaceService { async delete(params: GetSubSpaceParam): Promise { const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params; - await this.spaceService.validateCommunityAndSpace( + await this.validationService.validateSpaceWithinCommunityAndProject( communityUuid, - spaceUuid, projectUuid, + spaceUuid, ); const subspace = await this.subspaceRepository.findOne({ diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 002c2df..3a52f7b 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -43,9 +43,12 @@ import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories'; import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; +import { SpaceModelRepository } from '@app/common/modules/space-model'; +import { CommunityModule } from 'src/community/community.module'; +import { ValidationService } from './services/space-validation.service'; @Module({ - imports: [ConfigModule, SpaceRepositoryModule], + imports: [ConfigModule, SpaceRepositoryModule, CommunityModule], controllers: [ SpaceController, SpaceUserController, @@ -55,6 +58,7 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories'; SpaceSceneController, ], providers: [ + ValidationService, SpaceService, TuyaService, ProductRepository, @@ -81,6 +85,8 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories'; SpaceProductService, SpaceProductRepository, ProjectRepository, + SpaceModelRepository, + SubspaceRepository, ], exports: [SpaceService], }) From 72bebe3b06727581a41c6c46a6f308f9adf4234e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 12 Dec 2024 09:29:25 +0400 Subject: [PATCH 040/247] reduced duplication --- src/space/services/space-device.service.ts | 22 +++++++---------- src/space/services/space-scene.service.ts | 8 +++---- src/space/services/space-user.service.ts | 21 ++++++++-------- .../subspace/subspace-device.service.ts | 24 +++++++------------ 4 files changed, 32 insertions(+), 43 deletions(-) diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index 0a262a1..f905814 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -1,33 +1,29 @@ import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; -import { CommunityRepository } from '@app/common/modules/community/repositories'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { GetDeviceDetailsInterface } from 'src/device/interfaces/get.device.interface'; import { GetSpaceParam } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; -import { ProductRepository } from '@app/common/modules/product/repositories'; -import { SpaceService } from './space.service'; + +import { ValidationService } from './space-validation.service'; @Injectable() export class SpaceDeviceService { constructor( - private readonly spaceRepository: SpaceRepository, private readonly tuyaService: TuyaService, - private readonly productRepository: ProductRepository, - private readonly communityRepository: CommunityRepository, - private readonly spaceService: SpaceService, + private readonly validationService: ValidationService, ) {} async listDevicesInSpace(params: GetSpaceParam): Promise { const { spaceUuid, communityUuid, projectUuid } = params; try { - const space = await this.spaceService.validateCommunityAndSpace( - communityUuid, - spaceUuid, - projectUuid, - ); + const space = + await this.validationService.validateSpaceWithinCommunityAndProject( + communityUuid, + projectUuid, + spaceUuid, + ); const safeFetch = async (device: any) => { try { diff --git a/src/space/services/space-scene.service.ts b/src/space/services/space-scene.service.ts index 57d63a5..4e77158 100644 --- a/src/space/services/space-scene.service.ts +++ b/src/space/services/space-scene.service.ts @@ -1,16 +1,16 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { GetSpaceParam } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { SpaceService } from './space.service'; import { SceneService } from '../../scene/services'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { GetSceneDto } from '../../scene/dtos'; +import { ValidationService } from './space-validation.service'; @Injectable() export class SpaceSceneService { constructor( - private readonly spaceSevice: SpaceService, private readonly sceneSevice: SceneService, + private readonly validationService: ValidationService, ) {} async getScenes( @@ -20,10 +20,10 @@ export class SpaceSceneService { try { const { spaceUuid, communityUuid, projectUuid } = params; - await this.spaceSevice.validateCommunityAndSpace( + await this.validationService.validateSpaceWithinCommunityAndProject( communityUuid, - spaceUuid, projectUuid, + spaceUuid, ); const scenes = await this.sceneSevice.findScenesBySpace( diff --git a/src/space/services/space-user.service.ts b/src/space/services/space-user.service.ts index 0e10e1a..0912132 100644 --- a/src/space/services/space-user.service.ts +++ b/src/space/services/space-user.service.ts @@ -1,21 +1,19 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; import { UserRepository, UserSpaceRepository, } from '@app/common/modules/user/repositories'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { SpaceService } from './space.service'; import { UserSpaceParam } from '../dtos'; +import { ValidationService } from './space-validation.service'; @Injectable() export class SpaceUserService { constructor( - private readonly spaceRepository: SpaceRepository, + private readonly validationService: ValidationService, private readonly userRepository: UserRepository, private readonly userSpaceRepository: UserSpaceRepository, - private readonly spaceService: SpaceService, ) {} async associateUserToSpace(params: UserSpaceParam): Promise { const { communityUuid, spaceUuid, userUuid, projectUuid } = params; @@ -31,11 +29,12 @@ export class SpaceUserService { } // Find the space by ID - const space = await this.spaceService.validateCommunityAndSpace( - communityUuid, - spaceUuid, - projectUuid, - ); + const space = + await this.validationService.validateSpaceWithinCommunityAndProject( + communityUuid, + projectUuid, + spaceUuid, + ); // Check if the association already exists const existingAssociation = await this.userSpaceRepository.findOne({ @@ -73,10 +72,10 @@ export class SpaceUserService { } // Find the space by ID - await this.spaceService.validateCommunityAndSpace( + await this.validationService.validateSpaceWithinCommunityAndProject( communityUuid, - spaceUuid, projectUuid, + spaceUuid, ); // Find the existing association diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index e773ea4..2bcfb93 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -1,29 +1,23 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { DeviceRepository } from '@app/common/modules/device/repositories'; -import { - SpaceRepository, - SubspaceRepository, -} from '@app/common/modules/space/repositories'; +import { SubspaceRepository } from '@app/common/modules/space/repositories'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos'; -import { CommunityRepository } from '@app/common/modules/community/repositories'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { ProductRepository } from '@app/common/modules/product/repositories'; import { GetDeviceDetailsInterface } from '../../../device/interfaces/get.device.interface'; -import { SpaceService } from '../space.service'; +import { ValidationService } from '../space-validation.service'; @Injectable() export class SubspaceDeviceService { constructor( - private readonly spaceRepository: SpaceRepository, - private readonly communityRepository: CommunityRepository, private readonly subspaceRepository: SubspaceRepository, private readonly deviceRepository: DeviceRepository, private readonly tuyaService: TuyaService, private readonly productRepository: ProductRepository, - private readonly spaceService: SpaceService, + private readonly validationService: ValidationService, ) {} async listDevicesInSubspace( @@ -31,10 +25,10 @@ export class SubspaceDeviceService { ): Promise { const { subSpaceUuid, spaceUuid, communityUuid, projectUuid } = params; - await this.spaceService.validateCommunityAndSpace( + await this.validationService.validateSpaceWithinCommunityAndProject( communityUuid, - spaceUuid, projectUuid, + spaceUuid, ); const subspace = await this.findSubspaceWithDevices(subSpaceUuid); @@ -76,10 +70,10 @@ export class SubspaceDeviceService { const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid, projectUuid } = params; try { - await this.spaceService.validateCommunityAndSpace( + await this.validationService.validateSpaceWithinCommunityAndProject( communityUuid, - spaceUuid, projectUuid, + spaceUuid, ); const subspace = await this.findSubspace(subSpaceUuid); @@ -111,10 +105,10 @@ export class SubspaceDeviceService { const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid, projectUuid } = params; try { - await this.spaceService.validateCommunityAndSpace( + await this.validationService.validateSpaceWithinCommunityAndProject( communityUuid, - spaceUuid, projectUuid, + spaceUuid, ); const subspace = await this.findSubspace(subSpaceUuid); From b8590841a8efa148930417280a47b397f6a56de3 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 12 Dec 2024 14:37:44 +0400 Subject: [PATCH 041/247] aadded product item to space --- .../src/modules/space/entities/index.ts | 4 +- .../entities/space-product-item.entity.ts | 3 +- .../space/entities/space-product.entity.ts | 2 +- .../modules/space/entities/space.entity.ts | 2 +- .../modules/space/entities/subspace/index.ts | 1 + .../{ => subspace}/subspace.entity.ts | 10 +- libs/common/src/modules/space/index.ts | 4 + .../space/repositories/space.repository.ts | 14 +- .../space-product-item-model.service.ts | 7 - src/space/services/index.ts | 1 + .../services/space-product-items/index.ts | 1 + .../space-product-items.service.ts | 112 +++++++++ .../space-products/space-products.service.ts | 212 ++++++++++++------ .../services/space-validation.service.ts | 7 +- src/space/services/space.service.ts | 35 ++- .../services/subspace/subspace.service.ts | 11 +- src/space/space.module.ts | 4 + 17 files changed, 328 insertions(+), 102 deletions(-) create mode 100644 libs/common/src/modules/space/entities/subspace/index.ts rename libs/common/src/modules/space/entities/{ => subspace}/subspace.entity.ts (74%) create mode 100644 libs/common/src/modules/space/index.ts create mode 100644 src/space/services/space-product-items/index.ts create mode 100644 src/space/services/space-product-items/space-product-items.service.ts diff --git a/libs/common/src/modules/space/entities/index.ts b/libs/common/src/modules/space/entities/index.ts index d09bdac..f07ec93 100644 --- a/libs/common/src/modules/space/entities/index.ts +++ b/libs/common/src/modules/space/entities/index.ts @@ -1,5 +1,5 @@ export * from './space.entity'; -export * from './subspace.entity'; -export * from './space-product-item.entity'; +export * from './subspace'; export * from './space-product.entity'; +export * from './space-product-item.entity'; export * from './space-link.entity'; diff --git a/libs/common/src/modules/space/entities/space-product-item.entity.ts b/libs/common/src/modules/space/entities/space-product-item.entity.ts index 70e7c1f..c53dfd4 100644 --- a/libs/common/src/modules/space/entities/space-product-item.entity.ts +++ b/libs/common/src/modules/space/entities/space-product-item.entity.ts @@ -14,14 +14,13 @@ export class SpaceProductItemEntity extends AbstractEntity @ManyToOne(() => SpaceProductEntity, (spaceProduct) => spaceProduct.items, { nullable: false, }) - public spaceProducts: SpaceProductEntity; + public spaceProduct: SpaceProductEntity; @ManyToOne( () => SpaceProductItemModelEntity, (spaceProductItemModel) => spaceProductItemModel.items, { nullable: true, - onDelete: 'SET NULL', }, ) public spaceProductItemModel?: SpaceProductItemModelEntity; diff --git a/libs/common/src/modules/space/entities/space-product.entity.ts b/libs/common/src/modules/space/entities/space-product.entity.ts index 8fa0c4f..5f8a062 100644 --- a/libs/common/src/modules/space/entities/space-product.entity.ts +++ b/libs/common/src/modules/space/entities/space-product.entity.ts @@ -27,7 +27,7 @@ export class SpaceProductEntity extends AbstractEntity { }) productCount: number; - @OneToMany(() => SpaceProductItemEntity, (item) => item.spaceProducts, { + @OneToMany(() => SpaceProductItemEntity, (item) => item.spaceProduct, { cascade: true, }) public items: SpaceProductItemEntity[]; diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 9d6e6fd..1b972f6 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -11,7 +11,7 @@ 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'; +import { SubspaceEntity } from './subspace'; import { SpaceLinkEntity } from './space-link.entity'; import { SpaceProductEntity } from './space-product.entity'; import { SceneEntity } from '../../scene/entities'; diff --git a/libs/common/src/modules/space/entities/subspace/index.ts b/libs/common/src/modules/space/entities/subspace/index.ts new file mode 100644 index 0000000..be13961 --- /dev/null +++ b/libs/common/src/modules/space/entities/subspace/index.ts @@ -0,0 +1 @@ +export * from './subspace.entity'; diff --git a/libs/common/src/modules/space/entities/subspace.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace.entity.ts similarity index 74% rename from libs/common/src/modules/space/entities/subspace.entity.ts rename to libs/common/src/modules/space/entities/subspace/subspace.entity.ts index ccb5118..8f0d15d 100644 --- a/libs/common/src/modules/space/entities/subspace.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace.entity.ts @@ -1,9 +1,9 @@ +import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; +import { DeviceEntity } from '@app/common/modules/device/entities'; +import { SubspaceModelEntity } from '@app/common/modules/space-model'; import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { DeviceEntity } from '../../device/entities'; -import { SpaceEntity } from './space.entity'; -import { SubspaceDto } from '../dtos'; -import { SubspaceModelEntity } from '../../space-model'; +import { SubspaceDto } from '../../dtos'; +import { SpaceEntity } from '../space.entity'; @Entity({ name: 'subspace' }) export class SubspaceEntity extends AbstractEntity { diff --git a/libs/common/src/modules/space/index.ts b/libs/common/src/modules/space/index.ts new file mode 100644 index 0000000..b797801 --- /dev/null +++ b/libs/common/src/modules/space/index.ts @@ -0,0 +1,4 @@ +export * from './dtos'; +export * from './entities'; +export * from './repositories'; +export * from './space.repository.module'; diff --git a/libs/common/src/modules/space/repositories/space.repository.ts b/libs/common/src/modules/space/repositories/space.repository.ts index 43ce45e..677db0e 100644 --- a/libs/common/src/modules/space/repositories/space.repository.ts +++ b/libs/common/src/modules/space/repositories/space.repository.ts @@ -1,7 +1,12 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; import { SpaceProductEntity } from '../entities/space-product.entity'; -import { SpaceEntity, SpaceLinkEntity, SubspaceEntity } from '../entities'; +import { + SpaceEntity, + SpaceLinkEntity, + SpaceProductItemEntity, + SubspaceEntity, +} from '../entities'; @Injectable() export class SpaceRepository extends Repository { @@ -28,3 +33,10 @@ export class SpaceProductRepository extends Repository { super(SpaceProductEntity, dataSource.createEntityManager()); } } + +@Injectable() +export class SpaceProductItemRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SpaceProductItemEntity, dataSource.createEntityManager()); + } +} diff --git a/src/space-model/services/space-product-item-model.service.ts b/src/space-model/services/space-product-item-model.service.ts index ac9c0d8..9f40efa 100644 --- a/src/space-model/services/space-product-item-model.service.ts +++ b/src/space-model/services/space-product-item-model.service.ts @@ -20,13 +20,6 @@ export class SpaceProductItemModelService { queryRunner: QueryRunner, ) { await this.validateTags(itemModelDtos, queryRunner, spaceModel); - - if (!spaceProductModel) { - throw new HttpException( - 'Space product model is required to create product items.', - HttpStatus.BAD_REQUEST, - ); - } try { const productItems = itemModelDtos.map((dto) => queryRunner.manager.create(this.spaceProductItemRepository.target, { diff --git a/src/space/services/index.ts b/src/space/services/index.ts index 79eb32f..6a3beeb 100644 --- a/src/space/services/index.ts +++ b/src/space/services/index.ts @@ -5,3 +5,4 @@ export * from './subspace'; export * from './space-link'; export * from './space-scene.service'; export * from './space-products'; +export * from './space-product-items'; diff --git a/src/space/services/space-product-items/index.ts b/src/space/services/space-product-items/index.ts new file mode 100644 index 0000000..fff8634 --- /dev/null +++ b/src/space/services/space-product-items/index.ts @@ -0,0 +1 @@ +export * from './space-product-items.service'; diff --git a/src/space/services/space-product-items/space-product-items.service.ts b/src/space/services/space-product-items/space-product-items.service.ts new file mode 100644 index 0000000..4dbe0ff --- /dev/null +++ b/src/space/services/space-product-items/space-product-items.service.ts @@ -0,0 +1,112 @@ +import { + SpaceEntity, + SpaceProductEntity, + SpaceProductItemRepository, +} from '@app/common/modules/space'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { CreateSpaceProductItemDto } from '../../dtos'; +import { QueryRunner } from 'typeorm'; +import { SpaceProductModelEntity } from '@app/common/modules/space-model'; + +@Injectable() +export class SpaceProductItemService { + constructor( + private readonly spaceProductItemRepository: SpaceProductItemRepository, + ) {} + + async createProductItem( + itemModelDtos: CreateSpaceProductItemDto[], + spaceProduct: SpaceProductEntity, + space: SpaceEntity, + queryRunner: QueryRunner, + ) { + await this.validateTags(itemModelDtos, queryRunner, space); + + try { + const productItems = itemModelDtos.map((dto) => + queryRunner.manager.create(this.spaceProductItemRepository.target, { + tag: dto.tag, + spaceProduct, + }), + ); + + await queryRunner.manager.save(productItems); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + error.message || + 'An unexpected error occurred while creating product items.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async createSpaceProductItemFromModel( + spaceProduct: SpaceProductEntity, + spaceProductModel: SpaceProductModelEntity, + queryRunner: QueryRunner, + ) { + const spaceProductItemModels = spaceProductModel.items; + + try { + const productItems = spaceProductItemModels.map((model) => + queryRunner.manager.create(this.spaceProductItemRepository.target, { + tag: model.tag, + spaceProduct, + }), + ); + + await queryRunner.manager.save(productItems); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + error.message || + 'An unexpected error occurred while creating product items.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private async validateTags( + itemModelDtos: CreateSpaceProductItemDto[], + queryRunner: QueryRunner, + space: SpaceEntity, + ) { + const incomingTags = itemModelDtos.map((item) => item.tag); + + const duplicateTags = incomingTags.filter( + (tag, index) => incomingTags.indexOf(tag) !== index, + ); + if (duplicateTags.length > 0) { + throw new HttpException( + `Duplicate tags found in the request: ${[...new Set(duplicateTags)].join(', ')}`, + HttpStatus.BAD_REQUEST, + ); + } + + const existingTags = await queryRunner.manager.find( + this.spaceProductItemRepository.target, + { + where: { spaceProduct: { space } }, + select: ['tag'], + }, + ); + const existingTagSet = new Set(existingTags.map((item) => item.tag)); + + const conflictingTags = incomingTags.filter((tag) => + existingTagSet.has(tag), + ); + if (conflictingTags.length > 0) { + throw new HttpException( + `Tags already exist in the model: ${conflictingTags.join(', ')}`, + HttpStatus.CONFLICT, + ); + } + } +} diff --git a/src/space/services/space-products/space-products.service.ts b/src/space/services/space-products/space-products.service.ts index 5b218e4..895053f 100644 --- a/src/space/services/space-products/space-products.service.ts +++ b/src/space/services/space-products/space-products.service.ts @@ -2,96 +2,98 @@ 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'; +import { In, QueryRunner } from 'typeorm'; +import { ProductAssignmentDto } from '../../dtos'; +import { SpaceProductItemService } from '../space-product-items'; +import { SpaceModelEntity } from '@app/common/modules/space-model'; @Injectable() export class SpaceProductService { constructor( private readonly productRepository: ProductRepository, - private readonly spaceProductRepository: SpaceProductRepository, + private readonly spaceProductItemService: SpaceProductItemService, ) {} + async createProductItemFromModel( + spaceModel: SpaceModelEntity, + space: SpaceEntity, + queryRunner: QueryRunner, + ) { + const spaceProductModels = spaceModel.spaceProductModels; + if (!spaceProductModels?.length) return; + const newSpaceProducts = []; + + spaceProductModels.map((spaceProductModel) => { + newSpaceProducts.push( + queryRunner.manager.create(SpaceProductEntity, { + space: space, + product: spaceProductModel.product, + productCount: spaceProductModel.productCount, + spaceProductModel: spaceProductModel, + }), + ); + }); + if (newSpaceProducts.length > 0) { + await queryRunner.manager.save(SpaceProductEntity, newSpaceProducts); + await Promise.all( + newSpaceProducts.map((spaceProduct, index) => { + const spaceProductModel = spaceProductModels[index]; + return this.spaceProductItemService.createSpaceProductItemFromModel( + spaceProduct, + spaceProductModel, + queryRunner, + ); + }), + ); + } + } + async assignProductsToSpace( space: SpaceEntity, - products: { productId: string; count: number }[], + products: ProductAssignmentDto[], + queryRunner: QueryRunner, ): Promise { + let updatedProducts: SpaceProductEntity[] = []; + try { const uniqueProducts = this.validateUniqueProducts(products); const productEntities = await this.getProductEntities(uniqueProducts); + const existingSpaceProducts = await this.getExistingSpaceProducts( + space, + queryRunner, + ); - // 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 (existingSpaceProducts) { + updatedProducts = await this.updateExistingProducts( + existingSpaceProducts, + uniqueProducts, + productEntities, + queryRunner, ); - - 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); - } + const newProducts = await this.createNewProducts( + uniqueProducts, + productEntities, + space, + queryRunner, + ); 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', + `An error occurred while assigning products to the space ${error}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } - throw error; } } private validateUniqueProducts( - products: { productId: string; count: number }[], - ): { productId: string; count: number }[] { + products: ProductAssignmentDto[], + ): ProductAssignmentDto[] { const productIds = new Set(); const uniqueProducts = []; @@ -110,15 +112,13 @@ export class SpaceProductService { } private async getProductEntities( - products: { productId: string; count: number }[], + products: ProductAssignmentDto[], ): Promise> { 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); @@ -128,4 +128,90 @@ export class SpaceProductService { ); } } + + private async getExistingSpaceProducts( + space: SpaceEntity, + queryRunner: QueryRunner, + ): Promise { + return queryRunner.manager.find(SpaceProductEntity, { + where: { space: { uuid: space.uuid } }, + relations: ['product'], + }); + } + + private async updateExistingProducts( + existingSpaceProducts: SpaceProductEntity[], + uniqueProducts: ProductAssignmentDto[], + productEntities: Map, + queryRunner: QueryRunner, + ): Promise { + const updatedProducts = []; + + for (const { productId, count } of uniqueProducts) { + productEntities.get(productId); + const existingProduct = existingSpaceProducts.find( + (spaceProduct) => spaceProduct.product.uuid === productId, + ); + + if (existingProduct && existingProduct.productCount !== count) { + existingProduct.productCount = count; + updatedProducts.push(existingProduct); + } + } + + if (updatedProducts.length > 0) { + await queryRunner.manager.save(SpaceProductEntity, updatedProducts); + } + + return updatedProducts; + } + + private async createNewProducts( + uniqueSpaceProducts: ProductAssignmentDto[], + productEntities: Map, + space: SpaceEntity, + queryRunner: QueryRunner, + ): Promise { + const newProducts = []; + + for (const uniqueSpaceProduct of uniqueSpaceProducts) { + const product = productEntities.get(uniqueSpaceProduct.productId); + this.validateProductCount(uniqueSpaceProduct); + + newProducts.push( + queryRunner.manager.create(SpaceProductEntity, { + space, + product, + productCount: uniqueSpaceProduct.count, + }), + ); + } + if (newProducts.length > 0) { + await queryRunner.manager.save(SpaceProductEntity, newProducts); + + await Promise.all( + uniqueSpaceProducts.map((dto, index) => { + const spaceProduct = newProducts[index]; + return this.spaceProductItemService.createProductItem( + dto.items, + spaceProduct, + space, + queryRunner, + ); + }), + ); + } + + return newProducts; + } + + private validateProductCount(dto: ProductAssignmentDto) { + const productItemCount = dto.items.length; + if (dto.count !== productItemCount) { + throw new HttpException( + `Product count (${dto.count}) does not match the number of items (${productItemCount}) for product ID ${dto.productId}.`, + HttpStatus.BAD_REQUEST, + ); + } + } } diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts index 56e6532..cd80387 100644 --- a/src/space/services/space-validation.service.ts +++ b/src/space/services/space-validation.service.ts @@ -59,7 +59,12 @@ export class ValidationService { async validateSpaceModel(spaceModelUuid: string): Promise { const spaceModel = await this.spaceModelRepository.findOne({ where: { uuid: spaceModelUuid }, - relations: ['subspaceModels'], + relations: [ + 'subspaceModels', + 'spaceProductModels', + 'spaceProductModels.product', + 'spaceProductModels.items', + ], }); if (!spaceModel) { diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 135f2e5..5481d19 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -63,7 +63,7 @@ export class SpaceService { : null; try { - const newSpace = this.spaceRepository.create({ + const newSpace = queryRunner.manager.create(SpaceEntity, { ...addSpaceDto, spaceModel, parent: parentUuid ? parent : null, @@ -80,13 +80,13 @@ export class SpaceService { ); } - if (subspaces) { + if (subspaces?.length) { await this.subSpaceService.createSubspacesFromNames( subspaces, newSpace, queryRunner, ); - } else { + } else if (spaceModel && spaceModel.subspaceModels.length) { await this.subSpaceService.createSubSpaceFromModel( spaceModel, newSpace, @@ -98,6 +98,13 @@ export class SpaceService { await this.spaceProductService.assignProductsToSpace( newSpace, products, + queryRunner, + ); + } else if (spaceModel && spaceModel.spaceProductModels.length) { + await this.spaceProductService.createProductItemFromModel( + spaceModel, + newSpace, + queryRunner, ); } await queryRunner.commitTransaction(); @@ -216,7 +223,12 @@ export class SpaceService { updateSpaceDto: UpdateSpaceDto, ): Promise { const { communityUuid, spaceUuid, projectUuid } = params; + const queryRunner = this.dataSource.createQueryRunner(); + try { + await queryRunner.connect(); + await queryRunner.startTransaction(); + const space = await this.validationService.validateSpaceWithinCommunityAndProject( communityUuid, @@ -234,14 +246,16 @@ export class SpaceService { Object.assign(space, updateSpaceDto, { parent }); // Save the updated space - const updatedSpace = await this.spaceRepository.save(space); + const updatedSpace = await queryRunner.manager.save(space); if (products && products.length > 0) { await this.spaceProductService.assignProductsToSpace( updatedSpace, products, + queryRunner, ); } + await queryRunner.commitTransaction(); return new SuccessResponseDto({ message: `Space with ID ${spaceUuid} successfully updated`, @@ -249,6 +263,8 @@ export class SpaceService { statusCode: HttpStatus.OK, }); } catch (error) { + await queryRunner.rollbackTransaction(); + if (error instanceof HttpException) { throw error; } @@ -256,6 +272,8 @@ export class SpaceService { 'An error occurred while updating the space', HttpStatus.INTERNAL_SERVER_ERROR, ); + } finally { + await queryRunner.release(); } } @@ -348,13 +366,6 @@ export class SpaceService { return rootSpaces; } - private throwNotFound(entity: string, uuid: string) { - throw new HttpException( - `${entity} with ID ${uuid} not found`, - HttpStatus.NOT_FOUND, - ); - } - private validateSpaceCreation( spaceModelUuid?: string, products?: ProductAssignmentDto[], @@ -362,7 +373,7 @@ export class SpaceService { ) { if (spaceModelUuid && (products?.length || subSpaces?.length)) { throw new HttpException( - 'Space model cannot be assigned with products or subspaces.', + 'For space creation choose either space model or products and subspace', HttpStatus.CONFLICT, ); } diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index dae280b..4e9b2e3 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -49,9 +49,7 @@ export class SubSpaceService { ): Promise { const subSpaces = spaceModel.subspaceModels; - if (!subSpaces || subSpaces.length === 0) { - return; - } + if (!subSpaces?.length) return; const subspaceData = subSpaces.map((subSpaceModel) => ({ subspaceName: subSpaceModel.subspaceName, @@ -79,12 +77,11 @@ export class SubSpaceService { addSubspaceDto: AddSubspaceDto, params: GetSpaceParam, ): Promise { - const { communityUuid, spaceUuid, projectUuid } = params; const space = await this.validationService.validateSpaceWithinCommunityAndProject( - communityUuid, - projectUuid, - spaceUuid, + params.projectUuid, + params.projectUuid, + params.spaceUuid, ); try { diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 3a52f7b..40a72d3 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -12,6 +12,7 @@ import { import { SpaceDeviceService, SpaceLinkService, + SpaceProductItemService, SpaceProductService, SpaceSceneService, SpaceService, @@ -24,6 +25,7 @@ import { SpaceRepository, SubspaceRepository, SpaceLinkRepository, + SpaceProductItemRepository, } from '@app/common/modules/space/repositories'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { @@ -87,6 +89,8 @@ import { ValidationService } from './services/space-validation.service'; ProjectRepository, SpaceModelRepository, SubspaceRepository, + SpaceProductItemService, + SpaceProductItemRepository, ], exports: [SpaceService], }) From 2e95dbfd9ff1c4a4904380f978f04a19b05502f6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 12 Dec 2024 14:42:24 +0400 Subject: [PATCH 042/247] folder restructure --- libs/common/src/modules/space-model/entities/index.ts | 2 +- .../modules/space-model/entities/space-model.entity.ts | 2 +- .../modules/space-model/entities/subspace-model/index.ts | 1 + .../{ => subspace-model}/subspace-model.entity.ts | 8 ++++---- .../entities/subspace/subspace-product-item.entity.ts | 0 .../space/entities/subspace/subspace-product.entity.ts | 0 6 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 libs/common/src/modules/space-model/entities/subspace-model/index.ts rename libs/common/src/modules/space-model/entities/{ => subspace-model}/subspace-model.entity.ts (73%) create mode 100644 libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts create mode 100644 libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts diff --git a/libs/common/src/modules/space-model/entities/index.ts b/libs/common/src/modules/space-model/entities/index.ts index f1e4016..ec19308 100644 --- a/libs/common/src/modules/space-model/entities/index.ts +++ b/libs/common/src/modules/space-model/entities/index.ts @@ -1,4 +1,4 @@ export * from './space-model.entity'; export * from './space-product-item-model.entity'; export * from './space-product-model.entity'; -export * from './subspace-model.entity'; \ No newline at end of file +export * from './subspace-model'; diff --git a/libs/common/src/modules/space-model/entities/space-model.entity.ts b/libs/common/src/modules/space-model/entities/space-model.entity.ts index b67c642..c340b8b 100644 --- a/libs/common/src/modules/space-model/entities/space-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-model.entity.ts @@ -8,7 +8,7 @@ import { } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceModelDto } from '../dtos'; -import { SubspaceModelEntity } from './subspace-model.entity'; +import { SubspaceModelEntity } from './subspace-model'; import { SpaceProductModelEntity } from './space-product-model.entity'; import { ProjectEntity } from '../../project/entities'; import { SpaceEntity } from '../../space/entities'; diff --git a/libs/common/src/modules/space-model/entities/subspace-model/index.ts b/libs/common/src/modules/space-model/entities/subspace-model/index.ts new file mode 100644 index 0000000..cd29d67 --- /dev/null +++ b/libs/common/src/modules/space-model/entities/subspace-model/index.ts @@ -0,0 +1 @@ +export * from './subspace-model.entity'; diff --git a/libs/common/src/modules/space-model/entities/subspace-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts similarity index 73% rename from libs/common/src/modules/space-model/entities/subspace-model.entity.ts rename to libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts index c1e335a..a8a3595 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts @@ -1,8 +1,8 @@ +import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { SpaceModelEntity } from './space-model.entity'; -import { SubSpaceModelDto } from '../dtos'; -import { SubspaceEntity } from '../../space/entities'; +import { SubSpaceModelDto } from '../../dtos'; +import { SpaceModelEntity } from '../space-model.entity'; +import { SubspaceEntity } from '@app/common/modules/space/entities'; @Entity({ name: 'subspace-model' }) @Unique(['subspaceName', 'spaceModel']) diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts new file mode 100644 index 0000000..e69de29 diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts new file mode 100644 index 0000000..e69de29 From ff7c02d5848c8160388fddccc9199429fe165074 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 12 Dec 2024 16:14:35 +0400 Subject: [PATCH 043/247] folder restructure --- libs/common/src/modules/space-model/dtos/index.ts | 2 +- .../common/src/modules/space-model/dtos/subspace-model/index.ts | 1 + .../space-model/dtos/{ => subspace-model}/subspace-model.dto.ts | 0 3 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 libs/common/src/modules/space-model/dtos/subspace-model/index.ts rename libs/common/src/modules/space-model/dtos/{ => subspace-model}/subspace-model.dto.ts (100%) diff --git a/libs/common/src/modules/space-model/dtos/index.ts b/libs/common/src/modules/space-model/dtos/index.ts index 3923af4..9ba8130 100644 --- a/libs/common/src/modules/space-model/dtos/index.ts +++ b/libs/common/src/modules/space-model/dtos/index.ts @@ -1,4 +1,4 @@ -export * from './subspace-model.dto'; +export * from './subspace-model'; export * from './space-model.dto'; export * from './space-product-item-model.dto'; export * from './space-product-model.dto'; diff --git a/libs/common/src/modules/space-model/dtos/subspace-model/index.ts b/libs/common/src/modules/space-model/dtos/subspace-model/index.ts new file mode 100644 index 0000000..05a7548 --- /dev/null +++ b/libs/common/src/modules/space-model/dtos/subspace-model/index.ts @@ -0,0 +1 @@ +export * from './subspace-model.dto' \ No newline at end of file diff --git a/libs/common/src/modules/space-model/dtos/subspace-model.dto.ts b/libs/common/src/modules/space-model/dtos/subspace-model/subspace-model.dto.ts similarity index 100% rename from libs/common/src/modules/space-model/dtos/subspace-model.dto.ts rename to libs/common/src/modules/space-model/dtos/subspace-model/subspace-model.dto.ts From 8d0001cb95f9b5a0d2dfe8f4787d0e05fb862c7f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 12 Dec 2024 16:19:37 +0400 Subject: [PATCH 044/247] added subspace model remaining dtos --- .../subspace-product-item-model.dto.ts | 15 ++++++++++++ .../subspace-product-model.dto.ts | 23 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-item-model.dto.ts create mode 100644 libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-model.dto.ts diff --git a/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-item-model.dto.ts b/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-item-model.dto.ts new file mode 100644 index 0000000..479642b --- /dev/null +++ b/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-item-model.dto.ts @@ -0,0 +1,15 @@ +import { IsString, IsNotEmpty } from 'class-validator'; + +export class SubspaceProductItemModelDto { + @IsString() + @IsNotEmpty() + uuid: string; + + @IsString() + @IsNotEmpty() + tag: string; + + @IsString() + @IsNotEmpty() + subspaceProductModelUuid: string; +} diff --git a/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-model.dto.ts b/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-model.dto.ts new file mode 100644 index 0000000..4eebaae --- /dev/null +++ b/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-model.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; +import { SubspaceProductItemModelDto } from './subspace-product-item-model.dto'; + +export class SubpaceProductModelDto { + @IsString() + @IsNotEmpty() + uuid: string; + + @IsNumber() + @IsNotEmpty() + productCount: number; + + @IsString() + @IsNotEmpty() + productUuid: string; + + @ApiProperty({ + description: 'List of individual items with specific names for the product', + type: [SubspaceProductItemModelDto], + }) + items: SubspaceProductItemModelDto[]; +} From e198c081cb3e159522762ab40c0f5c60366244c9 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 12 Dec 2024 16:19:57 +0400 Subject: [PATCH 045/247] indexing --- .../src/modules/space-model/dtos/subspace-model/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/common/src/modules/space-model/dtos/subspace-model/index.ts b/libs/common/src/modules/space-model/dtos/subspace-model/index.ts index 05a7548..70337b6 100644 --- a/libs/common/src/modules/space-model/dtos/subspace-model/index.ts +++ b/libs/common/src/modules/space-model/dtos/subspace-model/index.ts @@ -1 +1,3 @@ -export * from './subspace-model.dto' \ No newline at end of file +export * from './subspace-model.dto'; +export * from './subspace-product-item-model.dto'; +export * from './subspace-product-model.dto'; From 9fdd559b9fbea538863fd2f00ccd0ce99bd5a13c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 12 Dec 2024 16:37:49 +0400 Subject: [PATCH 046/247] added subspace model product and item entities --- libs/common/src/database/database.module.ts | 4 ++ .../product/entities/product.entity.ts | 7 ++++ .../entities/space-model.entity.ts | 1 - .../entities/subspace-model/index.ts | 2 + .../subspace-model/subspace-model.entity.ts | 10 +++++ .../subspace-product-item-model.entity.ts | 21 ++++++++++ .../subspace-product-model.entity.ts | 38 +++++++++++++++++++ 7 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts create mode 100644 libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 90b287a..3681bed 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -34,6 +34,8 @@ import { SpaceProductItemModelEntity, SpaceProductModelEntity, SubspaceModelEntity, + SubspaceProductItemModelEntity, + SubspaceProductModelEntity, } from '../modules/space-model/entities'; @Module({ imports: [ @@ -81,6 +83,8 @@ import { SubspaceModelEntity, SpaceProductEntity, SpaceProductItemEntity, + SubspaceProductModelEntity, + SubspaceProductItemModelEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), diff --git a/libs/common/src/modules/product/entities/product.entity.ts b/libs/common/src/modules/product/entities/product.entity.ts index 7b25470..926d179 100644 --- a/libs/common/src/modules/product/entities/product.entity.ts +++ b/libs/common/src/modules/product/entities/product.entity.ts @@ -4,6 +4,7 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceEntity } from '../../device/entities'; import { SpaceProductEntity } from '../../space/entities/space-product.entity'; import { SpaceProductModelEntity } from '../../space-model/entities'; +import { SubspaceProductModelEntity } from '../../space-model/entities/subspace-model/subspace-product-model.entity'; @Entity({ name: 'product' }) export class ProductEntity extends AbstractEntity { @@ -37,6 +38,12 @@ export class ProductEntity extends AbstractEntity { ) spaceProductModels: SpaceProductModelEntity[]; + @OneToMany( + () => SubspaceProductModelEntity, + (subspaceProductModel) => subspaceProductModel.product, + ) + subpaceProductModels: SubspaceProductModelEntity[]; + @OneToMany( () => DeviceEntity, (devicesProductEntity) => devicesProductEntity.productDevice, diff --git a/libs/common/src/modules/space-model/entities/space-model.entity.ts b/libs/common/src/modules/space-model/entities/space-model.entity.ts index c340b8b..591d04f 100644 --- a/libs/common/src/modules/space-model/entities/space-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-model.entity.ts @@ -49,7 +49,6 @@ export class SpaceModelEntity extends AbstractEntity { () => SpaceProductModelEntity, (productModel) => productModel.spaceModel, { - cascade: true, nullable: true, }, ) diff --git a/libs/common/src/modules/space-model/entities/subspace-model/index.ts b/libs/common/src/modules/space-model/entities/subspace-model/index.ts index cd29d67..e39403f 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model/index.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/index.ts @@ -1 +1,3 @@ export * from './subspace-model.entity'; +export * from './subspace-product-item-model.entity'; +export * from './subspace-product-model.entity'; diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts index a8a3595..2d1a170 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts @@ -3,6 +3,7 @@ import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; import { SubSpaceModelDto } from '../../dtos'; import { SpaceModelEntity } from '../space-model.entity'; import { SubspaceEntity } from '@app/common/modules/space/entities'; +import { SubspaceProductModelEntity } from './subspace-product-model.entity'; @Entity({ name: 'subspace-model' }) @Unique(['subspaceName', 'spaceModel']) @@ -33,4 +34,13 @@ export class SubspaceModelEntity extends AbstractEntity { cascade: true, }) public spaces: SubspaceEntity[]; + + @OneToMany( + () => SubspaceProductModelEntity, + (productModel) => productModel.subspaceModel, + { + nullable: true, + }, + ) + public subspaceProductModels: SubspaceProductModelEntity[]; } diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts new file mode 100644 index 0000000..decd0a7 --- /dev/null +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts @@ -0,0 +1,21 @@ +import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; +import { Entity, Column, ManyToOne } from 'typeorm'; +import { SubspaceProductItemModelDto } from '../../dtos'; +import { SubspaceProductModelEntity } from './subspace-product-model.entity'; + +@Entity({ name: 'subspace-product-item-model' }) +export class SubspaceProductItemModelEntity extends AbstractEntity { + @Column({ + nullable: false, + }) + public tag: string; + + @ManyToOne( + () => SubspaceProductModelEntity, + (subspaceProductModel) => subspaceProductModel.items, + { + nullable: false, + }, + ) + public subspaceProductModel: SubspaceProductModelEntity; +} diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts new file mode 100644 index 0000000..3a5ab1f --- /dev/null +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts @@ -0,0 +1,38 @@ +import { Entity, Column, ManyToOne, OneToMany } from 'typeorm'; +import { SubpaceProductModelDto } from '../../dtos'; +import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; +import { SubspaceModelEntity } from './subspace-model.entity'; +import { ProductEntity } from '@app/common/modules/product/entities'; +import { SubspaceProductItemModelEntity } from './subspace-product-item-model.entity'; + +@Entity({ name: 'subspace-product-model' }) +export class SubspaceProductModelEntity extends AbstractEntity { + @Column({ + nullable: false, + type: 'int', + }) + productCount: number; + + @ManyToOne( + () => SubspaceModelEntity, + (spaceModel) => spaceModel.subspaceProductModels, + { + nullable: false, + }, + ) + public subspaceModel: SubspaceModelEntity; + + @ManyToOne(() => ProductEntity, (product) => product.subpaceProductModels, { + nullable: false, + }) + public product: ProductEntity; + + @OneToMany( + () => SubspaceProductItemModelEntity, + (item) => item.subspaceProductModel, + { + cascade: true, + }, + ) + public items: SubspaceProductItemModelEntity[]; +} From 1b9b79ba6b567a8c9ae4fce1ab937b5917b4a208 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 13 Dec 2024 09:48:33 +0400 Subject: [PATCH 047/247] renamed dtos --- .../dtos/create-space-product-item-model.dto.ts | 2 +- src/space-model/dtos/create-space-product-model.dto.ts | 8 ++++---- .../services/space-product-item-model.service.ts | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/space-model/dtos/create-space-product-item-model.dto.ts b/src/space-model/dtos/create-space-product-item-model.dto.ts index 3825219..e6a7e76 100644 --- a/src/space-model/dtos/create-space-product-item-model.dto.ts +++ b/src/space-model/dtos/create-space-product-item-model.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; -export class CreateSpaceProductItemModelDto { +export class CreateProductItemModelDto { @ApiProperty({ description: 'Specific name for the product item', example: 'Light 1', diff --git a/src/space-model/dtos/create-space-product-model.dto.ts b/src/space-model/dtos/create-space-product-model.dto.ts index 9f251be..a4379a1 100644 --- a/src/space-model/dtos/create-space-product-model.dto.ts +++ b/src/space-model/dtos/create-space-product-model.dto.ts @@ -8,7 +8,7 @@ import { ArrayNotEmpty, } from 'class-validator'; import { Type } from 'class-transformer'; -import { CreateSpaceProductItemModelDto } from './create-space-product-item-model.dto'; +import { CreateProductItemModelDto } from './create-space-product-item-model.dto'; export class CreateSpaceProductModelDto { @ApiProperty({ @@ -29,11 +29,11 @@ export class CreateSpaceProductModelDto { @ApiProperty({ description: 'Specific names for each product item', - type: [CreateSpaceProductItemModelDto], + type: [CreateProductItemModelDto], }) @IsArray() @ArrayNotEmpty() @ValidateNested({ each: true }) - @Type(() => CreateSpaceProductItemModelDto) - items: CreateSpaceProductItemModelDto[]; + @Type(() => CreateProductItemModelDto) + items: CreateProductItemModelDto[]; } diff --git a/src/space-model/services/space-product-item-model.service.ts b/src/space-model/services/space-product-item-model.service.ts index 9f40efa..f7f8b04 100644 --- a/src/space-model/services/space-product-item-model.service.ts +++ b/src/space-model/services/space-product-item-model.service.ts @@ -4,7 +4,7 @@ import { SpaceProductModelEntity, } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { CreateSpaceProductItemModelDto } from '../dtos'; +import { CreateProductItemModelDto } from '../dtos'; import { QueryRunner } from 'typeorm'; @Injectable() @@ -14,7 +14,7 @@ export class SpaceProductItemModelService { ) {} async createProdutItemModel( - itemModelDtos: CreateSpaceProductItemModelDto[], + itemModelDtos: CreateProductItemModelDto[], spaceProductModel: SpaceProductModelEntity, spaceModel: SpaceModelEntity, queryRunner: QueryRunner, @@ -43,7 +43,7 @@ export class SpaceProductItemModelService { } private async validateTags( - itemModelDtos: CreateSpaceProductItemModelDto[], + itemModelDtos: CreateProductItemModelDto[], queryRunner: QueryRunner, spaceModel: SpaceModelEntity, ) { From 5faeba0869e98a71953a34ad21b87f673a364fa1 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 13 Dec 2024 09:56:43 +0400 Subject: [PATCH 048/247] common module import --- libs/common/src/common.module.ts | 27 +++++++++++++++++++ .../repositories/space-model.repository.ts | 16 +++++++++++ 2 files changed, 43 insertions(+) diff --git a/libs/common/src/common.module.ts b/libs/common/src/common.module.ts index de0780a..9a3de9c 100644 --- a/libs/common/src/common.module.ts +++ b/libs/common/src/common.module.ts @@ -9,6 +9,18 @@ import { EmailService } from './util/email.service'; import { ErrorMessageService } from 'src/error-message/error-message.service'; import { TuyaService } from './integrations/tuya/services/tuya.service'; import { SceneDeviceRepository } from './modules/scene-device/repositories'; +import { + SpaceProductItemRepository, + SpaceRepository, + SubspaceRepository, +} from './modules/space'; +import { + SpaceModelRepository, + SpaceProductModelRepository, + SubspaceModelRepository, + SubspaceProductItemModelRepository, + SubspaceProductModelRepository, +} from './modules/space-model'; @Module({ providers: [ CommonService, @@ -16,6 +28,14 @@ import { SceneDeviceRepository } from './modules/scene-device/repositories'; ErrorMessageService, TuyaService, SceneDeviceRepository, + SpaceRepository, + SubspaceRepository, + SubspaceModelRepository, + SubspaceProductModelRepository, + SubspaceProductItemModelRepository, + SpaceModelRepository, + SpaceProductModelRepository, + SpaceProductItemRepository, ], exports: [ CommonService, @@ -25,6 +45,13 @@ import { SceneDeviceRepository } from './modules/scene-device/repositories'; EmailService, ErrorMessageService, SceneDeviceRepository, + SpaceRepository, + SubspaceRepository, + SubspaceModelRepository, + SubspaceProductModelRepository, + SubspaceProductItemModelRepository, + SpaceModelRepository, + SpaceProductModelRepository, ], imports: [ ConfigModule.forRoot({ diff --git a/libs/common/src/modules/space-model/repositories/space-model.repository.ts b/libs/common/src/modules/space-model/repositories/space-model.repository.ts index 0e37479..fc92f14 100644 --- a/libs/common/src/modules/space-model/repositories/space-model.repository.ts +++ b/libs/common/src/modules/space-model/repositories/space-model.repository.ts @@ -5,6 +5,8 @@ import { SpaceProductItemModelEntity, SpaceProductModelEntity, SubspaceModelEntity, + SubspaceProductItemModelEntity, + SubspaceProductModelEntity, } from '../entities'; @Injectable() @@ -20,6 +22,20 @@ export class SubspaceModelRepository extends Repository { } } +@Injectable() +export class SubspaceProductModelRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SubspaceProductModelEntity, dataSource.createEntityManager()); + } +} + +@Injectable() +export class SubspaceProductItemModelRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SubspaceProductItemModelEntity, dataSource.createEntityManager()); + } +} + @Injectable() export class SpaceProductModelRepository extends Repository { constructor(private dataSource: DataSource) { From 1373dfb0ce3a106302480f517ce488f8f0d05d1d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 13 Dec 2024 15:00:33 +0400 Subject: [PATCH 049/247] subspace model also has product model and item --- src/product/product.module.ts | 4 +- src/product/services/product.service.ts | 19 +++++ src/space-model/common/index.ts | 1 + .../base-product-item-model.service.ts | 60 ++++++++++++++++ .../services/base-product-model.service.ts | 24 +++++++ src/space-model/common/services/index.ts | 2 + .../dtos/create-subspace-model.dto.ts | 20 +++++- src/space-model/services/index.ts | 2 + .../space-product-item-model.service.ts | 44 ++---------- .../services/space-product-model.service.ts | 32 ++------- .../services/subspace-model.service.ts | 14 ++++ .../subspace-product-item-model.service.ts | 53 ++++++++++++++ .../subspace-product-model.service.ts | 69 +++++++++++++++++++ src/space-model/space-model.module.ts | 8 +++ 14 files changed, 286 insertions(+), 66 deletions(-) create mode 100644 src/space-model/common/index.ts create mode 100644 src/space-model/common/services/base-product-item-model.service.ts create mode 100644 src/space-model/common/services/base-product-model.service.ts create mode 100644 src/space-model/common/services/index.ts create mode 100644 src/space-model/services/subspace-product-item-model.service.ts create mode 100644 src/space-model/services/subspace-product-model.service.ts diff --git a/src/product/product.module.ts b/src/product/product.module.ts index e5ffd25..aa13d20 100644 --- a/src/product/product.module.ts +++ b/src/product/product.module.ts @@ -1,10 +1,12 @@ -import { Module } from '@nestjs/common'; +import { Global, Module } from '@nestjs/common'; import { ProductService } from './services'; import { ProductRepository } from '@app/common/modules/product/repositories'; import { ProductController } from './controllers'; +@Global() @Module({ controllers: [ProductController], providers: [ProductService, ProductRepository], + exports: [ProductService], }) export class ProductModule {} diff --git a/src/product/services/product.service.ts b/src/product/services/product.service.ts index cebf9ba..4b9377c 100644 --- a/src/product/services/product.service.ts +++ b/src/product/services/product.service.ts @@ -21,4 +21,23 @@ export class ProductService { message: 'List of products retrieved successfully', }); } + + async findOne(productUuid: string): Promise { + const product = await this.productRepository.findOne({ + where: { + uuid: productUuid, + }, + }); + if (!product) { + throw new HttpException( + `No product with ${productUuid} found in the system`, + HttpStatus.NOT_FOUND, + ); + } + + return new SuccessResponseDto({ + data: product, + message: 'Succefully retrieved product', + }); + } } diff --git a/src/space-model/common/index.ts b/src/space-model/common/index.ts new file mode 100644 index 0000000..e371345 --- /dev/null +++ b/src/space-model/common/index.ts @@ -0,0 +1 @@ +export * from './services'; diff --git a/src/space-model/common/services/base-product-item-model.service.ts b/src/space-model/common/services/base-product-item-model.service.ts new file mode 100644 index 0000000..80c8119 --- /dev/null +++ b/src/space-model/common/services/base-product-item-model.service.ts @@ -0,0 +1,60 @@ +import { HttpException, HttpStatus } from '@nestjs/common'; +import { QueryRunner } from 'typeorm'; +import { SpaceModelEntity } from '@app/common/modules/space-model'; +import { CreateProductItemModelDto } from 'src/space-model/dtos'; + +export abstract class BaseProductItemService { + async validateTags( + itemModelDtos: CreateProductItemModelDto[], + queryRunner: QueryRunner, + spaceModel: SpaceModelEntity, + ): Promise { + const incomingTags = new Set( + itemModelDtos.map((item) => item.tag).filter(Boolean), + ); + + const duplicateTags = itemModelDtos + .map((item) => item.tag) + .filter((tag, index, array) => array.indexOf(tag) !== index); + + if (duplicateTags.length > 0) { + throw new HttpException( + `Duplicate tags found in the request: ${[...new Set(duplicateTags)].join(', ')}`, + HttpStatus.BAD_REQUEST, + ); + } + + const existingTagsQuery = ` + SELECT DISTINCT tag + FROM ( + SELECT spi.tag + FROM "subspace-product-item-model" spi + INNER JOIN "subspace-product-model" spm ON spi.subspace_product_model_uuid = spm.uuid + INNER JOIN "subspace-model" sm ON spm.subspace_model_uuid = sm.uuid + WHERE sm.space_model_uuid = $1 + UNION + SELECT spi.tag + FROM "space-product-item-model" spi + INNER JOIN "space-product-model" spm ON spi.space_product_model_uuid = spm.uuid + WHERE spm.space_model_uuid = $1 + ) AS combined_tags; + `; + + const existingTags = await queryRunner.manager.query(existingTagsQuery, [ + spaceModel.uuid, + ]); + const existingTagsSet = new Set( + existingTags.map((row: { tag: string }) => row.tag), + ); + + const conflictingTags = [...incomingTags].filter((tag) => + existingTagsSet.has(tag), + ); + if (conflictingTags.length > 0) { + throw new HttpException( + `Tags already exist in the model: ${conflictingTags.join(', ')}`, + HttpStatus.CONFLICT, + ); + } + } +} diff --git a/src/space-model/common/services/base-product-model.service.ts b/src/space-model/common/services/base-product-model.service.ts new file mode 100644 index 0000000..b83511a --- /dev/null +++ b/src/space-model/common/services/base-product-model.service.ts @@ -0,0 +1,24 @@ +import { HttpException, HttpStatus } from '@nestjs/common'; +import { CreateSpaceProductModelDto } from 'src/space-model/dtos'; +import { ProductService } from '../../../product/services'; + +export abstract class BaseProductModelService { + constructor(private readonly productService: ProductService) {} + + protected async validateProductCount( + dto: CreateSpaceProductModelDto, + ): Promise { + const productItemCount = dto.items.length; + if (dto.productCount !== productItemCount) { + throw new HttpException( + `Product count (${dto.productCount}) does not match the number of items (${productItemCount}) for product ID ${dto.productUuid}.`, + HttpStatus.BAD_REQUEST, + ); + } + } + + protected async getProduct(productId: string) { + const product = await this.productService.findOne(productId); + return product.data; + } +} diff --git a/src/space-model/common/services/index.ts b/src/space-model/common/services/index.ts new file mode 100644 index 0000000..d1cc61e --- /dev/null +++ b/src/space-model/common/services/index.ts @@ -0,0 +1,2 @@ +export * from './base-product-item-model.service'; +export * from './base-product-model.service'; diff --git a/src/space-model/dtos/create-subspace-model.dto.ts b/src/space-model/dtos/create-subspace-model.dto.ts index a27ad3b..f8dbcdd 100644 --- a/src/space-model/dtos/create-subspace-model.dto.ts +++ b/src/space-model/dtos/create-subspace-model.dto.ts @@ -1,5 +1,13 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { + IsArray, + IsNotEmpty, + IsOptional, + IsString, + ValidateNested, +} from 'class-validator'; +import { CreateSpaceProductModelDto } from './create-space-product-model.dto'; +import { Type } from 'class-transformer'; export class CreateSubspaceModelDto { @ApiProperty({ @@ -9,4 +17,14 @@ export class CreateSubspaceModelDto { @IsNotEmpty() @IsString() subspaceName: string; + + @ApiProperty({ + description: 'List of products included in the model', + type: [CreateSpaceProductModelDto], + }) + @IsArray() + @IsOptional() + @ValidateNested({ each: true }) + @Type(() => CreateSpaceProductModelDto) + spaceProductModels?: CreateSpaceProductModelDto[]; } diff --git a/src/space-model/services/index.ts b/src/space-model/services/index.ts index 88e2d41..9972aab 100644 --- a/src/space-model/services/index.ts +++ b/src/space-model/services/index.ts @@ -2,3 +2,5 @@ export * from './space-model.service'; export * from './space-product-item-model.service'; export * from './space-product-model.service'; export * from './subspace-model.service'; +export * from './subspace-product-item-model.service'; +export * from './space-product-model.service'; diff --git a/src/space-model/services/space-product-item-model.service.ts b/src/space-model/services/space-product-item-model.service.ts index f7f8b04..c69cae8 100644 --- a/src/space-model/services/space-product-item-model.service.ts +++ b/src/space-model/services/space-product-item-model.service.ts @@ -6,12 +6,15 @@ import { import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { CreateProductItemModelDto } from '../dtos'; import { QueryRunner } from 'typeorm'; +import { BaseProductItemService } from '../common'; @Injectable() -export class SpaceProductItemModelService { +export class SpaceProductItemModelService extends BaseProductItemService { constructor( private readonly spaceProductItemRepository: SpaceProductItemModelRepository, - ) {} + ) { + super(); + } async createProdutItemModel( itemModelDtos: CreateProductItemModelDto[], @@ -41,41 +44,4 @@ export class SpaceProductItemModelService { ); } } - - private async validateTags( - itemModelDtos: CreateProductItemModelDto[], - queryRunner: QueryRunner, - spaceModel: SpaceModelEntity, - ) { - const incomingTags = itemModelDtos.map((item) => item.tag); - - const duplicateTags = incomingTags.filter( - (tag, index) => incomingTags.indexOf(tag) !== index, - ); - if (duplicateTags.length > 0) { - throw new HttpException( - `Duplicate tags found in the request: ${[...new Set(duplicateTags)].join(', ')}`, - HttpStatus.BAD_REQUEST, - ); - } - - const existingTags = await queryRunner.manager.find( - this.spaceProductItemRepository.target, - { - where: { spaceProductModel: { spaceModel } }, - select: ['tag'], - }, - ); - const existingTagSet = new Set(existingTags.map((item) => item.tag)); - - const conflictingTags = incomingTags.filter((tag) => - existingTagSet.has(tag), - ); - if (conflictingTags.length > 0) { - throw new HttpException( - `Tags already exist in the model: ${conflictingTags.join(', ')}`, - HttpStatus.CONFLICT, - ); - } - } } diff --git a/src/space-model/services/space-product-model.service.ts b/src/space-model/services/space-product-model.service.ts index 27bad29..1e16c6f 100644 --- a/src/space-model/services/space-product-model.service.ts +++ b/src/space-model/services/space-product-model.service.ts @@ -4,17 +4,20 @@ import { } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { CreateSpaceProductModelDto } from '../dtos'; -import { ProductRepository } from '@app/common/modules/product/repositories'; import { SpaceProductItemModelService } from './space-product-item-model.service'; import { QueryRunner } from 'typeorm'; +import { BaseProductModelService } from '../common'; +import { ProductService } from 'src/product/services'; @Injectable() -export class SpaceProductModelService { +export class SpaceProductModelService extends BaseProductModelService { constructor( private readonly spaceProductModelRepository: SpaceProductModelRepository, - private readonly productRepository: ProductRepository, private readonly spaceProductItemModelService: SpaceProductItemModelService, - ) {} + productService: ProductService, + ) { + super(productService); + } async createSpaceProductModels( spaceProductModelDtos: CreateSpaceProductModelDto[], @@ -61,25 +64,4 @@ export class SpaceProductModelService { ); } } - - private validateProductCount(dto: CreateSpaceProductModelDto) { - const productItemCount = dto.items.length; - if (dto.productCount !== productItemCount) { - throw new HttpException( - `Product count (${dto.productCount}) does not match the number of items (${productItemCount}) for product ID ${dto.productUuid}.`, - HttpStatus.BAD_REQUEST, - ); - } - } - - private async getProduct(productId: string) { - const product = await this.productRepository.findOneBy({ uuid: productId }); - if (!product) { - throw new HttpException( - `Product with ID ${productId} not found.`, - HttpStatus.NOT_FOUND, - ); - } - return product; - } } diff --git a/src/space-model/services/subspace-model.service.ts b/src/space-model/services/subspace-model.service.ts index a6a75aa..11a501c 100644 --- a/src/space-model/services/subspace-model.service.ts +++ b/src/space-model/services/subspace-model.service.ts @@ -5,11 +5,13 @@ import { import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { CreateSubspaceModelDto } from '../dtos'; import { QueryRunner } from 'typeorm'; +import { SubspaceProductModelService } from './subspace-product-model.service'; @Injectable() export class SubSpaceModelService { constructor( private readonly subspaceModelRepository: SubspaceModelRepository, + private readonly subSpaceProducetModelService: SubspaceProductModelService, ) {} async createSubSpaceModels( @@ -28,6 +30,18 @@ export class SubSpaceModelService { ); await queryRunner.manager.save(subspaces); + + await Promise.all( + subSpaceModelDtos.map((dto, index) => { + const subspaceModel = subspaces[index]; + return this.subSpaceProducetModelService.createSubspaceProductModels( + dto.spaceProductModels, + spaceModel, + subspaceModel, + queryRunner, + ); + }), + ); } catch (error) { if (error instanceof HttpException) { throw error; diff --git a/src/space-model/services/subspace-product-item-model.service.ts b/src/space-model/services/subspace-product-item-model.service.ts new file mode 100644 index 0000000..54c76ff --- /dev/null +++ b/src/space-model/services/subspace-product-item-model.service.ts @@ -0,0 +1,53 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { CreateProductItemModelDto } from '../dtos'; +import { QueryRunner } from 'typeorm'; +import { + SpaceModelEntity, + SubspaceProductItemModelRepository, + SubspaceProductModelEntity, +} from '@app/common/modules/space-model'; +import { BaseProductItemService } from '../common'; + +@Injectable() +export class SubspaceProductItemModelService extends BaseProductItemService { + constructor( + private readonly subspaceProductItemRepository: SubspaceProductItemModelRepository, + ) { + super(); + } + + async createProdutItemModel( + itemModelDtos: CreateProductItemModelDto[], + subspaceProductModel: SubspaceProductModelEntity, + spaceModel: SpaceModelEntity, + queryRunner: QueryRunner, + ) { + if (!subspaceProductModel) { + throw new HttpException( + 'The spaceProductModel parameter is required but was not provided.', + HttpStatus.BAD_REQUEST, + ); + } + await this.validateTags(itemModelDtos, queryRunner, spaceModel); + try { + const productItems = itemModelDtos.map((dto) => + queryRunner.manager.create(this.subspaceProductItemRepository.target, { + tag: dto.tag, + subspaceProductModel, + }), + ); + + await queryRunner.manager.save(productItems); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + error.message || + 'An unexpected error occurred while creating product items.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/space-model/services/subspace-product-model.service.ts b/src/space-model/services/subspace-product-model.service.ts new file mode 100644 index 0000000..ccf3d11 --- /dev/null +++ b/src/space-model/services/subspace-product-model.service.ts @@ -0,0 +1,69 @@ +import { + SpaceModelEntity, + SubspaceModelEntity, + SubspaceProductModelRepository, +} from '@app/common/modules/space-model'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { SubspaceProductItemModelService } from './subspace-product-item-model.service'; +import { CreateSpaceProductModelDto } from '../dtos'; +import { QueryRunner } from 'typeorm'; +import { BaseProductModelService } from '../common'; +import { ProductService } from 'src/product/services'; + +@Injectable() +export class SubspaceProductModelService extends BaseProductModelService { + constructor( + private readonly subpaceProductModelRepository: SubspaceProductModelRepository, + productService: ProductService, + private readonly subspaceProductItemModelService: SubspaceProductItemModelService, + ) { + super(productService); + } + + async createSubspaceProductModels( + spaceProductModelDtos: CreateSpaceProductModelDto[], + spaceModel: SpaceModelEntity, + subspaceModel: SubspaceModelEntity, + queryRunner: QueryRunner, + ) { + try { + const productModels = await Promise.all( + spaceProductModelDtos.map(async (dto) => { + this.validateProductCount(dto); + const product = await this.getProduct(dto.productUuid); + return queryRunner.manager.create( + this.subpaceProductModelRepository.target, + { + product, + productCount: dto.productCount, + subspaceModel, + }, + ); + }), + ); + + const savedProductModels = await queryRunner.manager.save(productModels); + + await Promise.all( + spaceProductModelDtos.map((dto, index) => { + const savedModel = savedProductModels[index]; + return this.subspaceProductItemModelService.createProdutItemModel( + dto.items, + savedModel, // Pass the saved model + spaceModel, + queryRunner, + ); + }), + ); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( + error.message || + 'An unexpected error occurred while creating product models.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/space-model/space-model.module.ts b/src/space-model/space-model.module.ts index bbc6245..5c189a1 100644 --- a/src/space-model/space-model.module.ts +++ b/src/space-model/space-model.module.ts @@ -7,15 +7,19 @@ import { SpaceProductItemModelService, SpaceProductModelService, SubSpaceModelService, + SubspaceProductItemModelService, } from './services'; import { SpaceModelRepository, SpaceProductItemModelRepository, SpaceProductModelRepository, SubspaceModelRepository, + SubspaceProductItemModelRepository, + SubspaceProductModelRepository, } from '@app/common/modules/space-model'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProductRepository } from '@app/common/modules/product/repositories'; +import { SubspaceProductModelService } from './services/subspace-product-model.service'; @Module({ imports: [ConfigModule, SpaceRepositoryModule], @@ -31,6 +35,10 @@ import { ProductRepository } from '@app/common/modules/product/repositories'; ProductRepository, SpaceProductItemModelService, SpaceProductItemModelRepository, + SubspaceProductItemModelService, + SubspaceProductItemModelRepository, + SubspaceProductModelService, + SubspaceProductModelRepository, ], exports: [], }) From 4fb4e32c3d0a45472c685760fa9fe4ed2478cbdd Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 13 Dec 2024 15:08:50 +0400 Subject: [PATCH 050/247] folder restructuring --- src/space-model/services/index.ts | 4 +--- src/space-model/services/space-model.service.ts | 2 +- .../services/space-product-model.service.ts | 12 ++++++------ src/space-model/services/subspace/index.ts | 3 +++ .../{ => subspace}/subspace-model.service.ts | 2 +- .../subspace-product-item-model.service.ts | 4 ++-- .../{ => subspace}/subspace-product-model.service.ts | 4 ++-- src/space-model/space-model.module.ts | 2 +- 8 files changed, 17 insertions(+), 16 deletions(-) create mode 100644 src/space-model/services/subspace/index.ts rename src/space-model/services/{ => subspace}/subspace-model.service.ts (97%) rename src/space-model/services/{ => subspace}/subspace-product-item-model.service.ts (93%) rename src/space-model/services/{ => subspace}/subspace-product-model.service.ts (95%) diff --git a/src/space-model/services/index.ts b/src/space-model/services/index.ts index 9972aab..5c39727 100644 --- a/src/space-model/services/index.ts +++ b/src/space-model/services/index.ts @@ -1,6 +1,4 @@ export * from './space-model.service'; export * from './space-product-item-model.service'; export * from './space-product-model.service'; -export * from './subspace-model.service'; -export * from './subspace-product-item-model.service'; -export * from './space-product-model.service'; +export * from './subspace'; diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 7359e25..0423c42 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -4,7 +4,7 @@ import { CreateSpaceModelDto } from '../dtos'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProjectParam } from 'src/community/dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; -import { SubSpaceModelService } from './subspace-model.service'; +import { SubSpaceModelService } from './subspace/subspace-model.service'; import { SpaceProductModelService } from './space-product-model.service'; import { DataSource } from 'typeorm'; diff --git a/src/space-model/services/space-product-model.service.ts b/src/space-model/services/space-product-model.service.ts index 1e16c6f..0f1e93e 100644 --- a/src/space-model/services/space-product-model.service.ts +++ b/src/space-model/services/space-product-model.service.ts @@ -1,13 +1,13 @@ +import { QueryRunner } from 'typeorm'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { CreateSpaceProductModelDto } from '../dtos'; +import { SpaceProductItemModelService } from './space-product-item-model.service'; +import { BaseProductModelService } from '../common'; +import { ProductService } from 'src/product/services'; import { SpaceModelEntity, SpaceProductModelRepository, } from '@app/common/modules/space-model'; -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { CreateSpaceProductModelDto } from '../dtos'; -import { SpaceProductItemModelService } from './space-product-item-model.service'; -import { QueryRunner } from 'typeorm'; -import { BaseProductModelService } from '../common'; -import { ProductService } from 'src/product/services'; @Injectable() export class SpaceProductModelService extends BaseProductModelService { diff --git a/src/space-model/services/subspace/index.ts b/src/space-model/services/subspace/index.ts new file mode 100644 index 0000000..78d7cd3 --- /dev/null +++ b/src/space-model/services/subspace/index.ts @@ -0,0 +1,3 @@ +export * from './subspace-model.service'; +export * from './subspace-product-item-model.service'; +export * from './subspace-product-model.service'; diff --git a/src/space-model/services/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts similarity index 97% rename from src/space-model/services/subspace-model.service.ts rename to src/space-model/services/subspace/subspace-model.service.ts index 11a501c..222dee4 100644 --- a/src/space-model/services/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -3,7 +3,7 @@ import { SubspaceModelRepository, } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { CreateSubspaceModelDto } from '../dtos'; +import { CreateSubspaceModelDto } from '../../dtos'; import { QueryRunner } from 'typeorm'; import { SubspaceProductModelService } from './subspace-product-model.service'; diff --git a/src/space-model/services/subspace-product-item-model.service.ts b/src/space-model/services/subspace/subspace-product-item-model.service.ts similarity index 93% rename from src/space-model/services/subspace-product-item-model.service.ts rename to src/space-model/services/subspace/subspace-product-item-model.service.ts index 54c76ff..393a5f3 100644 --- a/src/space-model/services/subspace-product-item-model.service.ts +++ b/src/space-model/services/subspace/subspace-product-item-model.service.ts @@ -1,12 +1,12 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { CreateProductItemModelDto } from '../dtos'; import { QueryRunner } from 'typeorm'; import { SpaceModelEntity, SubspaceProductItemModelRepository, SubspaceProductModelEntity, } from '@app/common/modules/space-model'; -import { BaseProductItemService } from '../common'; +import { BaseProductItemService } from '../../common'; +import { CreateProductItemModelDto } from '../../dtos'; @Injectable() export class SubspaceProductItemModelService extends BaseProductItemService { diff --git a/src/space-model/services/subspace-product-model.service.ts b/src/space-model/services/subspace/subspace-product-model.service.ts similarity index 95% rename from src/space-model/services/subspace-product-model.service.ts rename to src/space-model/services/subspace/subspace-product-model.service.ts index ccf3d11..30f9062 100644 --- a/src/space-model/services/subspace-product-model.service.ts +++ b/src/space-model/services/subspace/subspace-product-model.service.ts @@ -5,9 +5,9 @@ import { } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { SubspaceProductItemModelService } from './subspace-product-item-model.service'; -import { CreateSpaceProductModelDto } from '../dtos'; +import { CreateSpaceProductModelDto } from '../../dtos'; import { QueryRunner } from 'typeorm'; -import { BaseProductModelService } from '../common'; +import { BaseProductModelService } from '../../common'; import { ProductService } from 'src/product/services'; @Injectable() diff --git a/src/space-model/space-model.module.ts b/src/space-model/space-model.module.ts index 5c189a1..ae275e2 100644 --- a/src/space-model/space-model.module.ts +++ b/src/space-model/space-model.module.ts @@ -19,7 +19,7 @@ import { } from '@app/common/modules/space-model'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProductRepository } from '@app/common/modules/product/repositories'; -import { SubspaceProductModelService } from './services/subspace-product-model.service'; +import { SubspaceProductModelService } from './services/subspace/subspace-product-model.service'; @Module({ imports: [ConfigModule, SpaceRepositoryModule], From d268a81d851000d2ea15cc85e2801e6728b8a190 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 13 Dec 2024 15:30:38 +0400 Subject: [PATCH 051/247] added subspace product --- libs/common/src/database/database.module.ts | 2 ++ .../dtos/subspace/subspace-product.dto.ts | 15 +++++++++ .../modules/space/entities/subspace/index.ts | 1 + .../subspace/subspace-product.entity.ts | 31 +++++++++++++++++++ .../entities/subspace/subspace.entity.ts | 10 ++++++ .../modules/space/space.repository.module.ts | 10 ++++-- 6 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 libs/common/src/modules/space/dtos/subspace/subspace-product.dto.ts diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 3681bed..2f87d05 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -13,6 +13,7 @@ import { SpaceLinkEntity, SpaceProductItemEntity, SubspaceEntity, + SubspaceProductEntity, } from '../modules/space/entities'; import { UserSpaceEntity } from '../modules/user/entities'; import { DeviceUserPermissionEntity } from '../modules/device/entities'; @@ -85,6 +86,7 @@ import { SpaceProductItemEntity, SubspaceProductModelEntity, SubspaceProductItemModelEntity, + SubspaceProductEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), diff --git a/libs/common/src/modules/space/dtos/subspace/subspace-product.dto.ts b/libs/common/src/modules/space/dtos/subspace/subspace-product.dto.ts new file mode 100644 index 0000000..4fd2db1 --- /dev/null +++ b/libs/common/src/modules/space/dtos/subspace/subspace-product.dto.ts @@ -0,0 +1,15 @@ +import { IsString, IsNotEmpty, IsNumber } from 'class-validator'; + +export class SubspaceProductDto { + @IsString() + @IsNotEmpty() + uuid: string; + + @IsNumber() + @IsNotEmpty() + productCount: number; + + @IsString() + @IsNotEmpty() + productUuid: string; +} diff --git a/libs/common/src/modules/space/entities/subspace/index.ts b/libs/common/src/modules/space/entities/subspace/index.ts index be13961..bd9d689 100644 --- a/libs/common/src/modules/space/entities/subspace/index.ts +++ b/libs/common/src/modules/space/entities/subspace/index.ts @@ -1 +1,2 @@ export * from './subspace.entity'; +export * from './subspace-product.entity'; diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts index e69de29..f9308ae 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts @@ -0,0 +1,31 @@ +import { ProductEntity } from '@app/common/modules/product/entities'; +import { Column, Entity, ManyToOne } from 'typeorm'; +import { SubspaceEntity } from './subspace.entity'; +import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; +import { SubspaceProductDto } from '../../dtos/subspace/subspace-product.dto'; + +@Entity({ name: 'subspace-product' }) +export class SubspaceProductEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', + nullable: false, + }) + public uuid: string; + + @Column({ + nullable: false, + type: 'int', + }) + productCount: number; + + @ManyToOne(() => SubspaceEntity, (subspace) => subspace.subspaceProducts, { + nullable: false, + }) + public subspace: SubspaceEntity; + + @ManyToOne(() => ProductEntity, (product) => product.subpaceProductModels, { + nullable: false, + }) + public product: ProductEntity; +} diff --git a/libs/common/src/modules/space/entities/subspace/subspace.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace.entity.ts index 8f0d15d..932757d 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace.entity.ts @@ -4,6 +4,7 @@ import { SubspaceModelEntity } from '@app/common/modules/space-model'; import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; import { SubspaceDto } from '../../dtos'; import { SpaceEntity } from '../space.entity'; +import { SubspaceProductEntity } from './subspace-product.entity'; @Entity({ name: 'subspace' }) export class SubspaceEntity extends AbstractEntity { @@ -35,6 +36,15 @@ export class SubspaceEntity extends AbstractEntity { @JoinColumn({ name: 'subspace_model_uuid' }) subSpaceModel?: SubspaceModelEntity; + @OneToMany( + () => SubspaceProductEntity, + (subspaceProduct) => subspaceProduct.subspace, + { + nullable: true, + }, + ) + public subspaceProducts: SubspaceProductEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/space/space.repository.module.ts b/libs/common/src/modules/space/space.repository.module.ts index 90916c2..b39f98d 100644 --- a/libs/common/src/modules/space/space.repository.module.ts +++ b/libs/common/src/modules/space/space.repository.module.ts @@ -1,11 +1,17 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { SpaceEntity, SubspaceEntity } from './entities'; +import { SpaceEntity, SubspaceEntity, SubspaceProductEntity } from './entities'; @Module({ providers: [], exports: [], controllers: [], - imports: [TypeOrmModule.forFeature([SpaceEntity, SubspaceEntity])], + imports: [ + TypeOrmModule.forFeature([ + SpaceEntity, + SubspaceEntity, + SubspaceProductEntity, + ]), + ], }) export class SpaceRepositoryModule {} From 05b2bddc26035da49a3c99573ebcfa93a6e98ad0 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 13 Dec 2024 15:39:13 +0400 Subject: [PATCH 052/247] added subspace product item --- libs/common/src/database/database.module.ts | 2 ++ .../modules/space/entities/subspace/index.ts | 1 + .../subspace/subspace-product-item.entity.ts | 21 +++++++++++++++++++ .../subspace/subspace-product.entity.ts | 8 ++++++- 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 2f87d05..3da6ed6 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -14,6 +14,7 @@ import { SpaceProductItemEntity, SubspaceEntity, SubspaceProductEntity, + SubspaceProductItemEntity, } from '../modules/space/entities'; import { UserSpaceEntity } from '../modules/user/entities'; import { DeviceUserPermissionEntity } from '../modules/device/entities'; @@ -87,6 +88,7 @@ import { SubspaceProductModelEntity, SubspaceProductItemModelEntity, SubspaceProductEntity, + SubspaceProductItemEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), diff --git a/libs/common/src/modules/space/entities/subspace/index.ts b/libs/common/src/modules/space/entities/subspace/index.ts index bd9d689..471b7b1 100644 --- a/libs/common/src/modules/space/entities/subspace/index.ts +++ b/libs/common/src/modules/space/entities/subspace/index.ts @@ -1,2 +1,3 @@ export * from './subspace.entity'; export * from './subspace-product.entity'; +export * from './subspace-product-item.entity'; diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts index e69de29..671b046 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts @@ -0,0 +1,21 @@ +import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; +import { SpaceProductItemDto } from '../../dtos'; +import { Column, Entity, ManyToOne } from 'typeorm'; +import { SubspaceProductEntity } from './subspace-product.entity'; + +@Entity({ name: 'subspace-product-item' }) +export class SubspaceProductItemEntity extends AbstractEntity { + @Column({ + nullable: false, + }) + public tag: string; + + @ManyToOne( + () => SubspaceProductEntity, + (subspaceProduct) => subspaceProduct.items, + { + nullable: false, + }, + ) + public subspaceProduct: SubspaceProductEntity; +} diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts index f9308ae..f5d0eda 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts @@ -1,8 +1,9 @@ import { ProductEntity } from '@app/common/modules/product/entities'; -import { Column, Entity, ManyToOne } from 'typeorm'; +import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'; import { SubspaceEntity } from './subspace.entity'; import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; import { SubspaceProductDto } from '../../dtos/subspace/subspace-product.dto'; +import { SubspaceProductItemEntity } from './subspace-product-item.entity'; @Entity({ name: 'subspace-product' }) export class SubspaceProductEntity extends AbstractEntity { @@ -28,4 +29,9 @@ export class SubspaceProductEntity extends AbstractEntity { nullable: false, }) public product: ProductEntity; + + @OneToMany(() => SubspaceProductItemEntity, (item) => item.subspaceProduct, { + nullable: true, + }) + public items: SubspaceProductItemEntity[]; } From 654642db6cc261a4e0c3a2d3745276e3ff8f2f6a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 13 Dec 2024 15:43:34 +0400 Subject: [PATCH 053/247] added relation between subspace product item and model --- .../subspace-model/subspace-product-item-model.entity.ts | 8 +++++++- .../entities/subspace/subspace-product-item.entity.ts | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts index decd0a7..3821e5c 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts @@ -1,7 +1,8 @@ import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; -import { Entity, Column, ManyToOne } from 'typeorm'; +import { Entity, Column, ManyToOne, OneToMany } from 'typeorm'; import { SubspaceProductItemModelDto } from '../../dtos'; import { SubspaceProductModelEntity } from './subspace-product-model.entity'; +import { SubspaceProductItemEntity } from '@app/common/modules/space/entities'; @Entity({ name: 'subspace-product-item-model' }) export class SubspaceProductItemModelEntity extends AbstractEntity { @@ -18,4 +19,9 @@ export class SubspaceProductItemModelEntity extends AbstractEntity SubspaceProductItemEntity, (item) => item.model, { + nullable: true, + }) + items: SubspaceProductItemEntity[]; } diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts index 671b046..5041053 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts @@ -2,6 +2,7 @@ import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.e import { SpaceProductItemDto } from '../../dtos'; import { Column, Entity, ManyToOne } from 'typeorm'; import { SubspaceProductEntity } from './subspace-product.entity'; +import { SubspaceProductItemModelEntity } from '@app/common/modules/space-model'; @Entity({ name: 'subspace-product-item' }) export class SubspaceProductItemEntity extends AbstractEntity { @@ -18,4 +19,9 @@ export class SubspaceProductItemEntity extends AbstractEntity SubspaceProductItemModelEntity, (model) => model.items, { + nullable: true, + }) + model: SubspaceProductItemModelEntity; } From 7cab62b02ae703bbfdae024877c8174095c35389 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 13 Dec 2024 15:57:25 +0400 Subject: [PATCH 054/247] added subspace product item --- .../subspace-product-item-model.entity.ts | 4 ++-- .../subspace-product-model.entity.ts | 12 +++++++++--- .../space/dtos/subspace/subspace-product.dto.ts | 15 --------------- .../subspace/subspace-product-item.entity.ts | 5 +++++ .../subspace/subspace-product.entity.ts | 17 +++++++++++++++-- 5 files changed, 31 insertions(+), 22 deletions(-) delete mode 100644 libs/common/src/modules/space/dtos/subspace/subspace-product.dto.ts diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts index 3821e5c..2b6d305 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts @@ -13,14 +13,14 @@ export class SubspaceProductItemModelEntity extends AbstractEntity SubspaceProductModelEntity, - (subspaceProductModel) => subspaceProductModel.items, + (productModel) => productModel.itemModels, { nullable: false, }, ) public subspaceProductModel: SubspaceProductModelEntity; - @OneToMany(() => SubspaceProductItemEntity, (item) => item.model, { + @OneToMany(() => SubspaceProductItemEntity, (item) => item.subspaceProduct, { nullable: true, }) items: SubspaceProductItemEntity[]; diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts index 3a5ab1f..6e22a35 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts @@ -3,6 +3,7 @@ import { SubpaceProductModelDto } from '../../dtos'; import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; import { SubspaceModelEntity } from './subspace-model.entity'; import { ProductEntity } from '@app/common/modules/product/entities'; +import { SubspaceProductEntity } from '@app/common/modules/space/entities'; import { SubspaceProductItemModelEntity } from './subspace-product-item-model.entity'; @Entity({ name: 'subspace-product-model' }) @@ -27,12 +28,17 @@ export class SubspaceProductModelEntity extends AbstractEntity SubspaceProductEntity, (product) => product.model, { + nullable: true, + }) + public subspaceProducts: SubspaceProductEntity[]; + @OneToMany( () => SubspaceProductItemModelEntity, - (item) => item.subspaceProductModel, + (product) => product.subspaceProductModel, { - cascade: true, + nullable: true, }, ) - public items: SubspaceProductItemModelEntity[]; + public itemModels: SubspaceProductItemModelEntity[]; } diff --git a/libs/common/src/modules/space/dtos/subspace/subspace-product.dto.ts b/libs/common/src/modules/space/dtos/subspace/subspace-product.dto.ts deleted file mode 100644 index 4fd2db1..0000000 --- a/libs/common/src/modules/space/dtos/subspace/subspace-product.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { IsString, IsNotEmpty, IsNumber } from 'class-validator'; - -export class SubspaceProductDto { - @IsString() - @IsNotEmpty() - uuid: string; - - @IsNumber() - @IsNotEmpty() - productCount: number; - - @IsString() - @IsNotEmpty() - productUuid: string; -} diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts index 5041053..1ec7958 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts @@ -24,4 +24,9 @@ export class SubspaceProductItemEntity extends AbstractEntity) { + super(); + Object.assign(this, partial); + } } diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts index f5d0eda..03531cc 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts @@ -2,11 +2,15 @@ import { ProductEntity } from '@app/common/modules/product/entities'; import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'; import { SubspaceEntity } from './subspace.entity'; import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; -import { SubspaceProductDto } from '../../dtos/subspace/subspace-product.dto'; import { SubspaceProductItemEntity } from './subspace-product-item.entity'; +import { + SubspaceProductItemModelEntity, + SubspaceProductModelEntity, +} from '@app/common/modules/space-model'; +import { SpaceProductModelDto } from '../../dtos'; @Entity({ name: 'subspace-product' }) -export class SubspaceProductEntity extends AbstractEntity { +export class SubspaceProductEntity extends AbstractEntity { @Column({ type: 'uuid', default: () => 'gen_random_uuid()', @@ -34,4 +38,13 @@ export class SubspaceProductEntity extends AbstractEntity { nullable: true, }) public items: SubspaceProductItemEntity[]; + + @ManyToOne( + () => SubspaceProductModelEntity, + (model) => model.subspaceProducts, + { + nullable: true, + }, + ) + model: SubspaceProductItemModelEntity; } From 3099e905e28fbe56e3120d790ca1945ea224ef52 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 15 Dec 2024 19:59:36 +0400 Subject: [PATCH 055/247] subspace product repository created --- libs/common/src/common.module.ts | 7 ++--- .../space/repositories/space.repository.ts | 7 ----- .../space/repositories/subspace.repository.ts | 28 +++++++++++++++++++ .../subspace/subspace-device.service.ts | 2 +- .../services/subspace/subspace.service.ts | 2 +- src/space/space.module.ts | 2 +- 6 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 libs/common/src/modules/space/repositories/subspace.repository.ts diff --git a/libs/common/src/common.module.ts b/libs/common/src/common.module.ts index 9a3de9c..582510d 100644 --- a/libs/common/src/common.module.ts +++ b/libs/common/src/common.module.ts @@ -9,11 +9,7 @@ import { EmailService } from './util/email.service'; import { ErrorMessageService } from 'src/error-message/error-message.service'; import { TuyaService } from './integrations/tuya/services/tuya.service'; import { SceneDeviceRepository } from './modules/scene-device/repositories'; -import { - SpaceProductItemRepository, - SpaceRepository, - SubspaceRepository, -} from './modules/space'; +import { SpaceProductItemRepository, SpaceRepository } from './modules/space'; import { SpaceModelRepository, SpaceProductModelRepository, @@ -21,6 +17,7 @@ import { SubspaceProductItemModelRepository, SubspaceProductModelRepository, } from './modules/space-model'; +import { SubspaceRepository } from './modules/space/repositories/subspace.repository'; @Module({ providers: [ CommonService, diff --git a/libs/common/src/modules/space/repositories/space.repository.ts b/libs/common/src/modules/space/repositories/space.repository.ts index 677db0e..66f96a4 100644 --- a/libs/common/src/modules/space/repositories/space.repository.ts +++ b/libs/common/src/modules/space/repositories/space.repository.ts @@ -5,7 +5,6 @@ import { SpaceEntity, SpaceLinkEntity, SpaceProductItemEntity, - SubspaceEntity, } from '../entities'; @Injectable() @@ -14,12 +13,6 @@ export class SpaceRepository extends Repository { super(SpaceEntity, dataSource.createEntityManager()); } } -@Injectable() -export class SubspaceRepository extends Repository { - constructor(private dataSource: DataSource) { - super(SubspaceEntity, dataSource.createEntityManager()); - } -} @Injectable() export class SpaceLinkRepository extends Repository { diff --git a/libs/common/src/modules/space/repositories/subspace.repository.ts b/libs/common/src/modules/space/repositories/subspace.repository.ts new file mode 100644 index 0000000..3682c05 --- /dev/null +++ b/libs/common/src/modules/space/repositories/subspace.repository.ts @@ -0,0 +1,28 @@ +import { DataSource, Repository } from 'typeorm'; +import { + SubspaceEntity, + SubspaceProductEntity, + SubspaceProductItemEntity, +} from '../entities'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class SubspaceRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SubspaceEntity, dataSource.createEntityManager()); + } +} + +@Injectable() +export class SubspaceProductRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SubspaceProductEntity, dataSource.createEntityManager()); + } +} + +@Injectable() +export class SubspaceProductItemRepository extends Repository { + constructor(private dataSource: DataSource) { + super(SubspaceProductItemEntity, dataSource.createEntityManager()); + } +} diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index 2bcfb93..d6f6320 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -1,7 +1,6 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { DeviceRepository } from '@app/common/modules/device/repositories'; -import { SubspaceRepository } from '@app/common/modules/space/repositories'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; @@ -9,6 +8,7 @@ import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service import { ProductRepository } from '@app/common/modules/product/repositories'; import { GetDeviceDetailsInterface } from '../../../device/interfaces/get.device.interface'; import { ValidationService } from '../space-validation.service'; +import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; @Injectable() export class SubspaceDeviceService { diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 4e9b2e3..96afa41 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -1,5 +1,4 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { SubspaceRepository } from '@app/common/modules/space/repositories'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../../dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; @@ -16,6 +15,7 @@ import { } from '@app/common/modules/space/entities'; import { SpaceModelEntity } from '@app/common/modules/space-model'; import { ValidationService } from '../space-validation.service'; +import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; @Injectable() export class SubSpaceService { diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 40a72d3..cd443e5 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -23,7 +23,6 @@ import { import { SpaceProductRepository, SpaceRepository, - SubspaceRepository, SpaceLinkRepository, SpaceProductItemRepository, } from '@app/common/modules/space/repositories'; @@ -48,6 +47,7 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { SpaceModelRepository } from '@app/common/modules/space-model'; import { CommunityModule } from 'src/community/community.module'; import { ValidationService } from './services/space-validation.service'; +import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, CommunityModule], From 852299a273c1a486424e06d948a999a413ca3e7b Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:16:24 -0600 Subject: [PATCH 056/247] Refactor user role handling to use single role object --- .../src/auth/interfaces/auth.interface.ts | 2 +- libs/common/src/auth/services/auth.service.ts | 5 +- .../src/auth/strategies/jwt.strategy.ts | 2 +- .../auth/strategies/refresh-token.strategy.ts | 2 +- libs/common/src/constants/role.type.enum.ts | 2 + .../src/seed/services/role.type.seeder.ts | 7 ++- .../src/seed/services/supper.admin.seeder.ts | 25 +++++---- src/guards/admin.role.guard.ts | 11 ++-- src/guards/community.permission.guard.ts | 8 +-- src/guards/super.admin.role.guard.ts | 5 +- src/role/controllers/role.controller.ts | 15 ------ src/role/role.module.ts | 2 - src/role/services/role.service.ts | 53 +++---------------- 13 files changed, 43 insertions(+), 96 deletions(-) diff --git a/libs/common/src/auth/interfaces/auth.interface.ts b/libs/common/src/auth/interfaces/auth.interface.ts index 9b73050..ba674ee 100644 --- a/libs/common/src/auth/interfaces/auth.interface.ts +++ b/libs/common/src/auth/interfaces/auth.interface.ts @@ -4,5 +4,5 @@ export class AuthInterface { uuid: string; sessionId: string; id: number; - roles?: string[]; + role?: object; } diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index 5792eb0..7f4f03a 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -39,7 +39,7 @@ export class AuthService { } : undefined, }, - relations: ['roles.roleType'], + relations: ['roleType'], }); if (!user.isUserVerified) { @@ -85,9 +85,8 @@ export class AuthService { email: user.email, userId: user.userId, uuid: user.uuid, - type: user.type, sessionId: user.sessionId, - roles: user?.roles, + role: user?.role, googleCode: user.googleCode, }; if (payload.googleCode) { diff --git a/libs/common/src/auth/strategies/jwt.strategy.ts b/libs/common/src/auth/strategies/jwt.strategy.ts index d548dd8..88ecb99 100644 --- a/libs/common/src/auth/strategies/jwt.strategy.ts +++ b/libs/common/src/auth/strategies/jwt.strategy.ts @@ -31,7 +31,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { userUuid: payload.uuid, uuid: payload.uuid, sessionId: payload.sessionId, - roles: payload?.roles, + role: payload?.role, }; } else { throw new BadRequestException('Unauthorized'); diff --git a/libs/common/src/auth/strategies/refresh-token.strategy.ts b/libs/common/src/auth/strategies/refresh-token.strategy.ts index b6d6c2a..6061bd2 100644 --- a/libs/common/src/auth/strategies/refresh-token.strategy.ts +++ b/libs/common/src/auth/strategies/refresh-token.strategy.ts @@ -34,7 +34,7 @@ export class RefreshTokenStrategy extends PassportStrategy( userUuid: payload.uuid, uuid: payload.uuid, sessionId: payload.sessionId, - roles: payload?.roles, + role: payload?.role, }; } else { throw new BadRequestException('Unauthorized'); diff --git a/libs/common/src/constants/role.type.enum.ts b/libs/common/src/constants/role.type.enum.ts index 3051b04..edaf2bf 100644 --- a/libs/common/src/constants/role.type.enum.ts +++ b/libs/common/src/constants/role.type.enum.ts @@ -1,4 +1,6 @@ export enum RoleType { SUPER_ADMIN = 'SUPER_ADMIN', ADMIN = 'ADMIN', + SPACE_OWNER = 'SPACE_OWNER', + SPACE_MEMBER = 'SPACE_MEMBER', } diff --git a/libs/common/src/seed/services/role.type.seeder.ts b/libs/common/src/seed/services/role.type.seeder.ts index 5f7a4b2..1294666 100644 --- a/libs/common/src/seed/services/role.type.seeder.ts +++ b/libs/common/src/seed/services/role.type.seeder.ts @@ -19,7 +19,12 @@ export class RoleTypeSeeder { if (!roleTypeNames.includes(RoleType.ADMIN)) { missingRoleTypes.push(RoleType.ADMIN); } - + if (!roleTypeNames.includes(RoleType.SPACE_OWNER)) { + missingRoleTypes.push(RoleType.SPACE_OWNER); + } + if (!roleTypeNames.includes(RoleType.SPACE_MEMBER)) { + missingRoleTypes.push(RoleType.SPACE_MEMBER); + } if (missingRoleTypes.length > 0) { await this.addRoleTypeData(missingRoleTypes); } diff --git a/libs/common/src/seed/services/supper.admin.seeder.ts b/libs/common/src/seed/services/supper.admin.seeder.ts index 9cfdfee..5f7ef21 100644 --- a/libs/common/src/seed/services/supper.admin.seeder.ts +++ b/libs/common/src/seed/services/supper.admin.seeder.ts @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import { UserRepository } from '@app/common/modules/user/repositories'; import { RoleType } from '@app/common/constants/role.type.enum'; -import { UserRoleRepository } from '@app/common/modules/user/repositories'; import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; import { ConfigService } from '@nestjs/config'; import { HelperHashService } from '../../helper/services'; @@ -11,19 +10,23 @@ export class SuperAdminSeeder { constructor( private readonly configService: ConfigService, private readonly userRepository: UserRepository, - private readonly userRoleRepository: UserRoleRepository, private readonly roleTypeRepository: RoleTypeRepository, private readonly helperHashService: HelperHashService, ) {} async createSuperAdminIfNotFound(): Promise { try { - const superAdminData = await this.userRoleRepository.find({ - where: { roleType: { type: RoleType.SUPER_ADMIN } }, + const superAdmin = await this.userRepository.findOne({ + where: { + roleType: { type: RoleType.SUPER_ADMIN }, + email: this.configService.get( + 'super-admin.SUPER_ADMIN_EMAIL', + ), + }, relations: ['roleType'], }); - if (superAdminData.length <= 0) { + if (!superAdmin) { // Create the super admin user if not found console.log('Creating super admin user...'); @@ -48,20 +51,16 @@ export class SuperAdminSeeder { salt, ); try { - const user = await this.userRepository.save({ + const defaultUserRoleUuid = await this.getRoleUuidByRoleType( + RoleType.SUPER_ADMIN, + ); + await this.userRepository.save({ email: this.configService.get('super-admin.SUPER_ADMIN_EMAIL'), password: hashedPassword, firstName: 'Super', lastName: 'Admin', isUserVerified: true, isActive: true, - }); - const defaultUserRoleUuid = await this.getRoleUuidByRoleType( - RoleType.SUPER_ADMIN, - ); - - await this.userRoleRepository.save({ - user: { uuid: user.uuid }, roleType: { uuid: defaultUserRoleUuid }, }); } catch (err) { diff --git a/src/guards/admin.role.guard.ts b/src/guards/admin.role.guard.ts index f7d64a0..db41d20 100644 --- a/src/guards/admin.role.guard.ts +++ b/src/guards/admin.role.guard.ts @@ -7,11 +7,12 @@ export class AdminRoleGuard extends AuthGuard('jwt') { if (err || !user) { throw err || new UnauthorizedException(); } else { - const isAdmin = user.roles.some( - (role) => - role.type === RoleType.SUPER_ADMIN || role.type === RoleType.ADMIN, - ); - if (!isAdmin) { + if ( + !( + user.role.type === RoleType.ADMIN || + user.role.type === RoleType.SUPER_ADMIN + ) + ) { throw new BadRequestException('Only admin role can access this route'); } } diff --git a/src/guards/community.permission.guard.ts b/src/guards/community.permission.guard.ts index 10d9c5e..12d87a8 100644 --- a/src/guards/community.permission.guard.ts +++ b/src/guards/community.permission.guard.ts @@ -20,10 +20,10 @@ export class CommunityPermissionGuard implements CanActivate { if ( user && - user.roles && - user.roles.some( - (role) => - role.type === RoleType.ADMIN || role.type === RoleType.SUPER_ADMIN, + user.role && + !( + user.role.type === RoleType.ADMIN || + user.role.type === RoleType.SUPER_ADMIN ) ) { return true; diff --git a/src/guards/super.admin.role.guard.ts b/src/guards/super.admin.role.guard.ts index ef93a75..b183d78 100644 --- a/src/guards/super.admin.role.guard.ts +++ b/src/guards/super.admin.role.guard.ts @@ -7,10 +7,7 @@ export class SuperAdminRoleGuard extends AuthGuard('jwt') { if (err || !user) { throw err || new UnauthorizedException(); } else { - const isSuperAdmin = user.roles.some( - (role) => role.type === RoleType.SUPER_ADMIN, - ); - if (!isSuperAdmin) { + if (!(user.role.type === RoleType.SUPER_ADMIN)) { throw new BadRequestException( 'Only super admin role can access this route', ); diff --git a/src/role/controllers/role.controller.ts b/src/role/controllers/role.controller.ts index a53cd1c..4317170 100644 --- a/src/role/controllers/role.controller.ts +++ b/src/role/controllers/role.controller.ts @@ -36,19 +36,4 @@ export class RoleController { data: roleTypes, }; } - - @ApiBearerAuth() - @UseGuards(SuperAdminRoleGuard) - @Post() - @ApiOperation({ - summary: ControllerRoute.ROLE.ACTIONS.ADD_USER_ROLE_SUMMARY, - description: ControllerRoute.ROLE.ACTIONS.ADD_USER_ROLE_DESCRIPTION, - }) - async addUserRoleType(@Body() addUserRoleDto: AddUserRoleDto) { - await this.roleService.addUserRoleType(addUserRoleDto); - return { - statusCode: HttpStatus.OK, - message: 'User Role Added Successfully', - }; - } } diff --git a/src/role/role.module.ts b/src/role/role.module.ts index 4e51725..fca78e0 100644 --- a/src/role/role.module.ts +++ b/src/role/role.module.ts @@ -7,7 +7,6 @@ import { RoleController } from './controllers/role.controller'; import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; -import { UserRoleRepository } from '@app/common/modules/user/repositories'; @Module({ imports: [ConfigModule, DeviceRepositoryModule], @@ -18,7 +17,6 @@ import { UserRoleRepository } from '@app/common/modules/user/repositories'; DeviceRepository, RoleService, RoleTypeRepository, - UserRoleRepository, ], exports: [RoleService], }) diff --git a/src/role/services/role.service.ts b/src/role/services/role.service.ts index ba10f44..5f75571 100644 --- a/src/role/services/role.service.ts +++ b/src/role/services/role.service.ts @@ -1,55 +1,16 @@ +import { Injectable } from '@nestjs/common'; import { RoleTypeRepository } from './../../../libs/common/src/modules/role-type/repositories/role.type.repository'; -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { AddUserRoleDto } from '../dtos/role.add.dto'; -import { UserRoleRepository } from '@app/common/modules/user/repositories'; -import { QueryFailedError } from 'typeorm'; -import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; +import { RoleType } from '@app/common/constants/role.type.enum'; @Injectable() export class RoleService { - constructor( - private readonly roleTypeRepository: RoleTypeRepository, - private readonly userRoleRepository: UserRoleRepository, - ) {} - - async addUserRoleType(addUserRoleDto: AddUserRoleDto) { - try { - const roleType = await this.fetchRoleByType(addUserRoleDto.roleType); - - if (roleType.uuid) { - return await this.userRoleRepository.save({ - user: { uuid: addUserRoleDto.userUuid }, - roleType: { uuid: roleType.uuid }, - }); - } - } catch (error) { - if ( - error instanceof QueryFailedError && - error.driverError.code === CommonErrorCodes.DUPLICATE_ENTITY - ) { - // Postgres unique constraint violation error code - throw new HttpException( - 'This role already exists for this user', - HttpStatus.CONFLICT, - ); - } - throw new HttpException( - error.message || 'Internal Server Error', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } + constructor(private readonly roleTypeRepository: RoleTypeRepository) {} async fetchRoleTypes() { const roleTypes = await this.roleTypeRepository.find(); - - return roleTypes; - } - private async fetchRoleByType(roleType: string) { - return await this.roleTypeRepository.findOne({ - where: { - type: roleType, - }, - }); + const roles = roleTypes.filter( + (roleType) => roleType.type !== RoleType.SUPER_ADMIN, + ); + return roles; } } From 57397e653abcd953c1641d74999724207a4234c5 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:17:50 -0600 Subject: [PATCH 057/247] Add Invite User Module and Update User and Space Entities This commit introduces a new module for handling user invitations, including DTOs, entities, and repositories. It also updates the User and Space entities to include relationships with the new Invite User entities. --- libs/common/src/constants/controller-route.ts | 20 ++++ libs/common/src/constants/user-status.enum.ts | 5 + .../Invite-user.repository.module.ts | 13 ++ .../Invite-user/dtos/Invite-user.dto.ts | 42 +++++++ .../src/modules/Invite-user/dtos/index.ts | 1 + .../entities/Invite-user.entity.ts | 112 ++++++++++++++++++ .../src/modules/Invite-user/entities/index.ts | 1 + .../repositories/Invite-user.repository.ts | 16 +++ .../modules/Invite-user/repositories/index.ts | 1 + .../modules/space/entities/space.entity.ts | 7 +- libs/common/src/modules/user/dtos/user.dto.ts | 14 --- .../src/modules/user/entities/user.entity.ts | 46 ++++--- .../user/repositories/user.repository.ts | 8 -- libs/common/src/seed/seeder.module.ts | 2 - .../services/invite-user.service.ts | 103 ++++++++++++++++ 15 files changed, 341 insertions(+), 50 deletions(-) create mode 100644 libs/common/src/constants/user-status.enum.ts create mode 100644 libs/common/src/modules/Invite-user/Invite-user.repository.module.ts create mode 100644 libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts create mode 100644 libs/common/src/modules/Invite-user/dtos/index.ts create mode 100644 libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts create mode 100644 libs/common/src/modules/Invite-user/entities/index.ts create mode 100644 libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts create mode 100644 libs/common/src/modules/Invite-user/repositories/index.ts create mode 100644 src/invite-user/services/invite-user.service.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 10e20d9..c26deca 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -721,4 +721,24 @@ export class ControllerRoute { 'This endpoint deletes a user’s subscription for device messages.'; }; }; + static INVITE_USER = class { + public static readonly ROUTE = 'invite-user'; + static ACTIONS = class { + public static readonly CREATE_USER_INVITATION_SUMMARY = + 'Create user invitation'; + + public static readonly CREATE_USER_INVITATION_DESCRIPTION = + 'This endpoint creates an invitation for a user to assign to role and spaces.'; + }; + }; + static PERMISSION = class { + public static readonly ROUTE = 'permission'; + static ACTIONS = class { + public static readonly GET_PERMISSION_BY_ROLE_SUMMARY = + 'Get permissions by role'; + + public static readonly GET_PERMISSION_BY_ROLE_DESCRIPTION = + 'This endpoint retrieves the permissions associated with a specific role.'; + }; + }; } diff --git a/libs/common/src/constants/user-status.enum.ts b/libs/common/src/constants/user-status.enum.ts new file mode 100644 index 0000000..b0b9817 --- /dev/null +++ b/libs/common/src/constants/user-status.enum.ts @@ -0,0 +1,5 @@ +export enum UserStatusEnum { + ACTIVE = 'active', + INVITED = 'invited', + DISABLED = 'disabled', +} diff --git a/libs/common/src/modules/Invite-user/Invite-user.repository.module.ts b/libs/common/src/modules/Invite-user/Invite-user.repository.module.ts new file mode 100644 index 0000000..4cc8596 --- /dev/null +++ b/libs/common/src/modules/Invite-user/Invite-user.repository.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { InviteUserEntity, InviteUserSpaceEntity } from './entities'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [ + TypeOrmModule.forFeature([InviteUserEntity, InviteUserSpaceEntity]), + ], +}) +export class InviteUserRepositoryModule {} diff --git a/libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts b/libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts new file mode 100644 index 0000000..d7db8dc --- /dev/null +++ b/libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts @@ -0,0 +1,42 @@ +import { UserStatusEnum } from '@app/common/constants/user-status.enum'; +import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; + +export class InviteUserDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public email: string; + @IsString() + @IsNotEmpty() + public jobTitle: string; + @IsEnum(UserStatusEnum) + @IsNotEmpty() + public status: UserStatusEnum; + @IsString() + @IsNotEmpty() + public firstName: string; + + @IsString() + @IsNotEmpty() + public lastName: string; +} +export class InviteUserSpaceDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public inviteUserUuid: string; + + @IsString() + @IsNotEmpty() + public spaceUuid: string; + + @IsString() + @IsNotEmpty() + public invitationCode: string; +} diff --git a/libs/common/src/modules/Invite-user/dtos/index.ts b/libs/common/src/modules/Invite-user/dtos/index.ts new file mode 100644 index 0000000..2385037 --- /dev/null +++ b/libs/common/src/modules/Invite-user/dtos/index.ts @@ -0,0 +1 @@ +export * from './Invite-user.dto'; diff --git a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts new file mode 100644 index 0000000..06e5105 --- /dev/null +++ b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts @@ -0,0 +1,112 @@ +import { + Column, + Entity, + JoinColumn, + ManyToOne, + OneToMany, + OneToOne, + Unique, +} from 'typeorm'; +import { InviteUserDto, InviteUserSpaceDto } from '../dtos'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { RoleTypeEntity } from '../../role-type/entities'; +import { UserStatusEnum } from '@app/common/constants/user-status.enum'; +import { UserEntity } from '../../user/entities'; +import { SpaceEntity } from '../../space/entities'; + +@Entity({ name: 'invite-user' }) +@Unique(['email', 'invitationCode']) +export class InviteUserEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', + nullable: false, + }) + public uuid: string; + + @Column({ + nullable: false, + unique: true, + }) + email: string; + + @Column({ + nullable: false, + }) + jobTitle: string; + + @Column({ + nullable: false, + enum: Object.values(UserStatusEnum), + }) + status: string; + + @Column() + public firstName: string; + + @Column({ + nullable: false, + }) + public lastName: string; + @Column({ + nullable: false, + }) + public phoneNumber: string; + + @Column({ + nullable: false, + default: true, + }) + public isEnabled: boolean; + + @Column({ + nullable: false, + default: true, + }) + public isActive: boolean; + @Column({ + nullable: false, + unique: true, + }) + public invitationCode: string; + // Relation with RoleTypeEntity + @ManyToOne(() => RoleTypeEntity, (roleType) => roleType.invitedUsers, { + nullable: false, + onDelete: 'CASCADE', + }) + public roleType: RoleTypeEntity; + @OneToOne(() => UserEntity, (user) => user.inviteUser, { nullable: true }) + @JoinColumn({ name: 'user_uuid' }) // Foreign key in InviteUserEntity + user: UserEntity; + @OneToMany( + () => InviteUserSpaceEntity, + (inviteUserSpace) => inviteUserSpace.inviteUser, + ) + spaces: InviteUserSpaceEntity[]; + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} +@Entity({ name: 'invite-user-space' }) +@Unique(['inviteUser', 'space']) +export class InviteUserSpaceEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', + nullable: false, + }) + public uuid: string; + + @ManyToOne(() => InviteUserEntity, (inviteUser) => inviteUser.spaces) + @JoinColumn({ name: 'invite_user_uuid' }) + public inviteUser: InviteUserEntity; + + @ManyToOne(() => SpaceEntity, (space) => space.invitedUsers) + @JoinColumn({ name: 'space_uuid' }) + public space: SpaceEntity; + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/Invite-user/entities/index.ts b/libs/common/src/modules/Invite-user/entities/index.ts new file mode 100644 index 0000000..3f0da22 --- /dev/null +++ b/libs/common/src/modules/Invite-user/entities/index.ts @@ -0,0 +1 @@ +export * from './Invite-user.entity'; diff --git a/libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts b/libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts new file mode 100644 index 0000000..3df7390 --- /dev/null +++ b/libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts @@ -0,0 +1,16 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { InviteUserEntity, InviteUserSpaceEntity } from '../entities/'; + +@Injectable() +export class InviteUserRepository extends Repository { + constructor(private dataSource: DataSource) { + super(InviteUserEntity, dataSource.createEntityManager()); + } +} +@Injectable() +export class InviteUserSpaceRepository extends Repository { + constructor(private dataSource: DataSource) { + super(InviteUserSpaceEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/Invite-user/repositories/index.ts b/libs/common/src/modules/Invite-user/repositories/index.ts new file mode 100644 index 0000000..a60f19a --- /dev/null +++ b/libs/common/src/modules/Invite-user/repositories/index.ts @@ -0,0 +1 @@ +export * from './Invite-user.repository'; diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 103f0a2..cb8367c 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -15,6 +15,7 @@ import { SubspaceEntity } from './subspace.entity'; import { SpaceLinkEntity } from './space-link.entity'; import { SpaceProductEntity } from './space-product.entity'; import { SceneEntity } from '../../scene/entities'; +import { InviteUserSpaceEntity } from '../../Invite-user/entities'; @Entity({ name: 'space' }) @Unique(['invitationCode']) @@ -97,7 +98,11 @@ export class SpaceEntity extends AbstractEntity { @OneToMany(() => SceneEntity, (scene) => scene.space) scenes: SceneEntity[]; - + @OneToMany( + () => InviteUserSpaceEntity, + (inviteUserSpace) => inviteUserSpace.space, + ) + invitedUsers: InviteUserSpaceEntity[]; constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/user/dtos/user.dto.ts b/libs/common/src/modules/user/dtos/user.dto.ts index 0a4bda2..01c0c24 100644 --- a/libs/common/src/modules/user/dtos/user.dto.ts +++ b/libs/common/src/modules/user/dtos/user.dto.ts @@ -58,20 +58,6 @@ export class UserOtpDto { public expiryTime: string; } -export class UserRoleDto { - @IsString() - @IsNotEmpty() - public uuid: string; - - @IsString() - @IsNotEmpty() - public userUuid: string; - - @IsString() - @IsNotEmpty() - public roleTypeUuid: string; -} - export class UserSpaceDto { @IsString() @IsNotEmpty() diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index c9a11e6..04697fa 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -2,15 +2,16 @@ import { Column, DeleteDateColumn, Entity, + JoinColumn, ManyToOne, OneToMany, + OneToOne, Unique, } from 'typeorm'; import { UserDto, UserNotificationDto, UserOtpDto, - UserRoleDto, UserSpaceDto, } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; @@ -26,6 +27,8 @@ import { OtpType } from '../../../../src/constants/otp-type.enum'; import { RoleTypeEntity } from '../../role-type/entities'; import { SpaceEntity } from '../../space/entities'; import { VisitorPasswordEntity } from '../../visitor-password/entities'; +import { InviteUserEntity } from '../../Invite-user/entities'; +import { ProjectEntity } from '../../project/entities'; @Entity({ name: 'user' }) export class UserEntity extends AbstractEntity { @@ -100,10 +103,7 @@ export class UserEntity extends AbstractEntity { (deviceUserNotification) => deviceUserNotification.user, ) deviceUserNotification: DeviceNotificationEntity[]; - @OneToMany(() => UserRoleEntity, (role) => role.user, { - nullable: true, - }) - roles: UserRoleEntity[]; + @ManyToOne(() => RegionEntity, (region) => region.users, { nullable: true }) region: RegionEntity; @ManyToOne(() => TimeZoneEntity, (timezone) => timezone.users, { @@ -116,6 +116,21 @@ export class UserEntity extends AbstractEntity { ) public visitorPasswords: VisitorPasswordEntity[]; + @ManyToOne(() => RoleTypeEntity, (roleType) => roleType.users, { + nullable: true, + }) + public roleType: RoleTypeEntity; + @OneToOne(() => InviteUserEntity, (inviteUser) => inviteUser.user, { + nullable: true, + }) + @JoinColumn({ name: 'invite_user_uuid' }) + inviteUser: InviteUserEntity; + + @ManyToOne(() => ProjectEntity, (project) => project.users, { + nullable: true, + }) + @JoinColumn({ name: 'project_uuid' }) + public project: ProjectEntity; constructor(partial: Partial) { super(); Object.assign(this, partial); @@ -125,7 +140,7 @@ export class UserEntity extends AbstractEntity { @Entity({ name: 'user-notification' }) @Unique(['user', 'subscriptionUuid']) export class UserNotificationEntity extends AbstractEntity { - @ManyToOne(() => UserEntity, (user) => user.roles, { + @ManyToOne(() => UserEntity, (user) => user.roleType, { nullable: false, }) user: UserEntity; @@ -178,25 +193,6 @@ export class UserOtpEntity extends AbstractEntity { } } -@Entity({ name: 'user-role' }) -@Unique(['user', 'roleType']) -export class UserRoleEntity extends AbstractEntity { - @ManyToOne(() => UserEntity, (user) => user.roles, { - nullable: false, - }) - user: UserEntity; - - @ManyToOne(() => RoleTypeEntity, (roleType) => roleType.roles, { - nullable: false, - }) - roleType: RoleTypeEntity; - - constructor(partial: Partial) { - super(); - Object.assign(this, partial); - } -} - @Entity({ name: 'user-space' }) @Unique(['user', 'space']) export class UserSpaceEntity extends AbstractEntity { diff --git a/libs/common/src/modules/user/repositories/user.repository.ts b/libs/common/src/modules/user/repositories/user.repository.ts index ffc1aa1..d2303f9 100644 --- a/libs/common/src/modules/user/repositories/user.repository.ts +++ b/libs/common/src/modules/user/repositories/user.repository.ts @@ -4,7 +4,6 @@ import { UserEntity, UserNotificationEntity, UserOtpEntity, - UserRoleEntity, UserSpaceEntity, } from '../entities/'; @@ -29,13 +28,6 @@ export class UserOtpRepository extends Repository { } } -@Injectable() -export class UserRoleRepository extends Repository { - constructor(private dataSource: DataSource) { - super(UserRoleEntity, dataSource.createEntityManager()); - } -} - @Injectable() export class UserSpaceRepository extends Repository { constructor(private dataSource: DataSource) { diff --git a/libs/common/src/seed/seeder.module.ts b/libs/common/src/seed/seeder.module.ts index eeb245b..6de5585 100644 --- a/libs/common/src/seed/seeder.module.ts +++ b/libs/common/src/seed/seeder.module.ts @@ -10,7 +10,6 @@ import { RoleTypeSeeder } from './services/role.type.seeder'; import { SpaceRepositoryModule } from '../modules/space/space.repository.module'; import { SuperAdminSeeder } from './services/supper.admin.seeder'; import { UserRepository } from '../modules/user/repositories'; -import { UserRoleRepository } from '../modules/user/repositories'; import { UserRepositoryModule } from '../modules/user/user.repository.module'; import { RegionSeeder } from './services/regions.seeder'; import { RegionRepository } from '../modules/region/repositories'; @@ -28,7 +27,6 @@ import { SceneIconRepository } from '../modules/scene/repositories'; RoleTypeRepository, SuperAdminSeeder, UserRepository, - UserRoleRepository, RegionSeeder, RegionRepository, TimeZoneSeeder, diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts new file mode 100644 index 0000000..931c163 --- /dev/null +++ b/src/invite-user/services/invite-user.service.ts @@ -0,0 +1,103 @@ +import { InviteUserSpaceRepository } from './../../../libs/common/src/modules/Invite-user/repositories/Invite-user.repository'; +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { AddUserInvitationDto } from '../dtos'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { InviteUserRepository } from '@app/common/modules/Invite-user/repositories'; +import { UserStatusEnum } from '@app/common/constants/user-status.enum'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { generateRandomString } from '@app/common/helper/randomString'; +import { IsNull, Not } from 'typeorm'; +import { DataSource } from 'typeorm'; +import { UserEntity } from '@app/common/modules/user/entities'; + +@Injectable() +export class InviteUserService { + constructor( + private readonly inviteUserRepository: InviteUserRepository, + private readonly inviteUserSpaceRepository: InviteUserSpaceRepository, + private readonly dataSource: DataSource, + ) {} + + async createUserInvitation( + dto: AddUserInvitationDto, + ): Promise { + const { + firstName, + lastName, + email, + jobTitle, + phoneNumber, + roleUuid, + spaceUuids, + } = dto; + + const invitationCode = generateRandomString(6); + const queryRunner = this.dataSource.createQueryRunner(); + + await queryRunner.startTransaction(); + + try { + const userRepo = queryRunner.manager.getRepository(UserEntity); + + const user = await userRepo.findOne({ + where: { + email, + project: Not(IsNull()), + }, + }); + + if (user) { + throw new HttpException( + 'User already has a project', + HttpStatus.BAD_REQUEST, + ); + } + + const inviteUser = this.inviteUserRepository.create({ + firstName, + lastName, + email, + jobTitle, + phoneNumber, + roleType: { uuid: roleUuid }, + status: UserStatusEnum.INVITED, + invitationCode, + }); + + const invitedUser = await queryRunner.manager.save(inviteUser); + + const spacePromises = spaceUuids.map(async (spaceUuid) => { + const inviteUserSpace = this.inviteUserSpaceRepository.create({ + inviteUser: { uuid: invitedUser.uuid }, + space: { uuid: spaceUuid }, + }); + return queryRunner.manager.save(inviteUserSpace); + }); + + await Promise.all(spacePromises); + + await queryRunner.commitTransaction(); + + return new SuccessResponseDto({ + statusCode: HttpStatus.CREATED, + success: true, + data: { + invitationCode: invitedUser.invitationCode, + }, + message: 'User invited successfully', + }); + } catch (error) { + await queryRunner.rollbackTransaction(); + if (error instanceof HttpException) { + throw error; + } + console.error('Error creating user invitation:', error); + throw new HttpException( + error.message || 'An unexpected error occurred while inviting the user', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } finally { + await queryRunner.release(); + } + } +} From 64027d3a16c0e978f0cd0278da4e55cd875a2f50 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:19:14 -0600 Subject: [PATCH 058/247] Add permission and role management features --- .../src/constants/permissions-mapping.ts | 43 ++++++ libs/common/src/constants/role-permissions.ts | 130 ++++++++++++++++++ libs/common/src/database/database.module.ts | 8 +- .../project/entities/project.entity.ts | 4 + .../role-type/entities/role.type.entity.ts | 11 +- .../modules/user/user.repository.module.ts | 2 - src/app.module.ts | 6 +- src/auth/auth.module.ts | 6 +- src/auth/services/user-auth.service.ts | 5 +- src/decorators/permissions.decorator.ts | 4 + src/guards/permissions.guard.ts | 44 ++++++ src/invite-user/controllers/index.ts | 1 + .../controllers/invite-user.controller.ts | 34 +++++ src/invite-user/dtos/add.invite-user.dto.ts | 75 ++++++++++ src/invite-user/dtos/index.ts | 1 + src/invite-user/invite-user.module.ts | 23 ++++ src/invite-user/services/index.ts | 1 + src/permission/controllers/index.ts | 1 + .../controllers/permission.controller.ts | 24 ++++ src/permission/permission.module.ts | 14 ++ src/permission/services/index.ts | 1 + src/permission/services/permission.service.ts | 52 +++++++ 22 files changed, 473 insertions(+), 17 deletions(-) create mode 100644 libs/common/src/constants/permissions-mapping.ts create mode 100644 libs/common/src/constants/role-permissions.ts create mode 100644 src/decorators/permissions.decorator.ts create mode 100644 src/guards/permissions.guard.ts create mode 100644 src/invite-user/controllers/index.ts create mode 100644 src/invite-user/controllers/invite-user.controller.ts create mode 100644 src/invite-user/dtos/add.invite-user.dto.ts create mode 100644 src/invite-user/dtos/index.ts create mode 100644 src/invite-user/invite-user.module.ts create mode 100644 src/invite-user/services/index.ts create mode 100644 src/permission/controllers/index.ts create mode 100644 src/permission/controllers/permission.controller.ts create mode 100644 src/permission/permission.module.ts create mode 100644 src/permission/services/index.ts create mode 100644 src/permission/services/permission.service.ts diff --git a/libs/common/src/constants/permissions-mapping.ts b/libs/common/src/constants/permissions-mapping.ts new file mode 100644 index 0000000..a9db542 --- /dev/null +++ b/libs/common/src/constants/permissions-mapping.ts @@ -0,0 +1,43 @@ +export const PermissionMapping = { + DEVICE_MANAGEMENT: { + DEVICE: ['SINGLE_CONTROL', 'VIEW', 'DELETE', 'UPDATE', 'BATCH_CONTROL'], + FIRMWARE: ['CONTROL', 'VIEW'], + }, + COMMUNITY_MANAGEMENT: { + COMMUNITY: ['VIEW', 'ADD', 'UPDATE', 'DELETE'], + }, + SPACE_MANAGEMENT: { + SPACE: [ + 'VIEW', + 'ADD', + 'UPDATE', + 'DELETE', + 'MODULE_ADD', + 'ASSIGN_USER_TO_SPACE', + 'DELETE_USER_FROM_SPACE', + ], + SUBSPACE: [ + 'VIEW', + 'ADD', + 'UPDATE', + 'DELETE', + 'ASSIGN_DEVICE_TO_SUBSPACE', + 'DELETE_DEVICE_FROM_SUBSPACE', + ], + }, + DEVICE_WIZARD: { + DEVICE_WIZARD: ['VIEW_DEVICE_WIZARD'], + SPACE_DEVICE: ['VIEW_DEVICE_IN_SPACE', 'ASSIGN_DEVICE_TO_SPACE'], + SUBSPACE_DEVICE: ['VIEW_DEVICE_IN_SUBSPACE', 'UPDATE_DEVICE_IN_SUBSPACE'], + }, + AUTOMATION_MANAGEMENT: { + AUTOMATION: ['VIEW', 'ADD', 'UPDATE', 'DELETE', 'CONTROL'], + SCENES: ['VIEW', 'ADD', 'UPDATE', 'DELETE', 'CONTROL'], + }, + VISITOR_PASSWORD_MANAGEMENT: { + VISITOR_PASSWORD: ['VIEW', 'ADD', 'UPDATE', 'DELETE'], + }, + USER_MANAGEMENT: { + USER: ['ADD'], + }, +}; diff --git a/libs/common/src/constants/role-permissions.ts b/libs/common/src/constants/role-permissions.ts new file mode 100644 index 0000000..ed61e5d --- /dev/null +++ b/libs/common/src/constants/role-permissions.ts @@ -0,0 +1,130 @@ +import { RoleType } from './role.type.enum'; + +export const RolePermissions = { + [RoleType.SUPER_ADMIN]: [ + 'DEVICE_SINGLE_CONTROL', + 'DEVICE_VIEW', + 'DEVICE_DELETE', + 'DEVICE_UPDATE', + 'DEVICE_BATCH_CONTROL', + 'COMMUNITY_VIEW', + 'COMMUNITY_ADD', + 'COMMUNITY_UPDATE', + 'COMMUNITY_DELETE', + 'FIRMWARE_CONTROL', + 'SPACE_VIEW', + 'SPACE_ADD', + 'SPACE_UPDATE', + 'SPACE_DELETE', + 'SPACE_MODULE_ADD', + 'ASSIGN_USER_TO_SPACE', + 'DELETE_USER_FROM_SPACE', + 'SUBSPACE_VIEW', + 'SUBSPACE_ADD', + 'SUBSPACE_UPDATE', + 'SUBSPACE_DELETE', + 'ASSIGN_DEVICE_TO_SUBSPACE', + 'DELETE_DEVICE_FROM_SUBSPACE', + 'VIEW_DEVICE_WIZARD', + 'VIEW_DEVICE_IN_SUBSPACE', + 'VIEW_DEVICE_IN_SPACE', + 'UPDATE_DEVICE_IN_SUBSPACE', + 'ASSIGN_DEVICE_TO_SPACE', + 'AUTOMATION_VIEW', + 'AUTOMATION_ADD', + 'AUTOMATION_UPDATE', + 'AUTOMATION_DELETE', + 'AUTOMATION_CONTROL', + 'SCENES_VIEW', + 'SCENES_ADD', + 'SCENES_UPDATE', + 'SCENES_DELETE', + 'SCENES_CONTROL', + 'VISITOR_PASSWORD_VIEW', + 'VISITOR_PASSWORD_ADD', + 'USER_ADD', + ], + [RoleType.ADMIN]: [ + 'DEVICE_SINGLE_CONTROL', + 'DEVICE_VIEW', + 'DEVICE_DELETE', + 'DEVICE_UPDATE', + 'DEVICE_BATCH_CONTROL', + 'COMMUNITY_VIEW', + 'COMMUNITY_ADD', + 'COMMUNITY_UPDATE', + 'COMMUNITY_DELETE', + 'FIRMWARE_CONTROL', + 'SPACE_VIEW', + 'SPACE_ADD', + 'SPACE_UPDATE', + 'SPACE_DELETE', + 'SPACE_MODULE_ADD', + 'ASSIGN_USER_TO_SPACE', + 'DELETE_USER_FROM_SPACE', + 'SUBSPACE_VIEW', + 'SUBSPACE_ADD', + 'SUBSPACE_UPDATE', + 'SUBSPACE_DELETE', + 'ASSIGN_DEVICE_TO_SUBSPACE', + 'DELETE_DEVICE_FROM_SUBSPACE', + 'VIEW_DEVICE_WIZARD', + 'VIEW_DEVICE_IN_SUBSPACE', + 'VIEW_DEVICE_IN_SPACE', + 'UPDATE_DEVICE_IN_SUBSPACE', + 'ASSIGN_DEVICE_TO_SPACE', + 'AUTOMATION_VIEW', + 'AUTOMATION_ADD', + 'AUTOMATION_UPDATE', + 'AUTOMATION_DELETE', + 'AUTOMATION_CONTROL', + 'SCENES_VIEW', + 'SCENES_ADD', + 'SCENES_UPDATE', + 'SCENES_DELETE', + 'SCENES_CONTROL', + 'VISITOR_PASSWORD_VIEW', + 'VISITOR_PASSWORD_ADD', + 'USER_ADD', + ], + [RoleType.SPACE_MEMBER]: [ + 'DEVICE_SINGLE_CONTROL', + 'DEVICE_VIEW', + 'SPACE_VIEW', + 'SUBSPACE_VIEW', + 'VIEW_DEVICE_WIZARD', + 'VIEW_DEVICE_IN_SUBSPACE', + 'VIEW_DEVICE_IN_SPACE', + 'AUTOMATION_VIEW', + 'AUTOMATION_CONTROL', + 'SCENES_VIEW', + 'SCENES_CONTROL', + 'VISITOR_PASSWORD_VIEW', + ], + [RoleType.SPACE_OWNER]: [ + 'DEVICE_SINGLE_CONTROL', + 'DEVICE_VIEW', + 'FIRMWARE_CONTROL', + 'FIRMWARE_VIEW', + 'SPACE_VIEW', + 'SPACE_MEMBER_ADD', + 'SUBSPACE_VIEW', + 'SUBSPACE_ADD', + 'SUBSPACE_UPDATE', + 'SUBSPACE_DELETE', + 'AUTOMATION_VIEW', + 'AUTOMATION_ADD', + 'AUTOMATION_UPDATE', + 'AUTOMATION_DELETE', + 'AUTOMATION_CONTROL', + 'SCENES_VIEW', + 'SCENES_ADD', + 'SCENES_UPDATE', + 'SCENES_DELETE', + 'SCENES_CONTROL', + 'VISITOR_PASSWORD_VIEW', + 'VISITOR_PASSWORD_ADD', + 'VISITOR_PASSWORD_UPDATE', + 'VISITOR_PASSWORD_DELETE', + ], +}; diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 44fd854..eeabcd4 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -15,7 +15,6 @@ import { } from '../modules/space/entities'; import { UserSpaceEntity } from '../modules/user/entities'; import { DeviceUserPermissionEntity } from '../modules/device/entities'; -import { UserRoleEntity } from '../modules/user/entities'; import { RoleTypeEntity } from '../modules/role-type/entities'; import { UserNotificationEntity } from '../modules/user/entities'; import { DeviceNotificationEntity } from '../modules/device/entities'; @@ -34,6 +33,10 @@ import { SpaceProductModelEntity, SubspaceModelEntity, } from '../modules/space-model/entities'; +import { + InviteUserEntity, + InviteUserSpaceEntity, +} from '../modules/Invite-user/entities'; @Module({ imports: [ TypeOrmModule.forRootAsync({ @@ -63,7 +66,6 @@ import { SpaceProductEntity, UserSpaceEntity, DeviceUserPermissionEntity, - UserRoleEntity, RoleTypeEntity, UserNotificationEntity, DeviceNotificationEntity, @@ -78,6 +80,8 @@ import { SpaceProductModelEntity, SpaceProductItemModelEntity, SubspaceModelEntity, + InviteUserEntity, + InviteUserSpaceEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), diff --git a/libs/common/src/modules/project/entities/project.entity.ts b/libs/common/src/modules/project/entities/project.entity.ts index f7213a2..01fba17 100644 --- a/libs/common/src/modules/project/entities/project.entity.ts +++ b/libs/common/src/modules/project/entities/project.entity.ts @@ -3,6 +3,7 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { ProjectDto } from '../dtos'; import { CommunityEntity } from '../../community/entities'; import { SpaceModelEntity } from '../../space-model'; +import { UserEntity } from '../../user/entities'; @Entity({ name: 'project' }) @Unique(['name']) @@ -28,6 +29,9 @@ export class ProjectEntity extends AbstractEntity { @OneToMany(() => CommunityEntity, (community) => community.project) communities: CommunityEntity[]; + @OneToMany(() => UserEntity, (user) => user.project) + public users: UserEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/role-type/entities/role.type.entity.ts b/libs/common/src/modules/role-type/entities/role.type.entity.ts index 10f30bd..b7289a3 100644 --- a/libs/common/src/modules/role-type/entities/role.type.entity.ts +++ b/libs/common/src/modules/role-type/entities/role.type.entity.ts @@ -2,7 +2,8 @@ import { Column, Entity, OneToMany, Unique } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { RoleTypeDto } from '../dtos/role.type.dto'; import { RoleType } from '@app/common/constants/role.type.enum'; -import { UserRoleEntity } from '../../user/entities'; +import { UserEntity } from '../../user/entities'; +import { InviteUserEntity } from '../../Invite-user/entities'; @Entity({ name: 'role-type' }) @Unique(['type']) @@ -12,10 +13,14 @@ export class RoleTypeEntity extends AbstractEntity { enum: Object.values(RoleType), }) type: string; - @OneToMany(() => UserRoleEntity, (role) => role.roleType, { + @OneToMany(() => UserEntity, (inviteUser) => inviteUser.roleType, { nullable: true, }) - roles: UserRoleEntity[]; + users: UserEntity[]; + @OneToMany(() => InviteUserEntity, (inviteUser) => inviteUser.roleType, { + nullable: true, + }) + invitedUsers: InviteUserEntity[]; constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/user/user.repository.module.ts b/libs/common/src/modules/user/user.repository.module.ts index 11cefe0..e7f7d3e 100644 --- a/libs/common/src/modules/user/user.repository.module.ts +++ b/libs/common/src/modules/user/user.repository.module.ts @@ -4,7 +4,6 @@ import { UserEntity, UserNotificationEntity, UserOtpEntity, - UserRoleEntity, UserSpaceEntity, } from './entities'; @@ -17,7 +16,6 @@ import { UserEntity, UserNotificationEntity, UserOtpEntity, - UserRoleEntity, UserSpaceEntity, ]), ], diff --git a/src/app.module.ts b/src/app.module.ts index ac06b84..3606c0a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -7,7 +7,6 @@ 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 { RoleModule } from './role/role.module'; import { SeederModule } from '@app/common/seed/seeder.module'; import { UserNotificationModule } from './user-notification/user-notification.module'; import { DeviceMessagesSubscriptionModule } from './device-messages/device-messages.module'; @@ -24,6 +23,8 @@ import { SpaceModule } from './space/space.module'; import { ProductModule } from './product'; import { ProjectModule } from './project'; import { SpaceModelModule } from './space-model'; +import { InviteUserModule } from './invite-user/invite-user.module'; +import { PermissionModule } from './permission/permission.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -31,7 +32,7 @@ import { SpaceModelModule } from './space-model'; }), AuthenticationModule, UserModule, - RoleModule, + InviteUserModule, CommunityModule, SpaceModule, @@ -51,6 +52,7 @@ import { SpaceModelModule } from './space-model'; ScheduleModule, ProductModule, ProjectModule, + PermissionModule, ], providers: [ { diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 66d335d..4af4688 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -6,10 +6,7 @@ import { UserAuthController } from './controllers'; import { UserAuthService } from './services'; import { UserRepository } from '@app/common/modules/user/repositories'; import { UserSessionRepository } from '@app/common/modules/session/repositories/session.repository'; -import { - UserRoleRepository, - UserOtpRepository, -} from '@app/common/modules/user/repositories'; +import { UserOtpRepository } from '@app/common/modules/user/repositories'; import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; @Module({ @@ -20,7 +17,6 @@ import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; UserRepository, UserSessionRepository, UserOtpRepository, - UserRoleRepository, RoleTypeRepository, ], exports: [UserAuthService], diff --git a/src/auth/services/user-auth.service.ts b/src/auth/services/user-auth.service.ts index 80be3c2..b9b3ed3 100644 --- a/src/auth/services/user-auth.service.ts +++ b/src/auth/services/user-auth.service.ts @@ -134,13 +134,12 @@ export class UserAuthService { isLoggedOut: false, }), ]); + const res = await this.authService.login({ email: user.email, userId: user.uuid, uuid: user.uuid, - roles: user?.roles?.map((role) => { - return { uuid: role.uuid, type: role.roleType.type }; - }), + role: user.roleType, sessionId: session[1].uuid, }); return res; diff --git a/src/decorators/permissions.decorator.ts b/src/decorators/permissions.decorator.ts new file mode 100644 index 0000000..ad21bf4 --- /dev/null +++ b/src/decorators/permissions.decorator.ts @@ -0,0 +1,4 @@ +import { SetMetadata } from '@nestjs/common'; + +export const Permissions = (...permissions: string[]) => + SetMetadata('permissions', permissions); diff --git a/src/guards/permissions.guard.ts b/src/guards/permissions.guard.ts new file mode 100644 index 0000000..4075943 --- /dev/null +++ b/src/guards/permissions.guard.ts @@ -0,0 +1,44 @@ +import { Injectable, ExecutionContext } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; +import { Reflector } from '@nestjs/core'; +import { RolePermissions } from '@app/common/constants/role-permissions'; +import { RoleType } from '@app/common/constants/role.type.enum'; + +@Injectable() +export class PermissionsGuard extends AuthGuard('jwt') { + constructor(private reflector: Reflector) { + super(); + } + + async canActivate(context: ExecutionContext): Promise { + // First, run the AuthGuard logic to validate the JWT + const isAuthenticated = await super.canActivate(context); + if (!isAuthenticated) { + return false; + } + + // Authorization logic + const requiredPermissions = this.reflector.get( + 'permissions', + context.getHandler(), + ); + + if (!requiredPermissions) { + return true; // Allow if no permissions are specified + } + + const request = context.switchToHttp().getRequest(); + const user = request.user; // User is now available after AuthGuard + console.log('user', user); + + const userRole = user?.role.type as RoleType; + if (!userRole || !RolePermissions[userRole]) { + return false; // Deny if role or permissions are missing + } + + const userPermissions = RolePermissions[userRole]; + + // Check if the user has the required permissions + return requiredPermissions.every((perm) => userPermissions.includes(perm)); + } +} diff --git a/src/invite-user/controllers/index.ts b/src/invite-user/controllers/index.ts new file mode 100644 index 0000000..045b43b --- /dev/null +++ b/src/invite-user/controllers/index.ts @@ -0,0 +1 @@ +export * from './invite-user.controller'; diff --git a/src/invite-user/controllers/invite-user.controller.ts b/src/invite-user/controllers/invite-user.controller.ts new file mode 100644 index 0000000..7c25450 --- /dev/null +++ b/src/invite-user/controllers/invite-user.controller.ts @@ -0,0 +1,34 @@ +import { InviteUserService } from '../services/invite-user.service'; +import { Body, Controller, Post, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; +import { AddUserInvitationDto } from '../dtos/add.invite-user.dto'; +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; + +@ApiTags('Invite User Module') +@Controller({ + version: '1', + path: ControllerRoute.INVITE_USER.ROUTE, +}) +export class InviteUserController { + constructor(private readonly inviteUserService: InviteUserService) {} + + @ApiBearerAuth() + @UseGuards(PermissionsGuard) + @Permissions('USER_ADD') + @Post() + @ApiOperation({ + summary: ControllerRoute.INVITE_USER.ACTIONS.CREATE_USER_INVITATION_SUMMARY, + description: + ControllerRoute.INVITE_USER.ACTIONS.CREATE_USER_INVITATION_DESCRIPTION, + }) + async createUserInvitation( + @Body() addUserInvitationDto: AddUserInvitationDto, + ): Promise { + return await this.inviteUserService.createUserInvitation( + addUserInvitationDto, + ); + } +} diff --git a/src/invite-user/dtos/add.invite-user.dto.ts b/src/invite-user/dtos/add.invite-user.dto.ts new file mode 100644 index 0000000..e47758d --- /dev/null +++ b/src/invite-user/dtos/add.invite-user.dto.ts @@ -0,0 +1,75 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + ArrayMinSize, + IsArray, + IsNotEmpty, + IsOptional, + IsString, +} from 'class-validator'; + +export class AddUserInvitationDto { + @ApiProperty({ + description: 'The first name of the user', + example: 'John', + required: true, + }) + @IsString() + @IsNotEmpty() + public firstName: string; + + @ApiProperty({ + description: 'The last name of the user', + example: 'Doe', + required: true, + }) + @IsString() + @IsNotEmpty() + public lastName: string; + + @ApiProperty({ + description: 'The email of the user', + example: 'OqM9A@example.com', + required: true, + }) + @IsString() + @IsNotEmpty() + public email: string; + + @ApiProperty({ + description: 'The job title of the user', + example: 'Software Engineer', + required: true, + }) + @IsString() + @IsNotEmpty() + public jobTitle: string; + + @ApiProperty({ + description: 'The phone number of the user', + example: '+1234567890', + required: true, + }) + @IsString() + @IsOptional() + public phoneNumber?: string; + + @ApiProperty({ + description: 'The role uuid of the user', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + required: true, + }) + @IsString() + @IsNotEmpty() + public roleUuid: string; + @ApiProperty({ + description: 'The array of space UUIDs (at least one required)', + example: ['b5f3c9d2-58b7-4377-b3f7-60acb711d5d9'], + required: true, + }) + @IsArray() + @ArrayMinSize(1) + public spaceUuids: string[]; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/invite-user/dtos/index.ts b/src/invite-user/dtos/index.ts new file mode 100644 index 0000000..6cdec2a --- /dev/null +++ b/src/invite-user/dtos/index.ts @@ -0,0 +1 @@ +export * from './add.invite-user.dto'; diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts new file mode 100644 index 0000000..d7a19b6 --- /dev/null +++ b/src/invite-user/invite-user.module.ts @@ -0,0 +1,23 @@ +import { Module } from '@nestjs/common'; +import { InviteUserService } from './services/invite-user.service'; +import { InviteUserController } from './controllers/invite-user.controller'; +import { ConfigModule } from '@nestjs/config'; +import { + InviteUserRepository, + InviteUserSpaceRepository, +} from '@app/common/modules/Invite-user/repositories'; +import { UserRepository } from '@app/common/modules/user/repositories'; +import { InviteUserRepositoryModule } from '@app/common/modules/Invite-user/Invite-user.repository.module'; + +@Module({ + imports: [ConfigModule, InviteUserRepositoryModule], + controllers: [InviteUserController], + providers: [ + InviteUserService, + InviteUserRepository, + UserRepository, + InviteUserSpaceRepository, + ], + exports: [InviteUserService], +}) +export class InviteUserModule {} diff --git a/src/invite-user/services/index.ts b/src/invite-user/services/index.ts new file mode 100644 index 0000000..024e5a5 --- /dev/null +++ b/src/invite-user/services/index.ts @@ -0,0 +1 @@ +export * from './invite-user.service'; diff --git a/src/permission/controllers/index.ts b/src/permission/controllers/index.ts new file mode 100644 index 0000000..e78a8bd --- /dev/null +++ b/src/permission/controllers/index.ts @@ -0,0 +1 @@ +export * from './permission.controller'; diff --git a/src/permission/controllers/permission.controller.ts b/src/permission/controllers/permission.controller.ts new file mode 100644 index 0000000..9b07db2 --- /dev/null +++ b/src/permission/controllers/permission.controller.ts @@ -0,0 +1,24 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { PermissionService } from '../services'; + +@ApiTags('Permission Module') +@Controller({ + version: EnableDisableStatusEnum.ENABLED, + path: ControllerRoute.PERMISSION.ROUTE, +}) +export class PermissionController { + constructor(private readonly permissionService: PermissionService) {} + + @Get(':roleUuid') + @ApiOperation({ + summary: ControllerRoute.PERMISSION.ACTIONS.GET_PERMISSION_BY_ROLE_SUMMARY, + description: + ControllerRoute.PERMISSION.ACTIONS.GET_PERMISSION_BY_ROLE_DESCRIPTION, + }) + async getPermissionsByRole(@Param('roleUuid') roleUuid: string) { + return await this.permissionService.getPermissionsByRole(roleUuid); + } +} diff --git a/src/permission/permission.module.ts b/src/permission/permission.module.ts new file mode 100644 index 0000000..8c74871 --- /dev/null +++ b/src/permission/permission.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { CommonModule } from '@app/common'; +import { PermissionController } from './controllers'; +import { PermissionService } from './services'; +import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; + +@Module({ + imports: [ConfigModule, CommonModule], + controllers: [PermissionController], + providers: [PermissionService, RoleTypeRepository], + exports: [PermissionService], +}) +export class PermissionModule {} diff --git a/src/permission/services/index.ts b/src/permission/services/index.ts new file mode 100644 index 0000000..89f3aec --- /dev/null +++ b/src/permission/services/index.ts @@ -0,0 +1 @@ +export * from './permission.service'; diff --git a/src/permission/services/permission.service.ts b/src/permission/services/permission.service.ts new file mode 100644 index 0000000..33c5f18 --- /dev/null +++ b/src/permission/services/permission.service.ts @@ -0,0 +1,52 @@ +import { PermissionMapping } from '@app/common/constants/permissions-mapping'; +import { RolePermissions } from '@app/common/constants/role-permissions'; +import { RoleType } from '@app/common/constants/role.type.enum'; +import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; + +@Injectable() +export class PermissionService { + constructor(private readonly roleTypeRepository: RoleTypeRepository) {} + + async getPermissionsByRole(roleUuid: string) { + try { + const role = await this.roleTypeRepository.findOne({ + where: { + uuid: roleUuid, + }, + }); + + if (!role) { + throw new HttpException('Role not found', HttpStatus.NOT_FOUND); + } + + const permissions = this.mapPermissions(role.type.toString() as RoleType); + return permissions; + } catch (err) { + throw new HttpException( + err.message || 'Internal Server Error', + err.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + mapPermissions(role: RoleType): any[] { + const rolePermissions = RolePermissions[role]; // Permissions for the role + + const mappedPermissions = Object.entries(PermissionMapping).map( + ([title, subOptions]) => ({ + title, + subOptions: Object.entries(subOptions).map( + ([subTitle, permissions]) => ({ + title: subTitle, + subOptions: permissions.map((permission) => ({ + title: permission, + isChecked: rolePermissions.includes(`${subTitle}_${permission}`), // Check if the role has the permission + })), + }), + ), + }), + ); + + return mappedPermissions; + } +} From d169999675227265a19ebf5202029d095a51bf95 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 00:19:34 -0600 Subject: [PATCH 059/247] Replace JwtAuthGuard with PermissionsGuard and add specific permissions --- .../controllers/automation.controller.ts | 21 ++++-- .../controllers/community.controller.ts | 18 ++++-- src/device/controllers/device.controller.ts | 64 ++++++++++++------- src/group/controllers/group.controller.ts | 9 ++- src/scene/controllers/scene.controller.ts | 24 ++++--- .../controllers/space-model.controller.ts | 6 +- .../controllers/space-device.controller.ts | 6 +- .../controllers/space-scene.controller.ts | 6 +- .../controllers/space-user.controller.ts | 9 ++- src/space/controllers/space.controller.ts | 24 ++++--- .../subspace/subspace-device.controller.ts | 12 ++-- .../subspace/subspace.controller.ts | 18 ++++-- .../visitor-password.controller.ts | 21 ++++-- 13 files changed, 158 insertions(+), 80 deletions(-) diff --git a/src/automation/controllers/automation.controller.ts b/src/automation/controllers/automation.controller.ts index 8d9d1ab..265638c 100644 --- a/src/automation/controllers/automation.controller.ts +++ b/src/automation/controllers/automation.controller.ts @@ -16,10 +16,11 @@ import { UpdateAutomationDto, UpdateAutomationStatusDto, } from '../dtos/automation.dto'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { AutomationParamDto, SpaceParamDto } from '../dtos'; import { ControllerRoute } from '@app/common/constants/controller-route'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Automation Module') @Controller({ @@ -30,7 +31,8 @@ export class AutomationController { constructor(private readonly automationService: AutomationService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('AUTOMATION_ADD') @Post() @ApiOperation({ summary: ControllerRoute.AUTOMATION.ACTIONS.ADD_AUTOMATION_SUMMARY, @@ -48,7 +50,8 @@ export class AutomationController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('AUTOMATION_VIEW') @Get(':spaceUuid') @ApiOperation({ summary: ControllerRoute.AUTOMATION.ACTIONS.GET_AUTOMATION_BY_SPACE_SUMMARY, @@ -63,7 +66,8 @@ export class AutomationController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('AUTOMATION_VIEW') @Get('details/:automationUuid') @ApiOperation({ summary: ControllerRoute.AUTOMATION.ACTIONS.GET_AUTOMATION_DETAILS_SUMMARY, @@ -78,7 +82,8 @@ export class AutomationController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('AUTOMATION_DELETE') @Delete(':automationUuid') @ApiOperation({ summary: ControllerRoute.AUTOMATION.ACTIONS.DELETE_AUTOMATION_SUMMARY, @@ -94,7 +99,8 @@ export class AutomationController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('AUTOMATION_UPDATE') @Put(':automationUuid') @ApiOperation({ summary: ControllerRoute.AUTOMATION.ACTIONS.UPDATE_AUTOMATION_SUMMARY, @@ -118,7 +124,8 @@ export class AutomationController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('AUTOMATION_UPDATE') @Put('status/:automationUuid') @ApiOperation({ summary: diff --git a/src/community/controllers/community.controller.ts b/src/community/controllers/community.controller.ts index 5548e84..e823989 100644 --- a/src/community/controllers/community.controller.ts +++ b/src/community/controllers/community.controller.ts @@ -15,11 +15,12 @@ import { AddCommunityDto } from '../dtos/add.community.dto'; import { GetCommunityParams } from '../dtos/get.community.dto'; import { UpdateCommunityNameDto } from '../dtos/update.community.dto'; // import { CheckUserCommunityGuard } from 'src/guards/user.community.guard'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { ControllerRoute } from '@app/common/constants/controller-route'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; import { ProjectParam } from '../dtos'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Community Module') @Controller({ @@ -30,7 +31,8 @@ export class CommunityController { constructor(private readonly communityService: CommunityService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('COMMUNITY_ADD') @Post() @ApiOperation({ summary: ControllerRoute.COMMUNITY.ACTIONS.CREATE_COMMUNITY_SUMMARY, @@ -44,7 +46,8 @@ export class CommunityController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('COMMUNITY_VIEW') @ApiOperation({ summary: ControllerRoute.COMMUNITY.ACTIONS.GET_COMMUNITY_BY_ID_SUMMARY, description: @@ -58,7 +61,8 @@ export class CommunityController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('COMMUNITY_VIEW') @ApiOperation({ summary: ControllerRoute.COMMUNITY.ACTIONS.LIST_COMMUNITY_SUMMARY, description: ControllerRoute.COMMUNITY.ACTIONS.LIST_COMMUNITY_DESCRIPTION, @@ -72,7 +76,8 @@ export class CommunityController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('COMMUNITY_UPDATE') @ApiOperation({ summary: ControllerRoute.COMMUNITY.ACTIONS.UPDATE_COMMUNITY_SUMMARY, description: ControllerRoute.COMMUNITY.ACTIONS.UPDATE_COMMUNITY_DESCRIPTION, @@ -86,7 +91,8 @@ export class CommunityController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('COMMUNITY_DELETE') @Delete('/:communityUuid') @ApiOperation({ summary: ControllerRoute.COMMUNITY.ACTIONS.DELETE_COMMUNITY_SUMMARY, diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index a49b1f7..26b49ea 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -28,15 +28,15 @@ import { GetSceneFourSceneDeviceDto, } from '../dtos/control.device.dto'; import { CheckRoomGuard } from 'src/guards/room.guard'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { CheckDeviceGuard } from 'src/guards/device.guard'; -import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { CheckFourAndSixSceneDeviceTypeGuard } from 'src/guards/scene.device.type.guard'; import { ControllerRoute } from '@app/common/constants/controller-route'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { DeviceSceneParamDto } from '../dtos/device.param.dto'; import { DeleteSceneFromSceneDeviceDto } from '../dtos/delete.device.dto'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Device Module') @Controller({ @@ -46,7 +46,8 @@ import { DeleteSceneFromSceneDeviceDto } from '../dtos/delete.device.dto'; export class DeviceController { constructor(private readonly deviceService: DeviceService) {} @ApiBearerAuth() - @UseGuards(SuperAdminRoleGuard, CheckDeviceGuard) + @UseGuards(PermissionsGuard, CheckDeviceGuard) + @Permissions('ASSIGN_DEVICE_TO_SPACE') @Post() @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.ADD_DEVICE_TO_USER_SUMMARY, @@ -63,7 +64,8 @@ export class DeviceController { }; } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get('user/:userUuid') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_BY_USER_SUMMARY, @@ -74,7 +76,8 @@ export class DeviceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VIEW_DEVICE_IN_SPACE') @Get('space/:spaceUuid') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_BY_SPACE_UUID_SUMMARY, @@ -85,7 +88,8 @@ export class DeviceController { return await this.deviceService.getDevicesBySpaceUuid(spaceUuid); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckRoomGuard) + @UseGuards(PermissionsGuard, CheckRoomGuard) + @Permissions('UPDATE_DEVICE_IN_SUBSPACE') @Put('space') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_IN_ROOM_SUMMARY, @@ -108,7 +112,8 @@ export class DeviceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get(':deviceUuid') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_DETAILS_SUMMARY, @@ -125,7 +130,8 @@ export class DeviceController { ); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_UPDATE') @Put(':deviceUuid') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_SUMMARY, @@ -149,7 +155,8 @@ export class DeviceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get(':deviceUuid/functions') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_INSTRUCTION_SUMMARY, @@ -162,7 +169,8 @@ export class DeviceController { return await this.deviceService.getDeviceInstructionByDeviceId(deviceUuid); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get(':deviceUuid/functions/status') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_STATUS_SUMMARY, @@ -173,7 +181,8 @@ export class DeviceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_SINGLE_CONTROL') @Post(':deviceUuid/control') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.CONTROL_DEVICE_SUMMARY, @@ -186,7 +195,8 @@ export class DeviceController { return await this.deviceService.controlDevice(controlDeviceDto, deviceUuid); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('FIRMWARE_CONTROL') @Post(':deviceUuid/firmware/:firmwareVersion') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_FIRMWARE_SUMMARY, @@ -203,7 +213,8 @@ export class DeviceController { ); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get('gateway/:gatewayUuid/devices') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_IN_GATEWAY_SUMMARY, @@ -214,7 +225,8 @@ export class DeviceController { return await this.deviceService.getDevicesInGateway(gatewayUuid); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get() @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_ALL_DEVICES_SUMMARY, @@ -225,7 +237,8 @@ export class DeviceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get('report-logs/:deviceUuid') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICE_LOGS_SUMMARY, @@ -238,7 +251,8 @@ export class DeviceController { return await this.deviceService.getDeviceLogs(deviceUuid, query); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_BATCH_CONTROL') @Post('control/batch') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.BATCH_CONTROL_DEVICES_SUMMARY, @@ -251,7 +265,8 @@ export class DeviceController { return await this.deviceService.batchControlDevices(batchControlDevicesDto); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_BATCH_CONTROL') @Get('status/batch') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.BATCH_STATUS_DEVICES_SUMMARY, @@ -264,7 +279,8 @@ export class DeviceController { return await this.deviceService.batchStatusDevices(batchStatusDevicesDto); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_DELETE') @Post('factory/reset/:deviceUuid') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.BATCH_FACTORY_RESET_DEVICES_SUMMARY, @@ -279,7 +295,8 @@ export class DeviceController { ); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_VIEW') @Get(':powerClampUuid/power-clamp/status') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_POWER_CLAMP_STATUS_SUMMARY, @@ -294,7 +311,8 @@ export class DeviceController { ); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) + @UseGuards(PermissionsGuard, CheckFourAndSixSceneDeviceTypeGuard) + @Permissions('DEVICE_SINGLE_CONTROL') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.ADD_SCENE_TO_DEVICE_SUMMARY, description: ControllerRoute.DEVICE.ACTIONS.ADD_SCENE_TO_DEVICE_DESCRIPTION, @@ -317,7 +335,8 @@ export class DeviceController { }; } @ApiBearerAuth() - @UseGuards(JwtAuthGuard, CheckFourAndSixSceneDeviceTypeGuard) + @UseGuards(PermissionsGuard, CheckFourAndSixSceneDeviceTypeGuard) + @Permissions('DEVICE_VIEW') @Get(':deviceUuid/scenes') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_SCENES_BY_DEVICE_SUMMARY, @@ -334,7 +353,8 @@ export class DeviceController { ); } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DEVICE_DELETE') @Delete(':deviceUuid/scenes') @ApiOperation({ summary: diff --git a/src/group/controllers/group.controller.ts b/src/group/controllers/group.controller.ts index a5d7cd7..513bbeb 100644 --- a/src/group/controllers/group.controller.ts +++ b/src/group/controllers/group.controller.ts @@ -1,9 +1,10 @@ import { GroupService } from '../services/group.service'; import { Controller, Get, UseGuards, Param, Req } 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 { ControllerRoute } from '@app/common/constants/controller-route'; // Assuming this is where the routes are defined +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Group Module') @Controller({ @@ -14,7 +15,8 @@ export class GroupController { constructor(private readonly groupService: GroupService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VIEW_DEVICE_WIZARD') @Get(':spaceUuid') @ApiOperation({ summary: ControllerRoute.GROUP.ACTIONS.GET_GROUPS_BY_SPACE_UUID_SUMMARY, @@ -26,7 +28,8 @@ export class GroupController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VIEW_DEVICE_WIZARD') @Get(':spaceUuid/devices/:groupName') @ApiOperation({ summary: diff --git a/src/scene/controllers/scene.controller.ts b/src/scene/controllers/scene.controller.ts index eaf67ec..655efe5 100644 --- a/src/scene/controllers/scene.controller.ts +++ b/src/scene/controllers/scene.controller.ts @@ -16,11 +16,12 @@ import { AddSceneTapToRunDto, UpdateSceneTapToRunDto, } from '../dtos/scene.dto'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { SceneParamDto } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { ControllerRoute } from '@app/common/constants/controller-route'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Scene Module') @Controller({ @@ -31,7 +32,8 @@ export class SceneController { constructor(private readonly sceneService: SceneService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_ADD') @Post('tap-to-run') @ApiOperation({ summary: ControllerRoute.SCENE.ACTIONS.CREATE_TAP_TO_RUN_SCENE_SUMMARY, @@ -45,7 +47,8 @@ export class SceneController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_DELETE') @Delete('tap-to-run/:sceneUuid') @ApiOperation({ summary: ControllerRoute.SCENE.ACTIONS.DELETE_TAP_TO_RUN_SCENE_SUMMARY, @@ -59,7 +62,8 @@ export class SceneController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_CONTROL') @Post('tap-to-run/:sceneUuid/trigger') @ApiOperation({ summary: ControllerRoute.SCENE.ACTIONS.TRIGGER_TAP_TO_RUN_SCENE_SUMMARY, @@ -71,7 +75,8 @@ export class SceneController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_VIEW') @Get('tap-to-run/:sceneUuid') @ApiOperation({ summary: ControllerRoute.SCENE.ACTIONS.GET_TAP_TO_RUN_SCENE_SUMMARY, @@ -84,7 +89,8 @@ export class SceneController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_UPDATE') @Put('tap-to-run/:sceneUuid') @ApiOperation({ summary: ControllerRoute.SCENE.ACTIONS.UPDATE_TAP_TO_RUN_SCENE_SUMMARY, @@ -102,7 +108,8 @@ export class SceneController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_ADD') @Post('icon') async addSceneIcon(@Body() addSceneIconDto: AddSceneIconDto) { const tapToRunScene = await this.sceneService.addSceneIcon(addSceneIconDto); @@ -114,7 +121,8 @@ export class SceneController { }; } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_VIEW') @Get('icon') async getAllIcons() { const icons = await this.sceneService.getAllIcons(); diff --git a/src/space-model/controllers/space-model.controller.ts b/src/space-model/controllers/space-model.controller.ts index 8475b6c..bedb81e 100644 --- a/src/space-model/controllers/space-model.controller.ts +++ b/src/space-model/controllers/space-model.controller.ts @@ -4,8 +4,9 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { SpaceModelService } from '../services'; import { CreateSpaceModelDto } from '../dtos'; import { ProjectParam } from 'src/community/dtos'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Space Model Module') @Controller({ @@ -16,7 +17,8 @@ export class SpaceModelController { constructor(private readonly spaceModelService: SpaceModelService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_MODULE_ADD') @ApiOperation({ summary: ControllerRoute.SPACE_MODEL.ACTIONS.CREATE_SPACE_MODEL_SUMMARY, description: diff --git a/src/space/controllers/space-device.controller.ts b/src/space/controllers/space-device.controller.ts index 160a130..dbb1585 100644 --- a/src/space/controllers/space-device.controller.ts +++ b/src/space/controllers/space-device.controller.ts @@ -1,10 +1,11 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; import { Controller, Get, Param, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { GetSpaceParam } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SpaceDeviceService } from '../services'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Space Module') @Controller({ @@ -15,7 +16,8 @@ export class SpaceDeviceController { constructor(private readonly spaceDeviceService: SpaceDeviceService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VIEW_DEVICE_IN_SPACE') @ApiOperation({ summary: ControllerRoute.SPACE_DEVICES.ACTIONS.LIST_SPACE_DEVICE_SUMMARY, description: diff --git a/src/space/controllers/space-scene.controller.ts b/src/space/controllers/space-scene.controller.ts index 5517362..7d6a13d 100644 --- a/src/space/controllers/space-scene.controller.ts +++ b/src/space/controllers/space-scene.controller.ts @@ -1,11 +1,12 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { SpaceSceneService } from '../services'; import { GetSceneDto } from '../../scene/dtos'; import { GetSpaceParam } from '../dtos'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Space Module') @Controller({ @@ -16,7 +17,8 @@ export class SpaceSceneController { constructor(private readonly sceneService: SpaceSceneService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SCENES_VIEW') @ApiOperation({ summary: ControllerRoute.SPACE_SCENE.ACTIONS.GET_TAP_TO_RUN_SCENE_BY_SPACE_SUMMARY, diff --git a/src/space/controllers/space-user.controller.ts b/src/space/controllers/space-user.controller.ts index 9efdac8..faf887a 100644 --- a/src/space/controllers/space-user.controller.ts +++ b/src/space/controllers/space-user.controller.ts @@ -3,8 +3,9 @@ import { Controller, Delete, Param, Post, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { SpaceUserService } from '../services'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { UserSpaceParam } from '../dtos'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Space Module') @Controller({ @@ -16,7 +17,8 @@ export class SpaceUserController { @ApiBearerAuth() @Post('/:userUuid') - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('ASSIGN_USER_TO_SPACE') @ApiOperation({ summary: ControllerRoute.SPACE_USER.ACTIONS.ASSOCIATE_SPACE_USER_DESCRIPTION, @@ -31,7 +33,8 @@ export class SpaceUserController { @ApiBearerAuth() @Delete('/:userUuid') - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DELETE_USER_FROM_SPACE') @ApiOperation({ summary: ControllerRoute.SPACE_USER.ACTIONS.DISSOCIATE_SPACE_USER_SUMMARY, description: diff --git a/src/space/controllers/space.controller.ts b/src/space/controllers/space.controller.ts index d31bb23..18d556d 100644 --- a/src/space/controllers/space.controller.ts +++ b/src/space/controllers/space.controller.ts @@ -11,10 +11,11 @@ import { Put, UseGuards, } from '@nestjs/common'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { AddSpaceDto, CommunitySpaceParam, UpdateSpaceDto } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { GetSpaceParam } from '../dtos/get.space.param'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Space Module') @Controller({ @@ -25,7 +26,8 @@ export class SpaceController { constructor(private readonly spaceService: SpaceService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_ADD') @ApiOperation({ summary: ControllerRoute.SPACE.ACTIONS.CREATE_SPACE_SUMMARY, description: ControllerRoute.SPACE.ACTIONS.CREATE_SPACE_DESCRIPTION, @@ -42,7 +44,8 @@ export class SpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_VIEW') @ApiOperation({ summary: ControllerRoute.SPACE.ACTIONS.GET_COMMUNITY_SPACES_HIERARCHY_SUMMARY, @@ -57,7 +60,8 @@ export class SpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_DELETE') @ApiOperation({ summary: ControllerRoute.SPACE.ACTIONS.DELETE_SPACE_SUMMARY, description: ControllerRoute.SPACE.ACTIONS.DELETE_SPACE_DESCRIPTION, @@ -68,7 +72,8 @@ export class SpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_UPDATE') @Put('/:spaceUuid') @ApiOperation({ summary: ControllerRoute.SPACE.ACTIONS.UPDATE_SPACE_SUMMARY, @@ -82,7 +87,8 @@ export class SpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_VIEW') @ApiOperation({ summary: ControllerRoute.SPACE.ACTIONS.GET_SPACE_SUMMARY, description: ControllerRoute.SPACE.ACTIONS.GET_SPACE_DESCRIPTION, @@ -93,7 +99,8 @@ export class SpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_VIEW') @ApiOperation({ summary: ControllerRoute.SPACE.ACTIONS.GET_HEIRARCHY_SUMMARY, description: ControllerRoute.SPACE.ACTIONS.GET_HEIRARCHY_DESCRIPTION, @@ -107,7 +114,8 @@ export class SpaceController { //should it be post? @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SPACE_MEMBER_ADD') @ApiOperation({ summary: ControllerRoute.SPACE.ACTIONS.CREATE_INVITATION_CODE_SPACE_SUMMARY, description: diff --git a/src/space/controllers/subspace/subspace-device.controller.ts b/src/space/controllers/subspace/subspace-device.controller.ts index 189d490..1bb3db4 100644 --- a/src/space/controllers/subspace/subspace-device.controller.ts +++ b/src/space/controllers/subspace/subspace-device.controller.ts @@ -1,5 +1,4 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { Controller, Delete, @@ -12,6 +11,8 @@ import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { DeviceSubSpaceParam, GetSubSpaceParam } from '../../dtos'; import { SubspaceDeviceService } from 'src/space/services'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Space Module') @Controller({ @@ -22,7 +23,8 @@ export class SubSpaceDeviceController { constructor(private readonly subspaceDeviceService: SubspaceDeviceService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VIEW_DEVICE_IN_SUBSPACE') @ApiOperation({ summary: ControllerRoute.SUBSPACE_DEVICE.ACTIONS.LIST_SUBSPACE_DEVICE_SUMMARY, @@ -37,7 +39,8 @@ export class SubSpaceDeviceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('ASSIGN_DEVICE_TO_SUBSPACE') @ApiOperation({ summary: ControllerRoute.SUBSPACE_DEVICE.ACTIONS.ASSOCIATE_SUBSPACE_DEVICE_SUMMARY, @@ -53,7 +56,8 @@ export class SubSpaceDeviceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('DELETE_DEVICE_FROM_SUBSPACE') @ApiOperation({ summary: ControllerRoute.SUBSPACE_DEVICE.ACTIONS diff --git a/src/space/controllers/subspace/subspace.controller.ts b/src/space/controllers/subspace/subspace.controller.ts index 9f766c4..37e264b 100644 --- a/src/space/controllers/subspace/subspace.controller.ts +++ b/src/space/controllers/subspace/subspace.controller.ts @@ -14,8 +14,9 @@ import { SubSpaceService } from '../../services'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Space Module') @Controller({ @@ -26,7 +27,8 @@ export class SubSpaceController { constructor(private readonly subSpaceService: SubSpaceService) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SUBSPACE_ADD') @Post() @ApiOperation({ summary: ControllerRoute.SUBSPACE.ACTIONS.CREATE_SUBSPACE_SUMMARY, @@ -40,7 +42,8 @@ export class SubSpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SUBSPACE_VIEW') @ApiOperation({ summary: ControllerRoute.SUBSPACE.ACTIONS.LIST_SUBSPACES_SUMMARY, description: ControllerRoute.SUBSPACE.ACTIONS.LIST_SUBSPACES_DESCRIPTION, @@ -54,7 +57,8 @@ export class SubSpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SUBSPACE_VIEW') @ApiOperation({ summary: ControllerRoute.SUBSPACE.ACTIONS.GET_SUBSPACE_SUMMARY, description: ControllerRoute.SUBSPACE.ACTIONS.GET_SUBSPACE_DESCRIPTION, @@ -65,7 +69,8 @@ export class SubSpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SUBSPACE_UPDATE') @ApiOperation({ summary: ControllerRoute.SUBSPACE.ACTIONS.UPDATE_SUBSPACE_SUMMARY, description: ControllerRoute.SUBSPACE.ACTIONS.UPDATE_SUBSPACE_DESCRIPTION, @@ -79,7 +84,8 @@ export class SubSpaceController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('SUBSPACE_DELETE') @ApiOperation({ summary: ControllerRoute.SUBSPACE.ACTIONS.DELETE_SUBSPACE_SUMMARY, description: ControllerRoute.SUBSPACE.ACTIONS.DELETE_SUBSPACE_DESCRIPTION, diff --git a/src/vistor-password/controllers/visitor-password.controller.ts b/src/vistor-password/controllers/visitor-password.controller.ts index 55276be..a298114 100644 --- a/src/vistor-password/controllers/visitor-password.controller.ts +++ b/src/vistor-password/controllers/visitor-password.controller.ts @@ -15,9 +15,10 @@ import { AddDoorLockOnlineMultipleDto, AddDoorLockOnlineOneTimeDto, } from '../dtos/temp-pass.dto'; -import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { ControllerRoute } from '@app/common/constants/controller-route'; +import { PermissionsGuard } from 'src/guards/permissions.guard'; +import { Permissions } from 'src/decorators/permissions.decorator'; @ApiTags('Visitor Password Module') @Controller({ @@ -30,7 +31,8 @@ export class VisitorPasswordController { ) {} @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VISITOR_PASSWORD_ADD') @Post('temporary-password/online/multiple-time') @ApiOperation({ summary: @@ -58,7 +60,8 @@ export class VisitorPasswordController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VISITOR_PASSWORD_ADD') @Post('temporary-password/online/one-time') @ApiOperation({ summary: @@ -86,7 +89,8 @@ export class VisitorPasswordController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VISITOR_PASSWORD_ADD') @Post('temporary-password/offline/one-time') @ApiOperation({ summary: @@ -114,7 +118,8 @@ export class VisitorPasswordController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VISITOR_PASSWORD_ADD') @Post('temporary-password/offline/multiple-time') @ApiOperation({ summary: @@ -143,7 +148,8 @@ export class VisitorPasswordController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VISITOR_PASSWORD_VIEW') @Get() @ApiOperation({ summary: @@ -156,7 +162,8 @@ export class VisitorPasswordController { } @ApiBearerAuth() - @UseGuards(JwtAuthGuard) + @UseGuards(PermissionsGuard) + @Permissions('VISITOR_PASSWORD_VIEW') @Get('/devices') @ApiOperation({ summary: From c1af930dc9a3d28ba8458e72b8aa88cee9479c2e Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:36:40 -0600 Subject: [PATCH 060/247] Fix inconsistent casing in import paths for Invite-user module --- libs/common/src/database/database.module.ts | 2 +- .../Invite-user/repositories/Invite-user.repository.ts | 2 +- .../common/src/modules/role-type/entities/role.type.entity.ts | 2 +- libs/common/src/modules/space/entities/space.entity.ts | 2 +- libs/common/src/modules/user/entities/user.entity.ts | 2 +- src/invite-user/invite-user.module.ts | 4 ++-- src/invite-user/services/invite-user.service.ts | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index eeabcd4..bccffc1 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -36,7 +36,7 @@ import { import { InviteUserEntity, InviteUserSpaceEntity, -} from '../modules/Invite-user/entities'; +} from '../modules/invite-user/entities'; @Module({ imports: [ TypeOrmModule.forRootAsync({ diff --git a/libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts b/libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts index 3df7390..463059a 100644 --- a/libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts +++ b/libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts @@ -1,6 +1,6 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; -import { InviteUserEntity, InviteUserSpaceEntity } from '../entities/'; +import { InviteUserEntity, InviteUserSpaceEntity } from '../entities'; @Injectable() export class InviteUserRepository extends Repository { diff --git a/libs/common/src/modules/role-type/entities/role.type.entity.ts b/libs/common/src/modules/role-type/entities/role.type.entity.ts index b7289a3..a67d164 100644 --- a/libs/common/src/modules/role-type/entities/role.type.entity.ts +++ b/libs/common/src/modules/role-type/entities/role.type.entity.ts @@ -3,7 +3,7 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { RoleTypeDto } from '../dtos/role.type.dto'; import { RoleType } from '@app/common/constants/role.type.enum'; import { UserEntity } from '../../user/entities'; -import { InviteUserEntity } from '../../Invite-user/entities'; +import { InviteUserEntity } from '../../invite-user/entities'; @Entity({ name: 'role-type' }) @Unique(['type']) diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index cb8367c..f87ed4c 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -15,7 +15,7 @@ import { SubspaceEntity } from './subspace.entity'; import { SpaceLinkEntity } from './space-link.entity'; import { SpaceProductEntity } from './space-product.entity'; import { SceneEntity } from '../../scene/entities'; -import { InviteUserSpaceEntity } from '../../Invite-user/entities'; +import { InviteUserSpaceEntity } from '../../invite-user/entities'; @Entity({ name: 'space' }) @Unique(['invitationCode']) diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 04697fa..6c3f721 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -27,7 +27,7 @@ import { OtpType } from '../../../../src/constants/otp-type.enum'; import { RoleTypeEntity } from '../../role-type/entities'; import { SpaceEntity } from '../../space/entities'; import { VisitorPasswordEntity } from '../../visitor-password/entities'; -import { InviteUserEntity } from '../../Invite-user/entities'; +import { InviteUserEntity } from '../../invite-user/entities'; import { ProjectEntity } from '../../project/entities'; @Entity({ name: 'user' }) diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index d7a19b6..4eebe73 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -5,9 +5,9 @@ import { ConfigModule } from '@nestjs/config'; import { InviteUserRepository, InviteUserSpaceRepository, -} from '@app/common/modules/Invite-user/repositories'; +} from '@app/common/modules/invite-user/repositories'; import { UserRepository } from '@app/common/modules/user/repositories'; -import { InviteUserRepositoryModule } from '@app/common/modules/Invite-user/Invite-user.repository.module'; +import { InviteUserRepositoryModule } from '@app/common/modules/invite-user/Invite-user.repository.module'; @Module({ imports: [ConfigModule, InviteUserRepositoryModule], diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 931c163..1cef392 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -1,8 +1,8 @@ -import { InviteUserSpaceRepository } from './../../../libs/common/src/modules/Invite-user/repositories/Invite-user.repository'; +import { InviteUserSpaceRepository } from '../../../libs/common/src/modules/invite-user/repositories/Invite-user.repository'; import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { AddUserInvitationDto } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { InviteUserRepository } from '@app/common/modules/Invite-user/repositories'; +import { InviteUserRepository } from '@app/common/modules/invite-user/repositories'; import { UserStatusEnum } from '@app/common/constants/user-status.enum'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { generateRandomString } from '@app/common/helper/randomString'; From 9de1d7134aa092ee9ad76f87156e3d6ca8a0caf4 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:41:51 -0600 Subject: [PATCH 061/247] Fix inconsistent casing in file imports --- libs/common/src/modules/Invite-user/dtos/index.ts | 2 +- libs/common/src/modules/Invite-user/entities/index.ts | 2 +- libs/common/src/modules/Invite-user/repositories/index.ts | 2 +- src/invite-user/invite-user.module.ts | 2 +- src/invite-user/services/invite-user.service.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/common/src/modules/Invite-user/dtos/index.ts b/libs/common/src/modules/Invite-user/dtos/index.ts index 2385037..343abb8 100644 --- a/libs/common/src/modules/Invite-user/dtos/index.ts +++ b/libs/common/src/modules/Invite-user/dtos/index.ts @@ -1 +1 @@ -export * from './Invite-user.dto'; +export * from './invite-user.dto'; diff --git a/libs/common/src/modules/Invite-user/entities/index.ts b/libs/common/src/modules/Invite-user/entities/index.ts index 3f0da22..490fbbe 100644 --- a/libs/common/src/modules/Invite-user/entities/index.ts +++ b/libs/common/src/modules/Invite-user/entities/index.ts @@ -1 +1 @@ -export * from './Invite-user.entity'; +export * from './invite-user.entity'; diff --git a/libs/common/src/modules/Invite-user/repositories/index.ts b/libs/common/src/modules/Invite-user/repositories/index.ts index a60f19a..6f86fa1 100644 --- a/libs/common/src/modules/Invite-user/repositories/index.ts +++ b/libs/common/src/modules/Invite-user/repositories/index.ts @@ -1 +1 @@ -export * from './Invite-user.repository'; +export * from './invite-user.repository'; diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index 4eebe73..cfb69ee 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -7,7 +7,7 @@ import { InviteUserSpaceRepository, } from '@app/common/modules/invite-user/repositories'; import { UserRepository } from '@app/common/modules/user/repositories'; -import { InviteUserRepositoryModule } from '@app/common/modules/invite-user/Invite-user.repository.module'; +import { InviteUserRepositoryModule } from '@app/common/modules/invite-user/invite-user.repository.module'; @Module({ imports: [ConfigModule, InviteUserRepositoryModule], diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 1cef392..701b0c9 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -1,4 +1,4 @@ -import { InviteUserSpaceRepository } from '../../../libs/common/src/modules/invite-user/repositories/Invite-user.repository'; +import { InviteUserSpaceRepository } from '../../../libs/common/src/modules/invite-user/repositories/invite-user.repository'; import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { AddUserInvitationDto } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; From 6f3891a68e8fcc580c9785436da4f8699476932a Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:56:37 -0600 Subject: [PATCH 062/247] Add `invitedBy` field to `InviteUserDto` and `InviteUserEntity` --- .../src/modules/Invite-user/dtos/Invite-user.dto.ts | 8 ++++++++ .../Invite-user/entities/Invite-user.entity.ts | 11 +++++++++-- src/invite-user/controllers/invite-user.controller.ts | 5 ++++- src/invite-user/services/invite-user.service.ts | 3 +++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts b/libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts index d7db8dc..50e826c 100644 --- a/libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts +++ b/libs/common/src/modules/Invite-user/dtos/Invite-user.dto.ts @@ -1,3 +1,4 @@ +import { RoleType } from '@app/common/constants/role.type.enum'; import { UserStatusEnum } from '@app/common/constants/user-status.enum'; import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; @@ -9,12 +10,15 @@ export class InviteUserDto { @IsString() @IsNotEmpty() public email: string; + @IsString() @IsNotEmpty() public jobTitle: string; + @IsEnum(UserStatusEnum) @IsNotEmpty() public status: UserStatusEnum; + @IsString() @IsNotEmpty() public firstName: string; @@ -22,6 +26,10 @@ export class InviteUserDto { @IsString() @IsNotEmpty() public lastName: string; + + @IsEnum(RoleType) + @IsNotEmpty() + public invitedBy: RoleType; } export class InviteUserSpaceDto { @IsString() diff --git a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts index 06e5105..818b8ba 100644 --- a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts +++ b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts @@ -13,6 +13,7 @@ import { RoleTypeEntity } from '../../role-type/entities'; import { UserStatusEnum } from '@app/common/constants/user-status.enum'; import { UserEntity } from '../../user/entities'; import { SpaceEntity } from '../../space/entities'; +import { RoleType } from '@app/common/constants/role.type.enum'; @Entity({ name: 'invite-user' }) @Unique(['email', 'invitationCode']) @@ -69,14 +70,20 @@ export class InviteUserEntity extends AbstractEntity { unique: true, }) public invitationCode: string; - // Relation with RoleTypeEntity + + @Column({ + nullable: false, + enum: Object.values(RoleType), + }) + public invitedBy: string; + @ManyToOne(() => RoleTypeEntity, (roleType) => roleType.invitedUsers, { nullable: false, onDelete: 'CASCADE', }) public roleType: RoleTypeEntity; @OneToOne(() => UserEntity, (user) => user.inviteUser, { nullable: true }) - @JoinColumn({ name: 'user_uuid' }) // Foreign key in InviteUserEntity + @JoinColumn({ name: 'user_uuid' }) user: UserEntity; @OneToMany( () => InviteUserSpaceEntity, diff --git a/src/invite-user/controllers/invite-user.controller.ts b/src/invite-user/controllers/invite-user.controller.ts index 7c25450..756ab68 100644 --- a/src/invite-user/controllers/invite-user.controller.ts +++ b/src/invite-user/controllers/invite-user.controller.ts @@ -1,5 +1,5 @@ import { InviteUserService } from '../services/invite-user.service'; -import { Body, Controller, Post, UseGuards } from '@nestjs/common'; +import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common'; import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { AddUserInvitationDto } from '../dtos/add.invite-user.dto'; import { ControllerRoute } from '@app/common/constants/controller-route'; @@ -26,9 +26,12 @@ export class InviteUserController { }) async createUserInvitation( @Body() addUserInvitationDto: AddUserInvitationDto, + @Req() request: any, ): Promise { + const user = request.user; return await this.inviteUserService.createUserInvitation( addUserInvitationDto, + user.role.type, ); } } diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 701b0c9..9fe0db5 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -9,6 +9,7 @@ import { generateRandomString } from '@app/common/helper/randomString'; import { IsNull, Not } from 'typeorm'; import { DataSource } from 'typeorm'; import { UserEntity } from '@app/common/modules/user/entities'; +import { RoleType } from '@app/common/constants/role.type.enum'; @Injectable() export class InviteUserService { @@ -20,6 +21,7 @@ export class InviteUserService { async createUserInvitation( dto: AddUserInvitationDto, + roleType: RoleType, ): Promise { const { firstName, @@ -62,6 +64,7 @@ export class InviteUserService { roleType: { uuid: roleUuid }, status: UserStatusEnum.INVITED, invitationCode, + invitedBy: roleType, }); const invitedUser = await queryRunner.manager.save(inviteUser); From 61dae6d3f5ed0c1bfcf1f6c4868129ef42f0a86d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:57:35 -0600 Subject: [PATCH 063/247] Remove debug console log from PermissionsGuard --- src/guards/permissions.guard.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/guards/permissions.guard.ts b/src/guards/permissions.guard.ts index 4075943..ac5881e 100644 --- a/src/guards/permissions.guard.ts +++ b/src/guards/permissions.guard.ts @@ -29,7 +29,6 @@ export class PermissionsGuard extends AuthGuard('jwt') { const request = context.switchToHttp().getRequest(); const user = request.user; // User is now available after AuthGuard - console.log('user', user); const userRole = user?.role.type as RoleType; if (!userRole || !RolePermissions[userRole]) { From 5e9a8f3f7895a2ab1350e4c1582fc1eeb0ae03bc Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 16 Dec 2024 20:00:10 -0600 Subject: [PATCH 064/247] Make roleType field non-nullable in UserEntity --- libs/common/src/modules/user/entities/user.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 6c3f721..3246c41 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -117,7 +117,7 @@ export class UserEntity extends AbstractEntity { public visitorPasswords: VisitorPasswordEntity[]; @ManyToOne(() => RoleTypeEntity, (roleType) => roleType.users, { - nullable: true, + nullable: false, }) public roleType: RoleTypeEntity; @OneToOne(() => InviteUserEntity, (inviteUser) => inviteUser.user, { From 8f6e5e31e218c4d0fc8120209c0b5961b71c561e Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 17 Dec 2024 01:11:53 -0600 Subject: [PATCH 065/247] Update dependencies and refactor import statements --- .eslintrc.js | 7 + package-lock.json | 1543 ++++++++++++++++- package.json | 1 + .../services/invite-user.service.ts | 6 +- 4 files changed, 1504 insertions(+), 53 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 259de13..34440e3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -22,4 +22,11 @@ module.exports = { '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', }, + settings: { + 'import/resolver': { + node: { + caseSensitive: true, + }, + }, + }, }; diff --git a/package-lock.json b/package-lock.json index 9a92384..bcdff93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,7 @@ "concurrently": "^8.2.2", "eslint": "^8.42.0", "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-prettier": "^5.0.0", "jest": "^29.5.0", "prettier": "^3.0.0", @@ -2775,6 +2776,12 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -3028,6 +3035,12 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "node_modules/@types/jsonwebtoken": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", @@ -3736,11 +3749,47 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-timsort": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", @@ -3756,6 +3805,83 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -3783,6 +3909,21 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -4209,15 +4350,42 @@ } }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -4820,6 +4988,57 @@ "node": ">=0.10" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -4902,6 +5121,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -4914,6 +5134,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -5027,6 +5264,19 @@ "node": ">=12" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -5107,13 +5357,72 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "node_modules/es-abstract": { + "version": "1.23.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.6.tgz", + "integrity": "sha512-Ifco6n3yj2tMZDWNLyloZrytt9lqqlwvS83P3HtaETR0NUOYnIULGGHpktqYGObGy+8wc1okO25p8TjemhImvA==", + "dev": true, "dependencies": { - "get-intrinsic": "^1.2.4" + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.7", + "get-intrinsic": "^1.2.6", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.0.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-regex-test": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.3", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.16" }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "engines": { "node": ">= 0.4" } @@ -5132,6 +5441,57 @@ "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", "dev": true }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -5224,6 +5584,170 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-import/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, "node_modules/eslint-plugin-prettier": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", @@ -5929,6 +6453,15 @@ } } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -6080,6 +6613,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function.prototype.name": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.7.tgz", + "integrity": "sha512-2g4x+HqTJKM9zcJqBSpjoRmdcPFtJM60J3xJisTQSXBWka5XqyBN/2tNUgma1mztTXyDuUsEtYe5qcs7xYzYQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gaxios": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", @@ -6127,15 +6688,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -6165,6 +6731,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -6227,6 +6810,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -6286,11 +6885,11 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6383,6 +6982,15 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6404,6 +7012,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -6412,9 +7021,13 @@ } }, "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -6423,9 +7036,24 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -6434,9 +7062,9 @@ } }, "node_modules/hasown": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", - "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -6660,6 +7288,20 @@ "node": ">=12.0.0" } }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", @@ -6728,12 +7370,59 @@ "node": ">= 0.10" } }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -6746,13 +7435,77 @@ "node": ">=8" } }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "node_modules/is-boolean-object": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", + "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", "dev": true, "dependencies": { - "hasown": "^2.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", + "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6767,6 +7520,21 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -6784,6 +7552,21 @@ "node": ">=6" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -6805,6 +7588,30 @@ "node": ">=8" } }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6814,6 +7621,22 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -6823,6 +7646,51 @@ "node": ">=8" } }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -6834,6 +7702,54 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -6851,6 +7767,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", + "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -8011,6 +8970,14 @@ "tmpl": "1.0.5" } }, + "node_modules/math-intrinsics": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", + "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -8486,9 +9453,88 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8995,6 +10041,15 @@ "node": ">=4" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -9329,12 +10384,52 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz", + "integrity": "sha512-B5dj6usc5dkk8uFliwjwDHM8To5/QwdKz9JcBZ8Ic4G1f0YmeeJTtE/ZTdgRFPAfxZFiUaPhZ1Jcs4qeagItGQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "dunder-proto": "^1.0.0", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.2.0", + "which-builtin-type": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -9639,6 +10734,31 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -9658,6 +10778,23 @@ } ] }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -9807,16 +10944,32 @@ } }, "node_modules/set-function-length": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", - "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "dependencies": { - "define-data-property": "^1.1.2", + "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.3", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -9927,14 +11080,65 @@ } }, "node_modules/side-channel": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", - "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dependencies": { - "call-bind": "^1.0.6", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -10180,6 +11384,62 @@ "node": ">=8" } }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -10809,6 +12069,80 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.3.tgz", + "integrity": "sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -10980,6 +12314,24 @@ "node": ">=8" } }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undici": { "version": "5.28.4", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", @@ -11300,6 +12652,95 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.16.tgz", + "integrity": "sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index d3c90e0..961ef38 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "concurrently": "^8.2.2", "eslint": "^8.42.0", "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-prettier": "^5.0.0", "jest": "^29.5.0", "prettier": "^3.0.0", diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 9fe0db5..4c9b154 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -1,8 +1,6 @@ -import { InviteUserSpaceRepository } from '../../../libs/common/src/modules/invite-user/repositories/invite-user.repository'; import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { AddUserInvitationDto } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { InviteUserRepository } from '@app/common/modules/invite-user/repositories'; import { UserStatusEnum } from '@app/common/constants/user-status.enum'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { generateRandomString } from '@app/common/helper/randomString'; @@ -10,6 +8,10 @@ import { IsNull, Not } from 'typeorm'; import { DataSource } from 'typeorm'; import { UserEntity } from '@app/common/modules/user/entities'; import { RoleType } from '@app/common/constants/role.type.enum'; +import { + InviteUserRepository, + InviteUserSpaceRepository, +} from '@app/common/modules/invite-user/repositories'; @Injectable() export class InviteUserService { From a25496b92642a7f44a1cb8eae29d2a26d1fb808d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 17 Dec 2024 01:46:39 -0600 Subject: [PATCH 066/247] Remove unused and outdated dependencies --- libs/common/src/modules/Invite-user/index.ts | 1 + .../{repositories => repositiories}/index.ts | 0 .../invite-user.repository.ts} | 0 package-lock.json | 1543 +---------------- 4 files changed, 52 insertions(+), 1492 deletions(-) create mode 100644 libs/common/src/modules/Invite-user/index.ts rename libs/common/src/modules/Invite-user/{repositories => repositiories}/index.ts (100%) rename libs/common/src/modules/Invite-user/{repositories/Invite-user.repository.ts => repositiories/invite-user.repository.ts} (100%) diff --git a/libs/common/src/modules/Invite-user/index.ts b/libs/common/src/modules/Invite-user/index.ts new file mode 100644 index 0000000..251a484 --- /dev/null +++ b/libs/common/src/modules/Invite-user/index.ts @@ -0,0 +1 @@ +export * from './invite-user.repository.module'; diff --git a/libs/common/src/modules/Invite-user/repositories/index.ts b/libs/common/src/modules/Invite-user/repositiories/index.ts similarity index 100% rename from libs/common/src/modules/Invite-user/repositories/index.ts rename to libs/common/src/modules/Invite-user/repositiories/index.ts diff --git a/libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts b/libs/common/src/modules/Invite-user/repositiories/invite-user.repository.ts similarity index 100% rename from libs/common/src/modules/Invite-user/repositories/Invite-user.repository.ts rename to libs/common/src/modules/Invite-user/repositiories/invite-user.repository.ts diff --git a/package-lock.json b/package-lock.json index bcdff93..9a92384 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,7 +55,6 @@ "concurrently": "^8.2.2", "eslint": "^8.42.0", "eslint-config-prettier": "^9.0.0", - "eslint-plugin-import": "^2.31.0", "eslint-plugin-prettier": "^5.0.0", "jest": "^29.5.0", "prettier": "^3.0.0", @@ -2776,12 +2775,6 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true - }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -3035,12 +3028,6 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, "node_modules/@types/jsonwebtoken": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", @@ -3749,47 +3736,11 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, - "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array-timsort": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", @@ -3805,83 +3756,6 @@ "node": ">=8" } }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -3909,21 +3783,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -4350,42 +4209,15 @@ } }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", - "dependencies": { "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -4988,57 +4820,6 @@ "node": ">=0.10" } }, - "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -5121,7 +4902,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -5134,23 +4914,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -5264,19 +5027,6 @@ "node": ">=12" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -5357,72 +5107,13 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-abstract": { - "version": "1.23.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.6.tgz", - "integrity": "sha512-Ifco6n3yj2tMZDWNLyloZrytt9lqqlwvS83P3HtaETR0NUOYnIULGGHpktqYGObGy+8wc1okO25p8TjemhImvA==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.7", - "get-intrinsic": "^1.2.6", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.4", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.1.0", - "math-intrinsics": "^1.0.0", - "object-inspect": "^1.13.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.3", - "safe-array-concat": "^1.1.3", - "safe-regex-test": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.3", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, "engines": { "node": ">= 0.4" } @@ -5441,57 +5132,6 @@ "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", "dev": true }, - "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", - "dev": true, - "dependencies": { - "hasown": "^2.0.0" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -5584,170 +5224,6 @@ "eslint": ">=7.0.0" } }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", - "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", - "dev": true, - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", - "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", - "dev": true, - "dependencies": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.0", - "hasown": "^2.0.2", - "is-core-module": "^2.15.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.0", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.8", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-import/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-import/node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, "node_modules/eslint-plugin-prettier": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", @@ -6453,15 +5929,6 @@ } } }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -6613,34 +6080,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function.prototype.name": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.7.tgz", - "integrity": "sha512-2g4x+HqTJKM9zcJqBSpjoRmdcPFtJM60J3xJisTQSXBWka5XqyBN/2tNUgma1mztTXyDuUsEtYe5qcs7xYzYQA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gaxios": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", @@ -6688,20 +6127,15 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", - "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "dunder-proto": "^1.0.0", - "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.0.0" + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" }, "engines": { "node": ">= 0.4" @@ -6731,23 +6165,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -6810,22 +6227,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -6885,11 +6286,11 @@ } }, "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "engines": { - "node": ">= 0.4" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6982,15 +6383,6 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -7012,7 +6404,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -7021,13 +6412,9 @@ } }, "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "dependencies": { - "dunder-proto": "^1.0.0" - }, + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", "engines": { "node": ">= 0.4" }, @@ -7036,24 +6423,9 @@ } }, "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.3" - }, + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "engines": { "node": ">= 0.4" }, @@ -7062,9 +6434,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dependencies": { "function-bind": "^1.1.2" }, @@ -7288,20 +6660,6 @@ "node": ">=12.0.0" } }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", @@ -7370,59 +6728,12 @@ "node": ">= 0.10" } }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, - "node_modules/is-async-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", - "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -7435,77 +6746,13 @@ "node": ">=8" } }, - "node_modules/is-boolean-object": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", - "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-core-module": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", - "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7520,21 +6767,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -7552,21 +6784,6 @@ "node": ">=6" } }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -7588,30 +6805,6 @@ "node": ">=8" } }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -7621,22 +6814,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -7646,51 +6823,6 @@ "node": ">=8" } }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -7702,54 +6834,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dev": true, - "dependencies": { - "which-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -7767,49 +6851,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", - "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -8970,14 +8011,6 @@ "tmpl": "1.0.5" } }, - "node_modules/math-intrinsics": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", - "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -9453,88 +8486,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -10041,15 +8995,6 @@ "node": ">=4" } }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -10384,52 +9329,12 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz", - "integrity": "sha512-B5dj6usc5dkk8uFliwjwDHM8To5/QwdKz9JcBZ8Ic4G1f0YmeeJTtE/ZTdgRFPAfxZFiUaPhZ1Jcs4qeagItGQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "dunder-proto": "^1.0.0", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "gopd": "^1.2.0", - "which-builtin-type": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", - "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -10734,31 +9639,6 @@ "tslib": "^2.1.0" } }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -10778,23 +9658,6 @@ } ] }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -10944,32 +9807,16 @@ } }, "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", "dependencies": { - "define-data-property": "^1.1.4", + "define-data-property": "^1.1.2", "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -11080,65 +9927,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", + "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", "dependencies": { + "call-bind": "^1.0.6", "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" }, "engines": { "node": ">= 0.4" @@ -11384,62 +10180,6 @@ "node": ">=8" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -12069,80 +10809,6 @@ "node": ">= 0.6" } }, - "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.3.tgz", - "integrity": "sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -12314,24 +10980,6 @@ "node": ">=8" } }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/undici": { "version": "5.28.4", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", @@ -12652,95 +11300,6 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.16.tgz", - "integrity": "sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", From 4ce7620584a510d935ddab4c17af88907531194d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 17 Dec 2024 01:48:36 -0600 Subject: [PATCH 067/247] Fix typo in import path for repositories --- src/invite-user/invite-user.module.ts | 7 ++++--- src/invite-user/services/invite-user.service.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index cfb69ee..6b29358 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -2,12 +2,13 @@ import { Module } from '@nestjs/common'; import { InviteUserService } from './services/invite-user.service'; import { InviteUserController } from './controllers/invite-user.controller'; import { ConfigModule } from '@nestjs/config'; + +import { UserRepository } from '@app/common/modules/user/repositories'; +import { InviteUserRepositoryModule } from '@app/common/modules/invite-user/invite-user.repository.module'; import { InviteUserRepository, InviteUserSpaceRepository, -} from '@app/common/modules/invite-user/repositories'; -import { UserRepository } from '@app/common/modules/user/repositories'; -import { InviteUserRepositoryModule } from '@app/common/modules/invite-user/invite-user.repository.module'; +} from '@app/common/modules/invite-user/repositiories'; @Module({ imports: [ConfigModule, InviteUserRepositoryModule], diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 4c9b154..069834b 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -11,7 +11,7 @@ import { RoleType } from '@app/common/constants/role.type.enum'; import { InviteUserRepository, InviteUserSpaceRepository, -} from '@app/common/modules/invite-user/repositories'; +} from '@app/common/modules/invite-user/repositiories'; @Injectable() export class InviteUserService { From c27aca11963877f21c99a2a2cc0649128aa61c04 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 17 Dec 2024 02:13:34 -0600 Subject: [PATCH 068/247] Fix inconsistent casing in Invite-user module paths --- libs/common/src/database/database.module.ts | 2 +- libs/common/src/modules/Invite-user/dtos/index.ts | 2 +- .../src/modules/Invite-user/entities/Invite-user.entity.ts | 3 ++- libs/common/src/modules/Invite-user/entities/index.ts | 2 +- libs/common/src/modules/Invite-user/index.ts | 2 +- .../common/src/modules/role-type/entities/role.type.entity.ts | 2 +- libs/common/src/modules/space/entities/space.entity.ts | 2 +- libs/common/src/modules/user/entities/user.entity.ts | 2 +- src/invite-user/invite-user.module.ts | 4 ++-- src/invite-user/services/invite-user.service.ts | 2 +- 10 files changed, 12 insertions(+), 11 deletions(-) diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index bccffc1..eeabcd4 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -36,7 +36,7 @@ import { import { InviteUserEntity, InviteUserSpaceEntity, -} from '../modules/invite-user/entities'; +} from '../modules/Invite-user/entities'; @Module({ imports: [ TypeOrmModule.forRootAsync({ diff --git a/libs/common/src/modules/Invite-user/dtos/index.ts b/libs/common/src/modules/Invite-user/dtos/index.ts index 343abb8..2385037 100644 --- a/libs/common/src/modules/Invite-user/dtos/index.ts +++ b/libs/common/src/modules/Invite-user/dtos/index.ts @@ -1 +1 @@ -export * from './invite-user.dto'; +export * from './Invite-user.dto'; diff --git a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts index 818b8ba..e2414f5 100644 --- a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts +++ b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts @@ -7,13 +7,14 @@ import { OneToOne, Unique, } from 'typeorm'; -import { InviteUserDto, InviteUserSpaceDto } from '../dtos'; + import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { RoleTypeEntity } from '../../role-type/entities'; import { UserStatusEnum } from '@app/common/constants/user-status.enum'; import { UserEntity } from '../../user/entities'; import { SpaceEntity } from '../../space/entities'; import { RoleType } from '@app/common/constants/role.type.enum'; +import { InviteUserDto, InviteUserSpaceDto } from '../dtos'; @Entity({ name: 'invite-user' }) @Unique(['email', 'invitationCode']) diff --git a/libs/common/src/modules/Invite-user/entities/index.ts b/libs/common/src/modules/Invite-user/entities/index.ts index 490fbbe..3f0da22 100644 --- a/libs/common/src/modules/Invite-user/entities/index.ts +++ b/libs/common/src/modules/Invite-user/entities/index.ts @@ -1 +1 @@ -export * from './invite-user.entity'; +export * from './Invite-user.entity'; diff --git a/libs/common/src/modules/Invite-user/index.ts b/libs/common/src/modules/Invite-user/index.ts index 251a484..5fbc01a 100644 --- a/libs/common/src/modules/Invite-user/index.ts +++ b/libs/common/src/modules/Invite-user/index.ts @@ -1 +1 @@ -export * from './invite-user.repository.module'; +export * from './Invite-user.repository.module'; diff --git a/libs/common/src/modules/role-type/entities/role.type.entity.ts b/libs/common/src/modules/role-type/entities/role.type.entity.ts index a67d164..b7289a3 100644 --- a/libs/common/src/modules/role-type/entities/role.type.entity.ts +++ b/libs/common/src/modules/role-type/entities/role.type.entity.ts @@ -3,7 +3,7 @@ import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { RoleTypeDto } from '../dtos/role.type.dto'; import { RoleType } from '@app/common/constants/role.type.enum'; import { UserEntity } from '../../user/entities'; -import { InviteUserEntity } from '../../invite-user/entities'; +import { InviteUserEntity } from '../../Invite-user/entities'; @Entity({ name: 'role-type' }) @Unique(['type']) diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index f87ed4c..cb8367c 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -15,7 +15,7 @@ import { SubspaceEntity } from './subspace.entity'; import { SpaceLinkEntity } from './space-link.entity'; import { SpaceProductEntity } from './space-product.entity'; import { SceneEntity } from '../../scene/entities'; -import { InviteUserSpaceEntity } from '../../invite-user/entities'; +import { InviteUserSpaceEntity } from '../../Invite-user/entities'; @Entity({ name: 'space' }) @Unique(['invitationCode']) diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 3246c41..097b154 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -27,7 +27,7 @@ import { OtpType } from '../../../../src/constants/otp-type.enum'; import { RoleTypeEntity } from '../../role-type/entities'; import { SpaceEntity } from '../../space/entities'; import { VisitorPasswordEntity } from '../../visitor-password/entities'; -import { InviteUserEntity } from '../../invite-user/entities'; +import { InviteUserEntity } from '../../Invite-user/entities'; import { ProjectEntity } from '../../project/entities'; @Entity({ name: 'user' }) diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index 6b29358..04a65e6 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -4,11 +4,11 @@ import { InviteUserController } from './controllers/invite-user.controller'; import { ConfigModule } from '@nestjs/config'; import { UserRepository } from '@app/common/modules/user/repositories'; -import { InviteUserRepositoryModule } from '@app/common/modules/invite-user/invite-user.repository.module'; +import { InviteUserRepositoryModule } from '@app/common/modules/Invite-user/invite-user.repository.module'; import { InviteUserRepository, InviteUserSpaceRepository, -} from '@app/common/modules/invite-user/repositiories'; +} from '@app/common/modules/Invite-user/repositiories'; @Module({ imports: [ConfigModule, InviteUserRepositoryModule], diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 069834b..b63b3da 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -11,7 +11,7 @@ import { RoleType } from '@app/common/constants/role.type.enum'; import { InviteUserRepository, InviteUserSpaceRepository, -} from '@app/common/modules/invite-user/repositiories'; +} from '@app/common/modules/Invite-user/repositiories'; @Injectable() export class InviteUserService { From 74fb06f67e62843a631c5e39f5164b7080de3906 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 17 Dec 2024 02:16:01 -0600 Subject: [PATCH 069/247] Fix import case sensitivity in InviteUserRepositoryModule --- src/invite-user/invite-user.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index 04a65e6..e77f875 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -4,7 +4,7 @@ import { InviteUserController } from './controllers/invite-user.controller'; import { ConfigModule } from '@nestjs/config'; import { UserRepository } from '@app/common/modules/user/repositories'; -import { InviteUserRepositoryModule } from '@app/common/modules/Invite-user/invite-user.repository.module'; +import { InviteUserRepositoryModule } from '@app/common/modules/Invite-user/Invite-user.repository.module'; import { InviteUserRepository, InviteUserSpaceRepository, From b3e8af7540b5f5791983adfec0e3418fac909bf0 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 17 Dec 2024 12:18:03 +0400 Subject: [PATCH 070/247] added subspace product item --- .../subspace-model/subspace-model.entity.ts | 2 +- .../subspace-product-model.entity.ts | 2 +- .../subspace/subspace-product.entity.ts | 2 +- src/space/dtos/subspace/add.subspace.dto.ts | 21 +++++- src/space/services/index.ts | 1 + .../services/space-validation.service.ts | 2 + src/space/services/subspace/index.ts | 2 + .../subspace/subspace-product-item.service.ts | 50 ++++++++++++++ .../subspace/subspace-product.service.ts | 66 +++++++++++++++++++ .../services/subspace/subspace.service.ts | 31 +++++++-- src/space/space.module.ts | 12 +++- 11 files changed, 179 insertions(+), 12 deletions(-) create mode 100644 src/space/services/subspace/subspace-product-item.service.ts create mode 100644 src/space/services/subspace/subspace-product.service.ts diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts index 2d1a170..89fcf63 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts @@ -42,5 +42,5 @@ export class SubspaceModelEntity extends AbstractEntity { nullable: true, }, ) - public subspaceProductModels: SubspaceProductModelEntity[]; + public productModels: SubspaceProductModelEntity[]; } diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts index 6e22a35..843c70d 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts @@ -16,7 +16,7 @@ export class SubspaceProductModelEntity extends AbstractEntity SubspaceModelEntity, - (spaceModel) => spaceModel.subspaceProductModels, + (spaceModel) => spaceModel.productModels, { nullable: false, }, diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts index 03531cc..87ed807 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts @@ -46,5 +46,5 @@ export class SubspaceProductEntity extends AbstractEntity nullable: true, }, ) - model: SubspaceProductItemModelEntity; + model: SubspaceProductModelEntity; } diff --git a/src/space/dtos/subspace/add.subspace.dto.ts b/src/space/dtos/subspace/add.subspace.dto.ts index a2b12e2..6b5078b 100644 --- a/src/space/dtos/subspace/add.subspace.dto.ts +++ b/src/space/dtos/subspace/add.subspace.dto.ts @@ -1,5 +1,13 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { Type } from 'class-transformer'; +import { + IsArray, + IsNotEmpty, + IsOptional, + IsString, + ValidateNested, +} from 'class-validator'; +import { ProductAssignmentDto } from '../add.space.dto'; export class AddSubspaceDto { @ApiProperty({ @@ -9,4 +17,15 @@ export class AddSubspaceDto { @IsNotEmpty() @IsString() subspaceName: string; + + @ApiProperty({ + description: 'List of products assigned to this space', + type: [ProductAssignmentDto], + required: false, + }) + @IsArray() + @ValidateNested({ each: true }) + @IsOptional() + @Type(() => ProductAssignmentDto) + products?: ProductAssignmentDto[]; } diff --git a/src/space/services/index.ts b/src/space/services/index.ts index 6a3beeb..c67ccae 100644 --- a/src/space/services/index.ts +++ b/src/space/services/index.ts @@ -6,3 +6,4 @@ export * from './space-link'; export * from './space-scene.service'; export * from './space-products'; export * from './space-product-items'; +export * from './space-validation.service'; diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts index cd80387..fa4a5f7 100644 --- a/src/space/services/space-validation.service.ts +++ b/src/space/services/space-validation.service.ts @@ -61,6 +61,8 @@ export class ValidationService { where: { uuid: spaceModelUuid }, relations: [ 'subspaceModels', + 'subspaceModels.productModels.product', + 'subspaceModels.productModels', 'spaceProductModels', 'spaceProductModels.product', 'spaceProductModels.items', diff --git a/src/space/services/subspace/index.ts b/src/space/services/subspace/index.ts index 973d199..b51a84a 100644 --- a/src/space/services/subspace/index.ts +++ b/src/space/services/subspace/index.ts @@ -1,2 +1,4 @@ export * from './subspace.service'; export * from './subspace-device.service'; +export * from './subspace-product-item.service'; +export * from './subspace-product.service'; diff --git a/src/space/services/subspace/subspace-product-item.service.ts b/src/space/services/subspace/subspace-product-item.service.ts new file mode 100644 index 0000000..fa3f041 --- /dev/null +++ b/src/space/services/subspace/subspace-product-item.service.ts @@ -0,0 +1,50 @@ +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { QueryRunner } from 'typeorm'; + +import { SubspaceProductEntity } from '@app/common/modules/space'; +import { SubspaceProductModelEntity } from '@app/common/modules/space-model'; +import { SubspaceProductItemRepository } from '@app/common/modules/space/repositories/subspace.repository'; + +@Injectable() +export class SubspaceProductItemService { + constructor( + private readonly productItemRepository: SubspaceProductItemRepository, + ) {} + + async createItemFromModel( + product: SubspaceProductEntity, + productModel: SubspaceProductModelEntity, + queryRunner: QueryRunner, + ): Promise { + const itemModels = productModel.itemModels; + + if (!itemModels?.length) return; + + try { + const productItems = itemModels.map((model) => + this.createProductItem(product, model, queryRunner), + ); + + await queryRunner.manager.save( + this.productItemRepository.target, + productItems, + ); + } catch (error) { + throw new HttpException( + error.message || 'An error occurred while creating product items.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private createProductItem( + product: SubspaceProductEntity, + model: any, + queryRunner: QueryRunner, + ): Partial { + return queryRunner.manager.create(this.productItemRepository.target, { + tag: model.tag, + product, + }); + } +} diff --git a/src/space/services/subspace/subspace-product.service.ts b/src/space/services/subspace/subspace-product.service.ts new file mode 100644 index 0000000..0b43880 --- /dev/null +++ b/src/space/services/subspace/subspace-product.service.ts @@ -0,0 +1,66 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { QueryRunner } from 'typeorm'; + +import { + SubspaceEntity, + SubspaceProductEntity, +} from '@app/common/modules/space'; +import { + SubspaceModelEntity, + SubspaceProductModelEntity, +} from '@app/common/modules/space-model'; +import { SubspaceProductItemService } from './subspace-product-item.service'; + +@Injectable() +export class SubspaceProductService { + constructor( + private readonly subspaceProductItemService: SubspaceProductItemService, + ) {} + + async createFromModel( + subspaceModel: SubspaceModelEntity, + subspace: SubspaceEntity, + queryRunner: QueryRunner, + ): Promise { + const productModels = subspaceModel.productModels; + if (!productModels?.length) return; + + try { + const newSpaceProducts = productModels.map((productModel) => + this.createSubspaceProductEntity(subspace, productModel), + ); + + const subspaceProducts = await queryRunner.manager.save( + SubspaceProductEntity, + newSpaceProducts, + ); + + await Promise.all( + subspaceProducts.map((subspaceProduct, index) => + this.subspaceProductItemService.createItemFromModel( + subspaceProduct, + productModels[index], + queryRunner, + ), + ), + ); + } catch (error) { + throw new HttpException( + `Transaction failed: Unable to create subspace products ${error}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private createSubspaceProductEntity( + subspace: SubspaceEntity, + productModel: SubspaceProductModelEntity, + ): Partial { + return { + subspace, + product: productModel.product, + productCount: productModel.productCount, + model: productModel, + }; + } +} diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 96afa41..5848e43 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -13,19 +13,28 @@ import { SpaceEntity, SubspaceEntity, } from '@app/common/modules/space/entities'; -import { SpaceModelEntity } from '@app/common/modules/space-model'; +import { + SpaceModelEntity, + SubspaceModelEntity, +} from '@app/common/modules/space-model'; import { ValidationService } from '../space-validation.service'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; +import { SubspaceProductService } from './subspace-product.service'; @Injectable() export class SubSpaceService { constructor( private readonly subspaceRepository: SubspaceRepository, private readonly validationService: ValidationService, + private readonly productService: SubspaceProductService, ) {} async createSubspaces( - subspaceData: Array<{ subspaceName: string; space: SpaceEntity }>, + subspaceData: Array<{ + subspaceName: string; + space: SpaceEntity; + subSpaceModel?: SubspaceModelEntity; + }>, queryRunner: QueryRunner, ): Promise { try { @@ -47,17 +56,27 @@ export class SubSpaceService { space: SpaceEntity, queryRunner: QueryRunner, ): Promise { - const subSpaces = spaceModel.subspaceModels; + const subSpaceModels = spaceModel.subspaceModels; - if (!subSpaces?.length) return; + if (!subSpaceModels?.length) return; - const subspaceData = subSpaces.map((subSpaceModel) => ({ + const subspaceData = subSpaceModels.map((subSpaceModel) => ({ subspaceName: subSpaceModel.subspaceName, space, subSpaceModel, })); - await this.createSubspaces(subspaceData, queryRunner); + const subspaces = await this.createSubspaces(subspaceData, queryRunner); + + await Promise.all( + subSpaceModels.map((model, index) => { + this.productService.createFromModel( + model, + subspaces[index], + queryRunner, + ); + }), + ); } async createSubspacesFromNames( diff --git a/src/space/space.module.ts b/src/space/space.module.ts index cd443e5..da589e8 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -18,6 +18,7 @@ import { SpaceService, SpaceUserService, SubspaceDeviceService, + SubspaceProductItemService, SubSpaceService, } from './services'; import { @@ -46,8 +47,12 @@ import { SceneDeviceRepository } from '@app/common/modules/scene-device/reposito import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { SpaceModelRepository } from '@app/common/modules/space-model'; import { CommunityModule } from 'src/community/community.module'; -import { ValidationService } from './services/space-validation.service'; -import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; +import { ValidationService } from './services'; +import { + SubspaceProductItemRepository, + SubspaceRepository, +} from '@app/common/modules/space/repositories/subspace.repository'; +import { SubspaceProductService } from './services'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, CommunityModule], @@ -82,6 +87,7 @@ import { SubspaceRepository } from '@app/common/modules/space/repositories/subsp SceneRepository, DeviceService, DeviceStatusFirebaseService, + SubspaceProductItemRepository, DeviceStatusLogRepository, SceneDeviceRepository, SpaceProductService, @@ -91,6 +97,8 @@ import { SubspaceRepository } from '@app/common/modules/space/repositories/subsp SubspaceRepository, SpaceProductItemService, SpaceProductItemRepository, + SubspaceProductService, + SubspaceProductItemService, ], exports: [SpaceService], }) From 461a0e603689ed2fef365dfc448d62ce927913e4 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 17 Dec 2024 02:24:21 -0600 Subject: [PATCH 071/247] Add RoleModule and clean up role.controller imports --- src/app.module.ts | 2 ++ src/role/controllers/role.controller.ts | 10 +--------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index 3606c0a..fe656ff 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -25,6 +25,7 @@ import { ProjectModule } from './project'; import { SpaceModelModule } from './space-model'; import { InviteUserModule } from './invite-user/invite-user.module'; import { PermissionModule } from './permission/permission.module'; +import { RoleModule } from './role/role.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -53,6 +54,7 @@ import { PermissionModule } from './permission/permission.module'; ProductModule, ProjectModule, PermissionModule, + RoleModule, ], providers: [ { diff --git a/src/role/controllers/role.controller.ts b/src/role/controllers/role.controller.ts index 4317170..8d0d401 100644 --- a/src/role/controllers/role.controller.ts +++ b/src/role/controllers/role.controller.ts @@ -1,14 +1,6 @@ -import { - Body, - Controller, - Get, - HttpStatus, - Post, - UseGuards, -} from '@nestjs/common'; +import { Controller, Get, HttpStatus, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { RoleService } from '../services/role.service'; -import { AddUserRoleDto } from '../dtos'; import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { ControllerRoute } from '@app/common/constants/controller-route'; // Assuming this is where the routes are defined From d1050babd1cfb403a872a6d5a50114a18e9a7ca8 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 17 Dec 2024 19:08:48 +0400 Subject: [PATCH 072/247] space product service --- .../entities/subspace/subspace.entity.ts | 2 +- src/space/dtos/add.space.dto.ts | 2 - .../space-product-items.service.ts | 29 +++-- .../space-products/space-products.service.ts | 2 +- src/space/services/space.service.ts | 4 +- .../subspace/subspace-product-item.service.ts | 100 +++++++++++++++++- .../subspace/subspace-product.service.ts | 62 +++++++++++ .../services/subspace/subspace.service.ts | 31 ++++-- src/space/space.module.ts | 2 + 9 files changed, 212 insertions(+), 22 deletions(-) diff --git a/libs/common/src/modules/space/entities/subspace/subspace.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace.entity.ts index 932757d..bc6ff06 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace.entity.ts @@ -24,7 +24,7 @@ export class SubspaceEntity extends AbstractEntity { nullable: false, onDelete: 'CASCADE', }) - @JoinColumn({ name: 'space_id' }) + @JoinColumn({ name: 'space_uuid' }) space: SpaceEntity; @OneToMany(() => DeviceEntity, (device) => device.subspace, { diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index 402d37a..dba8e92 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -1,7 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { - ArrayNotEmpty, IsArray, IsBoolean, IsNotEmpty, @@ -43,7 +42,6 @@ export class ProductAssignmentDto { example: [{ tag: 'Light 1' }, { tag: 'Light 2' }, { tag: 'Light 3' }], }) @IsArray() - @ArrayNotEmpty() @ValidateNested({ each: true }) @Type(() => CreateSpaceProductItemDto) items: CreateSpaceProductItemDto[]; diff --git a/src/space/services/space-product-items/space-product-items.service.ts b/src/space/services/space-product-items/space-product-items.service.ts index 4dbe0ff..b253fa3 100644 --- a/src/space/services/space-product-items/space-product-items.service.ts +++ b/src/space/services/space-product-items/space-product-items.service.ts @@ -78,6 +78,27 @@ export class SpaceProductItemService { queryRunner: QueryRunner, space: SpaceEntity, ) { + const query = ` + SELECT spi.tag +FROM "space-product-item" spi +INNER JOIN "space-product" spm + ON spi.space_product_uuid = spm.uuid +WHERE spm.space_uuid = $1 + +UNION + +SELECT spi.tag +FROM "subspace-product-item" spi +INNER JOIN "subspace-product" spm + ON spi.subspace_product_uuid = spm.uuid +INNER JOIN "subspace" sm + ON spm.subspace_uuid = sm.uuid +WHERE sm.space_uuid = $1; + `; + + const result = await queryRunner.manager.query(query, [space.uuid]); + console.log(result); + const incomingTags = itemModelDtos.map((item) => item.tag); const duplicateTags = incomingTags.filter( @@ -90,13 +111,7 @@ export class SpaceProductItemService { ); } - const existingTags = await queryRunner.manager.find( - this.spaceProductItemRepository.target, - { - where: { spaceProduct: { space } }, - select: ['tag'], - }, - ); + const existingTags = await queryRunner.manager.query(query, [space.uuid]); const existingTagSet = new Set(existingTags.map((item) => item.tag)); const conflictingTags = incomingTags.filter((tag) => diff --git a/src/space/services/space-products/space-products.service.ts b/src/space/services/space-products/space-products.service.ts index 895053f..a523d72 100644 --- a/src/space/services/space-products/space-products.service.ts +++ b/src/space/services/space-products/space-products.service.ts @@ -14,7 +14,7 @@ export class SpaceProductService { private readonly spaceProductItemService: SpaceProductItemService, ) {} - async createProductItemFromModel( + async createFromModel( spaceModel: SpaceModelEntity, space: SpaceEntity, queryRunner: QueryRunner, diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 5481d19..59aceb5 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -81,7 +81,7 @@ export class SpaceService { } if (subspaces?.length) { - await this.subSpaceService.createSubspacesFromNames( + await this.subSpaceService.createSubspacesFromDto( subspaces, newSpace, queryRunner, @@ -101,7 +101,7 @@ export class SpaceService { queryRunner, ); } else if (spaceModel && spaceModel.spaceProductModels.length) { - await this.spaceProductService.createProductItemFromModel( + await this.spaceProductService.createFromModel( spaceModel, newSpace, queryRunner, diff --git a/src/space/services/subspace/subspace-product-item.service.ts b/src/space/services/subspace/subspace-product-item.service.ts index fa3f041..f9a0713 100644 --- a/src/space/services/subspace/subspace-product-item.service.ts +++ b/src/space/services/subspace/subspace-product-item.service.ts @@ -1,9 +1,17 @@ import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { QueryRunner } from 'typeorm'; -import { SubspaceProductEntity } from '@app/common/modules/space'; -import { SubspaceProductModelEntity } from '@app/common/modules/space-model'; +import { + SpaceEntity, + SubspaceProductEntity, + SubspaceProductItemEntity, +} from '@app/common/modules/space'; +import { + SubspaceProductItemModelEntity, + SubspaceProductModelEntity, +} from '@app/common/modules/space-model'; import { SubspaceProductItemRepository } from '@app/common/modules/space/repositories/subspace.repository'; +import { CreateSpaceProductItemDto } from 'src/space/dtos'; @Injectable() export class SubspaceProductItemService { @@ -39,12 +47,98 @@ export class SubspaceProductItemService { private createProductItem( product: SubspaceProductEntity, - model: any, + model: SubspaceProductItemModelEntity, queryRunner: QueryRunner, ): Partial { return queryRunner.manager.create(this.productItemRepository.target, { tag: model.tag, product, + model, }); } + + async createItemFromDtos( + product: SubspaceProductEntity, + itemDto: CreateSpaceProductItemDto[], + queryRunner: QueryRunner, + space: SpaceEntity, + ) { + if (!itemDto?.length) return; + + try { + await this.validateTags(itemDto, queryRunner, space); + + const productItems = itemDto.map((dto) => + queryRunner.manager.create(SubspaceProductItemEntity, { + tag: dto.tag, + subspaceProduct: product, + }), + ); + + await queryRunner.manager.save( + this.productItemRepository.target, + productItems, + ); + } catch (error) { + throw new HttpException( + error.message || 'An error occurred while creating product items.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private async validateTags( + subspaceItemModelDtos: CreateSpaceProductItemDto[], + queryRunner: QueryRunner, + space: SpaceEntity, + ) { + const incomingTags = subspaceItemModelDtos.map((item) => item.tag); + + const duplicateTags = incomingTags.filter( + (tag, index) => incomingTags.indexOf(tag) !== index, + ); + if (duplicateTags.length > 0) { + throw new HttpException( + `Duplicate tags found in the request: ${[...new Set(duplicateTags)].join(', ')}`, + HttpStatus.BAD_REQUEST, + ); + } + + const existingTagsQuery = ` + SELECT spi.tag +FROM "space-product-item" spi +INNER JOIN "space-product" spm + ON spi.space_product_uuid = spm.uuid +WHERE spm.space_uuid = $1 + +UNION + +SELECT spi.tag +FROM "subspace-product-item" spi +INNER JOIN "subspace-product" spm + ON spi.subspace_product_uuid = spm.uuid +INNER JOIN "subspace" sm + ON spm.subspace_uuid = sm.uuid +WHERE sm.space_uuid = $1; + `; + + const existingTags = await queryRunner.manager.query(existingTagsQuery, [ + space.uuid, + ]); + + console.log(existingTags); + + const existingTagsSet = new Set( + existingTags.map((row: { tag: string }) => row.tag), + ); + const conflictingTags = [...incomingTags].filter((tag) => + existingTagsSet.has(tag), + ); + if (conflictingTags.length > 0) { + throw new HttpException( + `Tags already exist in the model: ${conflictingTags.join(', ')}`, + HttpStatus.CONFLICT, + ); + } + } } diff --git a/src/space/services/subspace/subspace-product.service.ts b/src/space/services/subspace/subspace-product.service.ts index 0b43880..e789311 100644 --- a/src/space/services/subspace/subspace-product.service.ts +++ b/src/space/services/subspace/subspace-product.service.ts @@ -2,6 +2,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { QueryRunner } from 'typeorm'; import { + SpaceEntity, SubspaceEntity, SubspaceProductEntity, } from '@app/common/modules/space'; @@ -10,11 +11,15 @@ import { SubspaceProductModelEntity, } from '@app/common/modules/space-model'; import { SubspaceProductItemService } from './subspace-product-item.service'; +import { ProductAssignmentDto } from 'src/space/dtos'; +import { ProductService } from 'src/product/services'; +import { ProductEntity } from '@app/common/modules/product/entities'; @Injectable() export class SubspaceProductService { constructor( private readonly subspaceProductItemService: SubspaceProductItemService, + private readonly productService: ProductService, ) {} async createFromModel( @@ -63,4 +68,61 @@ export class SubspaceProductService { model: productModel, }; } + + async createFromDto( + productDtos: ProductAssignmentDto[], + subspace: SubspaceEntity, + queryRunner: QueryRunner, + space: SpaceEntity, + ): Promise { + try { + const newSpaceProducts = await Promise.all( + productDtos.map(async (dto) => { + this.validateProductCount(dto); + + const product = await this.getProduct(dto.productId); + return queryRunner.manager.create(SubspaceProductEntity, { + subspace, + product, + productCount: dto.count, + }); + }), + ); + + const subspaceProducts = await queryRunner.manager.save( + SubspaceProductEntity, + newSpaceProducts, + ); + + await Promise.all( + productDtos.map((dto, index) => + this.subspaceProductItemService.createItemFromDtos( + subspaceProducts[index], + dto.items, + queryRunner, + space, + ), + ), + ); + } catch (error) { + throw new HttpException( + `Failed to create subspace products from DTOs. Error: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async getProduct(productId: string): Promise { + const product = await this.productService.findOne(productId); + return product.data; + } + + async validateProductCount(dto: ProductAssignmentDto) { + if (dto.count !== dto.items.length) { + throw new HttpException( + 'Producy item and count doesnot match', + HttpStatus.BAD_REQUEST, + ); + } + } } diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 5848e43..6b9a1a3 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -79,17 +79,36 @@ export class SubSpaceService { ); } - async createSubspacesFromNames( + async createSubspacesFromDto( addSubspaceDtos: AddSubspaceDto[], space: SpaceEntity, queryRunner: QueryRunner, ): Promise { - const subspaceData = addSubspaceDtos.map((dto) => ({ - subspaceName: dto.subspaceName, - space, - })); + try { + const subspaceData = addSubspaceDtos.map((dto) => ({ + subspaceName: dto.subspaceName, + space, + })); - return await this.createSubspaces(subspaceData, queryRunner); + const subspaces = await this.createSubspaces(subspaceData, queryRunner); + + await Promise.all( + addSubspaceDtos.map((dto, index) => + this.productService.createFromDto( + dto.products, + subspaces[index], + queryRunner, + space, + ), + ), + ); + + return subspaces; + } catch (error) { + throw new Error( + `Transaction failed: Unable to create subspaces and products. ${error.message}`, + ); + } } async createSubspace( diff --git a/src/space/space.module.ts b/src/space/space.module.ts index da589e8..81d99e2 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -50,6 +50,7 @@ import { CommunityModule } from 'src/community/community.module'; import { ValidationService } from './services'; import { SubspaceProductItemRepository, + SubspaceProductRepository, SubspaceRepository, } from '@app/common/modules/space/repositories/subspace.repository'; import { SubspaceProductService } from './services'; @@ -99,6 +100,7 @@ import { SubspaceProductService } from './services'; SpaceProductItemRepository, SubspaceProductService, SubspaceProductItemService, + SubspaceProductRepository, ], exports: [SpaceService], }) From e35cefb03b5e2eb6192010f42acc1be0967c04b7 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 17 Dec 2024 19:21:00 +0400 Subject: [PATCH 073/247] added base service --- src/space/common/base-product-item.service.ts | 69 ++++++++++++++++++ src/space/common/index.ts | 1 + .../space-product-items.service.ts | 72 +++++-------------- .../subspace/subspace-product-item.service.ts | 68 +++--------------- 4 files changed, 94 insertions(+), 116 deletions(-) create mode 100644 src/space/common/base-product-item.service.ts create mode 100644 src/space/common/index.ts diff --git a/src/space/common/base-product-item.service.ts b/src/space/common/base-product-item.service.ts new file mode 100644 index 0000000..dd62050 --- /dev/null +++ b/src/space/common/base-product-item.service.ts @@ -0,0 +1,69 @@ +import { HttpException, HttpStatus } from '@nestjs/common'; +import { QueryRunner } from 'typeorm'; + +export abstract class BaseProductItemService { + protected async validateTags( + incomingTags: string[], + queryRunner: QueryRunner, + spaceUuid: string, + ): Promise { + const duplicateTags = incomingTags.filter( + (tag, index) => incomingTags.indexOf(tag) !== index, + ); + + if (duplicateTags.length > 0) { + throw new HttpException( + `Duplicate tags found in the request: ${[...new Set(duplicateTags)].join(', ')}`, + HttpStatus.BAD_REQUEST, + ); + } + + const existingTagsQuery = ` + SELECT tag FROM ( + SELECT spi.tag + FROM "space-product-item" spi + INNER JOIN "space-product" spm ON spi.space_product_uuid = spm.uuid + WHERE spm.space_uuid = $1 + UNION + SELECT spi.tag + FROM "subspace-product-item" spi + INNER JOIN "subspace-product" spm ON spi.subspace_product_uuid = spm.uuid + INNER JOIN "subspace" sm ON spm.subspace_uuid = sm.uuid + WHERE sm.space_uuid = $1 + ) AS combined_tags; + `; + + const existingTags = await queryRunner.manager.query(existingTagsQuery, [ + spaceUuid, + ]); + + const existingTagSet = new Set( + existingTags.map((row: { tag: string }) => row.tag), + ); + const conflictingTags = incomingTags.filter((tag) => + existingTagSet.has(tag), + ); + + if (conflictingTags.length > 0) { + throw new HttpException( + `Tags already exist in the model: ${conflictingTags.join(', ')}`, + HttpStatus.CONFLICT, + ); + } + } + + protected async saveProductItems( + productItems: T[], + targetRepository: any, + queryRunner: QueryRunner, + ): Promise { + try { + await queryRunner.manager.save(targetRepository, productItems); + } catch (error) { + throw new HttpException( + error.message || 'An error occurred while creating product items.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/src/space/common/index.ts b/src/space/common/index.ts new file mode 100644 index 0000000..6e76f39 --- /dev/null +++ b/src/space/common/index.ts @@ -0,0 +1 @@ +export * from './base-product-item.service'; diff --git a/src/space/services/space-product-items/space-product-items.service.ts b/src/space/services/space-product-items/space-product-items.service.ts index b253fa3..d4883b9 100644 --- a/src/space/services/space-product-items/space-product-items.service.ts +++ b/src/space/services/space-product-items/space-product-items.service.ts @@ -7,12 +7,15 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { CreateSpaceProductItemDto } from '../../dtos'; import { QueryRunner } from 'typeorm'; import { SpaceProductModelEntity } from '@app/common/modules/space-model'; +import { BaseProductItemService } from '../../common'; @Injectable() -export class SpaceProductItemService { +export class SpaceProductItemService extends BaseProductItemService { constructor( private readonly spaceProductItemRepository: SpaceProductItemRepository, - ) {} + ) { + super(); + } async createProductItem( itemModelDtos: CreateSpaceProductItemDto[], @@ -20,7 +23,11 @@ export class SpaceProductItemService { space: SpaceEntity, queryRunner: QueryRunner, ) { - await this.validateTags(itemModelDtos, queryRunner, space); + if (!itemModelDtos?.length) return; + + const incomingTags = itemModelDtos.map((item) => item.tag); + + await this.validateTags(incomingTags, queryRunner, space.uuid); try { const productItems = itemModelDtos.map((dto) => @@ -30,7 +37,11 @@ export class SpaceProductItemService { }), ); - await queryRunner.manager.save(productItems); + await this.saveProductItems( + productItems, + this.spaceProductItemRepository.target, + queryRunner, + ); } catch (error) { if (error instanceof HttpException) { throw error; @@ -50,6 +61,7 @@ export class SpaceProductItemService { queryRunner: QueryRunner, ) { const spaceProductItemModels = spaceProductModel.items; + if (!spaceProductItemModels?.length) return; try { const productItems = spaceProductItemModels.map((model) => @@ -72,56 +84,4 @@ export class SpaceProductItemService { ); } } - - private async validateTags( - itemModelDtos: CreateSpaceProductItemDto[], - queryRunner: QueryRunner, - space: SpaceEntity, - ) { - const query = ` - SELECT spi.tag -FROM "space-product-item" spi -INNER JOIN "space-product" spm - ON spi.space_product_uuid = spm.uuid -WHERE spm.space_uuid = $1 - -UNION - -SELECT spi.tag -FROM "subspace-product-item" spi -INNER JOIN "subspace-product" spm - ON spi.subspace_product_uuid = spm.uuid -INNER JOIN "subspace" sm - ON spm.subspace_uuid = sm.uuid -WHERE sm.space_uuid = $1; - `; - - const result = await queryRunner.manager.query(query, [space.uuid]); - console.log(result); - - const incomingTags = itemModelDtos.map((item) => item.tag); - - const duplicateTags = incomingTags.filter( - (tag, index) => incomingTags.indexOf(tag) !== index, - ); - if (duplicateTags.length > 0) { - throw new HttpException( - `Duplicate tags found in the request: ${[...new Set(duplicateTags)].join(', ')}`, - HttpStatus.BAD_REQUEST, - ); - } - - const existingTags = await queryRunner.manager.query(query, [space.uuid]); - const existingTagSet = new Set(existingTags.map((item) => item.tag)); - - const conflictingTags = incomingTags.filter((tag) => - existingTagSet.has(tag), - ); - if (conflictingTags.length > 0) { - throw new HttpException( - `Tags already exist in the model: ${conflictingTags.join(', ')}`, - HttpStatus.CONFLICT, - ); - } - } } diff --git a/src/space/services/subspace/subspace-product-item.service.ts b/src/space/services/subspace/subspace-product-item.service.ts index f9a0713..647a597 100644 --- a/src/space/services/subspace/subspace-product-item.service.ts +++ b/src/space/services/subspace/subspace-product-item.service.ts @@ -11,13 +11,16 @@ import { SubspaceProductModelEntity, } from '@app/common/modules/space-model'; import { SubspaceProductItemRepository } from '@app/common/modules/space/repositories/subspace.repository'; -import { CreateSpaceProductItemDto } from 'src/space/dtos'; +import { CreateSpaceProductItemDto } from '../../dtos'; +import { BaseProductItemService } from '../../common'; @Injectable() -export class SubspaceProductItemService { +export class SubspaceProductItemService extends BaseProductItemService { constructor( private readonly productItemRepository: SubspaceProductItemRepository, - ) {} + ) { + super(); + } async createItemFromModel( product: SubspaceProductEntity, @@ -64,10 +67,10 @@ export class SubspaceProductItemService { space: SpaceEntity, ) { if (!itemDto?.length) return; + const incomingTags = itemDto.map((item) => item.tag); + await this.validateTags(incomingTags, queryRunner, space.uuid); try { - await this.validateTags(itemDto, queryRunner, space); - const productItems = itemDto.map((dto) => queryRunner.manager.create(SubspaceProductItemEntity, { tag: dto.tag, @@ -86,59 +89,4 @@ export class SubspaceProductItemService { ); } } - - private async validateTags( - subspaceItemModelDtos: CreateSpaceProductItemDto[], - queryRunner: QueryRunner, - space: SpaceEntity, - ) { - const incomingTags = subspaceItemModelDtos.map((item) => item.tag); - - const duplicateTags = incomingTags.filter( - (tag, index) => incomingTags.indexOf(tag) !== index, - ); - if (duplicateTags.length > 0) { - throw new HttpException( - `Duplicate tags found in the request: ${[...new Set(duplicateTags)].join(', ')}`, - HttpStatus.BAD_REQUEST, - ); - } - - const existingTagsQuery = ` - SELECT spi.tag -FROM "space-product-item" spi -INNER JOIN "space-product" spm - ON spi.space_product_uuid = spm.uuid -WHERE spm.space_uuid = $1 - -UNION - -SELECT spi.tag -FROM "subspace-product-item" spi -INNER JOIN "subspace-product" spm - ON spi.subspace_product_uuid = spm.uuid -INNER JOIN "subspace" sm - ON spm.subspace_uuid = sm.uuid -WHERE sm.space_uuid = $1; - `; - - const existingTags = await queryRunner.manager.query(existingTagsQuery, [ - space.uuid, - ]); - - console.log(existingTags); - - const existingTagsSet = new Set( - existingTags.map((row: { tag: string }) => row.tag), - ); - const conflictingTags = [...incomingTags].filter((tag) => - existingTagsSet.has(tag), - ); - if (conflictingTags.length > 0) { - throw new HttpException( - `Tags already exist in the model: ${conflictingTags.join(', ')}`, - HttpStatus.CONFLICT, - ); - } - } } From ef33b3e5f64d141606f5ea9aafb567376d89c4d6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 17 Dec 2024 20:37:29 +0400 Subject: [PATCH 074/247] created abstract class --- .../services/space-products/space-products.service.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/space/services/space-products/space-products.service.ts b/src/space/services/space-products/space-products.service.ts index a523d72..2fe75cb 100644 --- a/src/space/services/space-products/space-products.service.ts +++ b/src/space/services/space-products/space-products.service.ts @@ -6,12 +6,15 @@ import { In, QueryRunner } from 'typeorm'; import { ProductAssignmentDto } from '../../dtos'; import { SpaceProductItemService } from '../space-product-items'; import { SpaceModelEntity } from '@app/common/modules/space-model'; +import { ProductEntity } from '@app/common/modules/product/entities'; +import { ProductService } from 'src/product/services'; @Injectable() export class SpaceProductService { constructor( private readonly productRepository: ProductRepository, private readonly spaceProductItemService: SpaceProductItemService, + private readonly productService: ProductService, ) {} async createFromModel( @@ -176,6 +179,7 @@ export class SpaceProductService { for (const uniqueSpaceProduct of uniqueSpaceProducts) { const product = productEntities.get(uniqueSpaceProduct.productId); + await this.getProduct(uniqueSpaceProduct.productId); this.validateProductCount(uniqueSpaceProduct); newProducts.push( @@ -214,4 +218,9 @@ export class SpaceProductService { ); } } + + async getProduct(productId: string): Promise { + const product = await this.productService.findOne(productId); + return product.data; + } } From a5ab88395731b1737f2581bcb6db375f9aec4564 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 18 Dec 2024 08:22:53 +0400 Subject: [PATCH 075/247] create orphan community and space for project --- libs/common/src/constants/orphan-constant.ts | 4 ++ .../src/modules/user/entities/user.entity.ts | 2 +- .../command/create-orphan-space-command.ts | 6 +++ src/project/command/index.ts | 0 .../create-orphan-space.handler.service.ts | 52 +++++++++++++++++++ src/project/handler/index.ts | 1 + src/project/project.module.ts | 18 +++++-- src/project/services/project.service.ts | 11 +++- 8 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 libs/common/src/constants/orphan-constant.ts create mode 100644 src/project/command/create-orphan-space-command.ts create mode 100644 src/project/command/index.ts create mode 100644 src/project/handler/create-orphan-space.handler.service.ts create mode 100644 src/project/handler/index.ts diff --git a/libs/common/src/constants/orphan-constant.ts b/libs/common/src/constants/orphan-constant.ts new file mode 100644 index 0000000..9cb31be --- /dev/null +++ b/libs/common/src/constants/orphan-constant.ts @@ -0,0 +1,4 @@ +export const ORPHAN_COMMUNITY_NAME = 'orphan-community'; +export const ORPHAN_COMMUNITY_DESCRIPTION = + 'Default community for orphan spaces'; +export const ORPHAN_SPACE_NAME = 'orphan-space'; diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 097b154..04697fa 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -117,7 +117,7 @@ export class UserEntity extends AbstractEntity { public visitorPasswords: VisitorPasswordEntity[]; @ManyToOne(() => RoleTypeEntity, (roleType) => roleType.users, { - nullable: false, + nullable: true, }) public roleType: RoleTypeEntity; @OneToOne(() => InviteUserEntity, (inviteUser) => inviteUser.user, { diff --git a/src/project/command/create-orphan-space-command.ts b/src/project/command/create-orphan-space-command.ts new file mode 100644 index 0000000..2355c91 --- /dev/null +++ b/src/project/command/create-orphan-space-command.ts @@ -0,0 +1,6 @@ +import { ICommand } from '@nestjs/cqrs'; +import { ProjectEntity } from '@app/common/modules/project/entities'; + +export class CreateOrphanSpaceCommand implements ICommand { + constructor(public readonly project: ProjectEntity) {} +} diff --git a/src/project/command/index.ts b/src/project/command/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/project/handler/create-orphan-space.handler.service.ts b/src/project/handler/create-orphan-space.handler.service.ts new file mode 100644 index 0000000..8935afe --- /dev/null +++ b/src/project/handler/create-orphan-space.handler.service.ts @@ -0,0 +1,52 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { CreateOrphanSpaceCommand } from '../command/create-orphan-space-command'; +import { SpaceRepository } from '@app/common/modules/space'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { Logger } from '@nestjs/common'; +import { + ORPHAN_COMMUNITY_DESCRIPTION, + ORPHAN_COMMUNITY_NAME, + ORPHAN_SPACE_NAME, +} from '@app/common/constants/orphan-constant'; + +@CommandHandler(CreateOrphanSpaceCommand) +export class CreateOrphanSpaceHandler + implements ICommandHandler +{ + private readonly logger = new Logger(CreateOrphanSpaceHandler.name); + + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly communityRepository: CommunityRepository, + ) {} + + async execute(command: CreateOrphanSpaceCommand): Promise { + try { + const { project } = command; + let orphanCommunity = await this.communityRepository.findOne({ + where: { name: ORPHAN_COMMUNITY_NAME, project }, + }); + + if (!orphanCommunity) { + orphanCommunity = this.communityRepository.create({ + name: ORPHAN_COMMUNITY_NAME, + description: ORPHAN_COMMUNITY_DESCRIPTION, + project, + }); + orphanCommunity = await this.communityRepository.save(orphanCommunity); + } + + const orphanSpace = this.spaceRepository.create({ + spaceName: ORPHAN_SPACE_NAME, + community: orphanCommunity, + }); + await this.spaceRepository.save(orphanSpace); + } catch (error) { + this.logger.error( + `Error when creating orphan space for project ${JSON.stringify( + command.project.uuid, + )} - ERROR ${error}.`, + ); + } + } +} diff --git a/src/project/handler/index.ts b/src/project/handler/index.ts new file mode 100644 index 0000000..acbf37d --- /dev/null +++ b/src/project/handler/index.ts @@ -0,0 +1 @@ +export * from './create-orphan-space.handler.service' \ No newline at end of file diff --git a/src/project/project.module.ts b/src/project/project.module.ts index 3cd906e..8711499 100644 --- a/src/project/project.module.ts +++ b/src/project/project.module.ts @@ -1,13 +1,25 @@ import { Global, Module } from '@nestjs/common'; +import { CqrsModule } from '@nestjs/cqrs'; import { ProjectController } from './controllers'; import { ProjectService } from './services'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; +import { CreateOrphanSpaceHandler } from './handler'; +import { SpaceRepository } from '@app/common/modules/space'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; + +const CommandHandlers = [CreateOrphanSpaceHandler]; @Global() @Module({ - imports: [], + imports: [CqrsModule], controllers: [ProjectController], - providers: [ProjectService, ProjectRepository], - exports: [ProjectService], + providers: [ + ...CommandHandlers, + SpaceRepository, + CommunityRepository, + ProjectService, + ProjectRepository, + ], + exports: [ProjectService, CqrsModule], }) export class ProjectModule {} diff --git a/src/project/services/project.service.ts b/src/project/services/project.service.ts index 63ea252..75aa67a 100644 --- a/src/project/services/project.service.ts +++ b/src/project/services/project.service.ts @@ -10,10 +10,15 @@ import { } from '@app/common/models/typeOrmCustom.model'; import { ProjectDto } from '@app/common/modules/project/dtos'; import { PageResponse } from '@app/common/dto/pagination.response.dto'; +import { CommandBus } from '@nestjs/cqrs'; +import { CreateOrphanSpaceCommand } from '../command/create-orphan-space-command'; @Injectable() export class ProjectService { - constructor(private readonly projectRepository: ProjectRepository) {} + constructor( + private readonly projectRepository: ProjectRepository, + private commandBus: CommandBus, + ) {} async createProject( createProjectDto: CreateProjectDto, @@ -33,6 +38,8 @@ export class ProjectService { const newProject = this.projectRepository.create(createProjectDto); const savedProject = await this.projectRepository.save(newProject); + await this.commandBus.execute(new CreateOrphanSpaceCommand(savedProject)); + return new SuccessResponseDto({ message: `Project with ID ${savedProject.uuid} successfully created`, data: savedProject, @@ -44,7 +51,7 @@ export class ProjectService { } throw new HttpException( - 'An error occurred while creating the project. Please try again later.', + `An error occurred while creating the project. Please try again later. ${error}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } From e4a5068f0da0c1c5cfa29bd32cfd213586e94583 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 18 Dec 2024 08:23:42 +0400 Subject: [PATCH 076/247] updated community service to manage orphan community --- package-lock.json | 1675 ++++++++++++++++++- package.json | 1 + src/community/services/community.service.ts | 52 +- 3 files changed, 1665 insertions(+), 63 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a92384..a1d7cca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.2.0", "@nestjs/core": "^10.0.0", + "@nestjs/cqrs": "^10.2.8", "@nestjs/jwt": "^10.2.0", "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", @@ -55,6 +56,7 @@ "concurrently": "^8.2.2", "eslint": "^8.42.0", "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.31.0", "eslint-plugin-prettier": "^5.0.0", "jest": "^29.5.0", "prettier": "^3.0.0", @@ -2462,6 +2464,34 @@ } } }, + "node_modules/@nestjs/cqrs": { + "version": "10.2.8", + "resolved": "https://registry.npmjs.org/@nestjs/cqrs/-/cqrs-10.2.8.tgz", + "integrity": "sha512-nes4J9duwogme6CzNg8uhF5WVbSKYnNotjhHP+3kJxe6RTzcvJDZN10KpROjWJALEVO5fNmKnkGMecoOfqYzYA==", + "license": "MIT", + "dependencies": { + "uuid": "11.0.2" + }, + "peerDependencies": { + "@nestjs/common": "^9.0.0 || ^10.0.0", + "@nestjs/core": "^9.0.0 || ^10.0.0", + "reflect-metadata": "^0.1.13 || ^0.2.0", + "rxjs": "^7.2.0" + } + }, + "node_modules/@nestjs/cqrs/node_modules/uuid": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.2.tgz", + "integrity": "sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/@nestjs/jwt": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-10.2.0.tgz", @@ -2775,6 +2805,13 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -3028,6 +3065,13 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/jsonwebtoken": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", @@ -3736,11 +3780,49 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-timsort": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", @@ -3756,6 +3838,87 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -3783,6 +3946,22 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -4209,15 +4388,45 @@ } }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -4820,6 +5029,60 @@ "node": ">=0.10" } }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -4902,6 +5165,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -4914,6 +5178,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -5027,6 +5309,20 @@ "node": ">=12" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -5107,13 +5403,74 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "node_modules/es-abstract": { + "version": "1.23.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.6.tgz", + "integrity": "sha512-Ifco6n3yj2tMZDWNLyloZrytt9lqqlwvS83P3HtaETR0NUOYnIULGGHpktqYGObGy+8wc1okO25p8TjemhImvA==", + "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4" + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.7", + "get-intrinsic": "^1.2.6", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.0.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-regex-test": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.3", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.16" }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -5132,6 +5489,61 @@ "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", "dev": true }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -5224,6 +5636,183 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-import/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, "node_modules/eslint-plugin-prettier": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", @@ -5929,6 +6518,16 @@ } } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -6080,6 +6679,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function.prototype.name": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.7.tgz", + "integrity": "sha512-2g4x+HqTJKM9zcJqBSpjoRmdcPFtJM60J3xJisTQSXBWka5XqyBN/2tNUgma1mztTXyDuUsEtYe5qcs7xYzYQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gaxios": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", @@ -6127,15 +6756,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -6165,6 +6800,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -6227,6 +6880,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -6286,11 +6956,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6383,6 +7054,16 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6404,6 +7085,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -6412,9 +7094,14 @@ } }, "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -6423,9 +7110,26 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -6434,9 +7138,10 @@ } }, "node_modules/hasown": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", - "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -6660,6 +7365,21 @@ "node": ">=12.0.0" } }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", @@ -6728,12 +7448,62 @@ "node": ">= 0.10" } }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -6746,13 +7516,82 @@ "node": ">=8" } }, - "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "node_modules/is-boolean-object": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", + "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", "dev": true, + "license": "MIT", "dependencies": { - "hasown": "^2.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", + "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6767,6 +7606,22 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -6784,6 +7639,22 @@ "node": ">=6" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -6805,6 +7676,32 @@ "node": ">=8" } }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -6814,6 +7711,23 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -6823,6 +7737,54 @@ "node": ">=8" } }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -6834,6 +7796,57 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -6851,6 +7864,52 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", + "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -8011,6 +9070,15 @@ "tmpl": "1.0.5" } }, + "node_modules/math-intrinsics": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", + "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -8486,9 +9554,94 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -8995,6 +10148,16 @@ "node": ">=4" } }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -9329,12 +10492,54 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz", + "integrity": "sha512-B5dj6usc5dkk8uFliwjwDHM8To5/QwdKz9JcBZ8Ic4G1f0YmeeJTtE/ZTdgRFPAfxZFiUaPhZ1Jcs4qeagItGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "dunder-proto": "^1.0.0", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.2.0", + "which-builtin-type": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "dev": true }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", + "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -9639,6 +10844,33 @@ "tslib": "^2.1.0" } }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -9658,6 +10890,24 @@ } ] }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -9807,16 +11057,34 @@ } }, "node_modules/set-function-length": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", - "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", "dependencies": { - "define-data-property": "^1.1.2", + "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.3", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -9927,14 +11195,69 @@ } }, "node_modules/side-channel": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", - "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -10180,6 +11503,65 @@ "node": ">=8" } }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -10809,6 +12191,84 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.3.tgz", + "integrity": "sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -10980,6 +12440,25 @@ "node": ">=8" } }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undici": { "version": "5.28.4", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", @@ -11300,6 +12779,100 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.16.tgz", + "integrity": "sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index 961ef38..6598f15 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.2.0", "@nestjs/core": "^10.0.0", + "@nestjs/cqrs": "^10.2.8", "@nestjs/jwt": "^10.2.0", "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index c21d20a..eee72d1 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -12,6 +12,8 @@ import { CommunityDto } from '@app/common/modules/community/dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; +import { ORPHAN_COMMUNITY_NAME } from '@app/common/constants/orphan-constant'; +import { Not } from 'typeorm'; @Injectable() export class CommunityService { @@ -90,20 +92,31 @@ export class CommunityService { param: ProjectParam, pageable: Partial, ): Promise { - await this.validateProject(param.projectUuid); + try { + const project = await this.validateProject(param.projectUuid); - pageable.modelName = 'community'; - pageable.where = { project: { uuid: param.projectUuid } }; + pageable.modelName = 'community'; + pageable.where = { + project: { uuid: param.projectUuid }, + name: Not(`${ORPHAN_COMMUNITY_NAME}-${project.name}`), + }; - const customModel = TypeORMCustomModel(this.communityRepository); + const customModel = TypeORMCustomModel(this.communityRepository); - const { baseResponseDto, paginationResponseDto } = - await customModel.findAll(pageable); + const { baseResponseDto, paginationResponseDto } = + await customModel.findAll(pageable); - return new PageResponse( - baseResponseDto, - paginationResponseDto, - ); + return new PageResponse( + baseResponseDto, + paginationResponseDto, + ); + } catch (error) { + // Generic error handling + throw new HttpException( + error.message || 'An error occurred while fetching communities.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } } async updateCommunity( @@ -112,7 +125,7 @@ export class CommunityService { ): Promise { const { communityUuid, projectUuid } = params; - await this.validateProject(projectUuid); + const project = await this.validateProject(projectUuid); const community = await this.communityRepository.findOne({ where: { uuid: communityUuid }, @@ -126,6 +139,13 @@ export class CommunityService { ); } + if (community.name === `${ORPHAN_COMMUNITY_NAME}-${project.name}`) { + throw new HttpException( + `Community with ID ${communityUuid} cannot be updated`, + HttpStatus.BAD_REQUEST, + ); + } + try { const { name } = updateCommunityDto; @@ -151,7 +171,7 @@ export class CommunityService { async deleteCommunity(params: GetCommunityParams): Promise { const { communityUuid, projectUuid } = params; - await this.validateProject(projectUuid); + const project = await this.validateProject(projectUuid); const community = await this.communityRepository.findOne({ where: { uuid: communityUuid }, @@ -164,6 +184,14 @@ export class CommunityService { HttpStatus.NOT_FOUND, ); } + + if (community.name === `${ORPHAN_COMMUNITY_NAME}-${project.name}`) { + throw new HttpException( + `Community with ID ${communityUuid} cannot be deleted`, + HttpStatus.BAD_REQUEST, + ); + } + try { await this.communityRepository.remove(community); From 70e1fe2ae5625128fc3deabb1513d850934be242 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 18 Dec 2024 08:30:30 +0400 Subject: [PATCH 077/247] added constrains for orphan space in services --- src/space/services/space.service.ts | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 59aceb5..df0b0a4 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -20,8 +20,9 @@ import { SpaceLinkService } from './space-link'; import { SpaceProductService } from './space-products'; import { CreateSubspaceModelDto } from 'src/space-model/dtos'; import { SubSpaceService } from './subspace'; -import { DataSource } from 'typeorm'; +import { DataSource, Not } from 'typeorm'; import { ValidationService } from './space-validation.service'; +import { ORPHAN_SPACE_NAME } from '@app/common/constants/orphan-constant'; @Injectable() export class SpaceService { @@ -42,6 +43,13 @@ export class SpaceService { addSpaceDto; const { communityUuid, projectUuid } = params; + if (addSpaceDto.spaceName === ORPHAN_SPACE_NAME) { + throw new HttpException( + `Name ${ORPHAN_SPACE_NAME} cannot be used`, + HttpStatus.BAD_REQUEST, + ); + } + const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); @@ -137,7 +145,10 @@ export class SpaceService { try { // Get all spaces related to the community, including the parent-child relations const spaces = await this.spaceRepository.find({ - where: { community: { uuid: communityUuid } }, + where: { + community: { uuid: communityUuid }, + spaceName: Not(`${ORPHAN_SPACE_NAME}`), + }, relations: [ 'parent', 'children', @@ -200,6 +211,12 @@ export class SpaceService { spaceUuid, ); + if (space.spaceName === ORPHAN_SPACE_NAME) { + throw new HttpException( + `space ${ORPHAN_SPACE_NAME} cannot be deleted`, + HttpStatus.BAD_REQUEST, + ); + } // Delete the space await this.spaceRepository.remove(space); @@ -236,6 +253,13 @@ export class SpaceService { spaceUuid, ); + if (space.spaceName === ORPHAN_SPACE_NAME) { + throw new HttpException( + `space ${ORPHAN_SPACE_NAME} cannot be updated`, + HttpStatus.BAD_REQUEST, + ); + } + // If a parentId is provided, check if the parent exists const { parentUuid, products } = updateSpaceDto; const parent = parentUuid From 196abad8babd590bb9dcf89c53be6f4437d3d3b7 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 18 Dec 2024 10:13:42 +0400 Subject: [PATCH 078/247] fixed issue in getting device details from space --- src/space/services/space-device.service.ts | 14 ++++++++------ src/space/services/space.service.ts | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index 0a262a1..44880c2 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -1,22 +1,17 @@ import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; -import { CommunityRepository } from '@app/common/modules/community/repositories'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; + import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { GetDeviceDetailsInterface } from 'src/device/interfaces/get.device.interface'; import { GetSpaceParam } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; -import { ProductRepository } from '@app/common/modules/product/repositories'; import { SpaceService } from './space.service'; @Injectable() export class SpaceDeviceService { constructor( - private readonly spaceRepository: SpaceRepository, private readonly tuyaService: TuyaService, - private readonly productRepository: ProductRepository, - private readonly communityRepository: CommunityRepository, private readonly spaceService: SpaceService, ) {} @@ -29,6 +24,13 @@ export class SpaceDeviceService { projectUuid, ); + if (!Array.isArray(space.devices)) { + throw new HttpException( + 'The space does not contain any devices or the devices property is invalid.', + HttpStatus.BAD_REQUEST, + ); + } + const safeFetch = async (device: any) => { try { const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya( diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 8189f8c..b40723f 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -325,6 +325,7 @@ export class SpaceService { private async validateSpace(spaceUuid: string) { const space = await this.spaceRepository.findOne({ where: { uuid: spaceUuid }, + relations: ['devices'], }); if (!space) this.throwNotFound('Space', spaceUuid); return space; From 422e3b1581ab64dd1c06c000ecd1cfc4e170c1e8 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 18 Dec 2024 10:21:50 +0400 Subject: [PATCH 079/247] added log --- src/space/services/space-device.service.ts | 3 +-- src/space/services/space.service.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index 44880c2..7dff75a 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -30,7 +30,6 @@ export class SpaceDeviceService { HttpStatus.BAD_REQUEST, ); } - const safeFetch = async (device: any) => { try { const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya( @@ -48,7 +47,7 @@ export class SpaceDeviceService { }; } catch (error) { console.warn( - `Skipping device with deviceTuyaUuid: ${device.deviceTuyaUuid} due to error.`, + `Skipping device with deviceTuyaUuid: ${device.deviceTuyaUuid} due to error. ${error}`, ); return null; } diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index b40723f..0061226 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -325,7 +325,7 @@ export class SpaceService { private async validateSpace(spaceUuid: string) { const space = await this.spaceRepository.findOne({ where: { uuid: spaceUuid }, - relations: ['devices'], + relations: ['devices', 'devices.productDevice'], }); if (!space) this.throwNotFound('Space', spaceUuid); return space; From 7833151b7094a830b5b049cf579830aa5e685c7c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 18 Dec 2024 11:40:45 +0400 Subject: [PATCH 080/247] list space-model --- libs/common/src/constants/controller-route.ts | 4 ++ .../src/constants/permissions-mapping.ts | 1 + libs/common/src/constants/role-permissions.ts | 2 + libs/common/src/models/typeOrmCustom.model.ts | 5 ++- .../src/util/buildTypeORMIncludeQuery.ts | 45 +++++++++++++++---- .../controllers/space-model.controller.ts | 27 ++++++++++- .../services/space-model.service.ts | 38 ++++++++++++++++ 7 files changed, 111 insertions(+), 11 deletions(-) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index c26deca..f6cbfe1 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -273,6 +273,10 @@ export class ControllerRoute { 'Create a New Space Model'; public static readonly CREATE_SPACE_MODEL_DESCRIPTION = 'This endpoint allows you to create a new space model within a specified project. A space model defines the structure of spaces, including subspaces, products, and product items, and is uniquely identifiable within the project.'; + + public static readonly LIST_SPACE_MODEL_SUMMARY = 'List Space Models'; + public static readonly LIST_SPACE_MODEL_DESCRIPTION = + 'This endpoint allows you to retrieve a list of space models within a specified project. Each space model includes its structure, associated subspaces, products, and product items.'; }; }; diff --git a/libs/common/src/constants/permissions-mapping.ts b/libs/common/src/constants/permissions-mapping.ts index a9db542..e940933 100644 --- a/libs/common/src/constants/permissions-mapping.ts +++ b/libs/common/src/constants/permissions-mapping.ts @@ -13,6 +13,7 @@ export const PermissionMapping = { 'UPDATE', 'DELETE', 'MODULE_ADD', + 'MODEL_VIEW', 'ASSIGN_USER_TO_SPACE', 'DELETE_USER_FROM_SPACE', ], diff --git a/libs/common/src/constants/role-permissions.ts b/libs/common/src/constants/role-permissions.ts index ed61e5d..aae658a 100644 --- a/libs/common/src/constants/role-permissions.ts +++ b/libs/common/src/constants/role-permissions.ts @@ -17,6 +17,7 @@ export const RolePermissions = { 'SPACE_UPDATE', 'SPACE_DELETE', 'SPACE_MODULE_ADD', + 'SPACE_MODEL_VIEW', 'ASSIGN_USER_TO_SPACE', 'DELETE_USER_FROM_SPACE', 'SUBSPACE_VIEW', @@ -60,6 +61,7 @@ export const RolePermissions = { 'SPACE_UPDATE', 'SPACE_DELETE', 'SPACE_MODULE_ADD', + 'SPACE_MODEL_VIEW', 'ASSIGN_USER_TO_SPACE', 'DELETE_USER_FROM_SPACE', 'SUBSPACE_VIEW', diff --git a/libs/common/src/models/typeOrmCustom.model.ts b/libs/common/src/models/typeOrmCustom.model.ts index 0f3abbc..2f11102 100644 --- a/libs/common/src/models/typeOrmCustom.model.ts +++ b/libs/common/src/models/typeOrmCustom.model.ts @@ -31,7 +31,7 @@ interface FindAllQueryWithDefaults extends CustomFindAllQuery { function getDefaultQueryOptions( query: Partial, ): FindManyOptions & FindAllQueryWithDefaults { - const { page, size, includeDisable, modelName, ...rest } = query; + const { page, size, includeDisable, include, modelName, ...rest } = query; // Set default if undefined or null const returnPage = page ? Number(page) : 1; @@ -48,7 +48,9 @@ function getDefaultQueryOptions( }, page: returnPage, size: returnSize, + include: include || undefined, includeDisable: returnIncludeDisable, + modelName: modelName || query.modelName, // Ensure modelName is passed through }; } @@ -97,7 +99,6 @@ export function TypeORMCustomModel(repository: Repository) { // Use the where clause directly, without wrapping it under 'where' const whereClause = buildTypeORMWhereClause({ where }); - console.log('Final where clause:', whereClause); // Ensure the whereClause is passed directly to findAndCount const [data, count] = await repository.findAndCount({ diff --git a/libs/common/src/util/buildTypeORMIncludeQuery.ts b/libs/common/src/util/buildTypeORMIncludeQuery.ts index 029bd67..bffd5be 100644 --- a/libs/common/src/util/buildTypeORMIncludeQuery.ts +++ b/libs/common/src/util/buildTypeORMIncludeQuery.ts @@ -19,6 +19,19 @@ const mappingInclude: { [key: string]: any } = { project: { project: true, }, + 'space-model': { + subspaceModels: 'subspace-model', + spaceProductModels: 'space-product-model', + }, + 'subspace-model': { + productModels: 'subspace-product-model', + }, + 'subspace-product-model': { + itemModels: true, + }, + 'space-product-model': { + items: true, + }, }; export function buildTypeORMIncludeQuery( @@ -30,17 +43,33 @@ export function buildTypeORMIncludeQuery( const fieldsToInclude: string[] = includeParam.split(','); fieldsToInclude.forEach((field: string) => { - if (mappingInclude[field]) { - relations.push(field); // Push mapped field - } else { - console.warn( - `Field ${field} not found in mappingInclude for ${modelName}`, - ); + const nestedFields = field.split('.'); + let currentModelName = modelName; + let isValid = true; + + nestedFields.forEach((nestedField, index) => { + const currentMapping = mappingInclude[currentModelName]; + if (currentMapping?.[nestedField]) { + currentModelName = + typeof currentMapping[nestedField] === 'string' + ? currentMapping[nestedField] + : currentModelName; + } else { + console.warn( + `Field "${nestedFields.slice(0, index + 1).join('.')}" not found in mappingInclude for model "${currentModelName}"`, + ); + isValid = false; + return; + } + }); + + if (isValid) { + relations.push(field); } }); - return relations; + return relations.length ? relations : undefined; } - return undefined; // If no includes, return undefined + return undefined; } diff --git a/src/space-model/controllers/space-model.controller.ts b/src/space-model/controllers/space-model.controller.ts index bedb81e..d994ba6 100644 --- a/src/space-model/controllers/space-model.controller.ts +++ b/src/space-model/controllers/space-model.controller.ts @@ -1,5 +1,13 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; -import { Body, Controller, Param, Post, UseGuards } from '@nestjs/common'; +import { + Body, + Controller, + Get, + Param, + Post, + Query, + UseGuards, +} from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { SpaceModelService } from '../services'; import { CreateSpaceModelDto } from '../dtos'; @@ -7,6 +15,7 @@ import { ProjectParam } from 'src/community/dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { PermissionsGuard } from 'src/guards/permissions.guard'; import { Permissions } from 'src/decorators/permissions.decorator'; +import { PaginationRequestGetListDto } from '@app/common/dto/pagination.request.dto'; @ApiTags('Space Model Module') @Controller({ @@ -34,4 +43,20 @@ export class SpaceModelController { projectParam, ); } + + @ApiBearerAuth() + @UseGuards(PermissionsGuard) + @Permissions('SPACE_MODEL_VIEW') + @ApiOperation({ + summary: ControllerRoute.SPACE_MODEL.ACTIONS.LIST_SPACE_MODEL_SUMMARY, + description: + ControllerRoute.SPACE_MODEL.ACTIONS.LIST_SPACE_MODEL_DESCRIPTION, + }) + @Get() + async listSpaceModel( + @Param() projectParam: ProjectParam, + @Query() query: PaginationRequestGetListDto, + ): Promise { + return await this.spaceModelService.list(projectParam, query); + } } diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 0423c42..0ce4083 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -7,6 +7,12 @@ import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SubSpaceModelService } from './subspace/subspace-model.service'; import { SpaceProductModelService } from './space-product-model.service'; import { DataSource } from 'typeorm'; +import { + TypeORMCustomModel, + TypeORMCustomModelFindAllQuery, +} from '@app/common/models/typeOrmCustom.model'; +import { PageResponse } from '@app/common/dto/pagination.response.dto'; +import { SpaceModelDto } from '@app/common/modules/space-model/dtos'; @Injectable() export class SpaceModelService { @@ -87,6 +93,38 @@ export class SpaceModelService { } } + async list( + param: ProjectParam, + pageable: Partial, + ) { + await this.validateProject(param.projectUuid); + + try { + pageable.modelName = 'space-model'; + pageable.where = { + project: { uuid: param.projectUuid }, + }; + pageable.include = + 'subspaceModels,spaceProductModels,subspaceModels.productModels,subspaceModels.productModels.itemModels,spaceProductModels.items'; + + const customModel = TypeORMCustomModel(this.spaceModelRepository); + + const { baseResponseDto, paginationResponseDto } = + await customModel.findAll(pageable); + + return new PageResponse( + baseResponseDto, + paginationResponseDto, + ); + } catch (error) { + throw new HttpException( + error.message || + 'An error occurred while fetching space models in project.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async validateProject(projectUuid: string) { const project = await this.projectRepository.findOne({ where: { From 85aa64ac369e8700d0f3629a6428e154a4ac8fc4 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 18 Dec 2024 15:00:34 +0400 Subject: [PATCH 081/247] added edit space module endpoint --- libs/common/src/constants/controller-route.ts | 4 ++ libs/common/src/constants/role-permissions.ts | 2 + .../controllers/space-model.controller.ts | 23 ++++++- src/space-model/dtos/index.ts | 2 + src/space-model/dtos/project-param.dto.ts | 2 +- src/space-model/dtos/space-model-param.ts | 11 ++++ .../dtos/update-space-model.dto.ts | 12 ++++ .../services/space-model.service.ts | 64 ++++++++++++++----- 8 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 src/space-model/dtos/space-model-param.ts create mode 100644 src/space-model/dtos/update-space-model.dto.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index f6cbfe1..aace4d6 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -277,6 +277,10 @@ export class ControllerRoute { public static readonly LIST_SPACE_MODEL_SUMMARY = 'List Space Models'; public static readonly LIST_SPACE_MODEL_DESCRIPTION = 'This endpoint allows you to retrieve a list of space models within a specified project. Each space model includes its structure, associated subspaces, products, and product items.'; + + public static readonly UPDATE_SPACE_MODEL_SUMMARY = 'Update Space Model'; + public static readonly UPDATE_SPACE_MODEL_DESCRIPTION = + 'This endpoint allows you to update a Space Model attributesas well as manage its associated Subspaces and Device'; }; }; diff --git a/libs/common/src/constants/role-permissions.ts b/libs/common/src/constants/role-permissions.ts index aae658a..a7163c3 100644 --- a/libs/common/src/constants/role-permissions.ts +++ b/libs/common/src/constants/role-permissions.ts @@ -18,6 +18,7 @@ export const RolePermissions = { 'SPACE_DELETE', 'SPACE_MODULE_ADD', 'SPACE_MODEL_VIEW', + 'SPACE_MODEL_UPDATE', 'ASSIGN_USER_TO_SPACE', 'DELETE_USER_FROM_SPACE', 'SUBSPACE_VIEW', @@ -62,6 +63,7 @@ export const RolePermissions = { 'SPACE_DELETE', 'SPACE_MODULE_ADD', 'SPACE_MODEL_VIEW', + 'SPACE_MODEL_UPDATE', 'ASSIGN_USER_TO_SPACE', 'DELETE_USER_FROM_SPACE', 'SUBSPACE_VIEW', diff --git a/src/space-model/controllers/space-model.controller.ts b/src/space-model/controllers/space-model.controller.ts index d994ba6..ba47ae9 100644 --- a/src/space-model/controllers/space-model.controller.ts +++ b/src/space-model/controllers/space-model.controller.ts @@ -5,12 +5,17 @@ import { Get, Param, Post, + Put, Query, UseGuards, } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { SpaceModelService } from '../services'; -import { CreateSpaceModelDto } from '../dtos'; +import { + CreateSpaceModelDto, + SpaceModelParam, + UpdateSpaceModelDto, +} from '../dtos'; import { ProjectParam } from 'src/community/dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { PermissionsGuard } from 'src/guards/permissions.guard'; @@ -59,4 +64,20 @@ export class SpaceModelController { ): Promise { return await this.spaceModelService.list(projectParam, query); } + + @ApiBearerAuth() + @UseGuards(PermissionsGuard) + @Permissions('SPACE_MODEL_UPDATE') + @ApiOperation({ + summary: ControllerRoute.SPACE_MODEL.ACTIONS.UPDATE_SPACE_MODEL_SUMMARY, + description: + ControllerRoute.SPACE_MODEL.ACTIONS.UPDATE_SPACE_MODEL_DESCRIPTION, + }) + @Put(':spaceModelUuid') + async update( + @Body() dto: UpdateSpaceModelDto, + @Param() param: SpaceModelParam, + ): Promise { + return await this.spaceModelService.update(dto, param); + } } diff --git a/src/space-model/dtos/index.ts b/src/space-model/dtos/index.ts index b4b1e07..cc4e0df 100644 --- a/src/space-model/dtos/index.ts +++ b/src/space-model/dtos/index.ts @@ -3,3 +3,5 @@ export * from './create-space-product-item-model.dto'; export * from './create-space-product-model.dto'; export * from './create-subspace-model.dto'; export * from './project-param.dto'; +export * from './update-space-model.dto'; +export * from './space-model-param'; diff --git a/src/space-model/dtos/project-param.dto.ts b/src/space-model/dtos/project-param.dto.ts index e7d9e97..69e09b5 100644 --- a/src/space-model/dtos/project-param.dto.ts +++ b/src/space-model/dtos/project-param.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsUUID } from 'class-validator'; -export class projectParam { +export class ProjectParam { @ApiProperty({ description: 'UUID of the Project', example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', diff --git a/src/space-model/dtos/space-model-param.ts b/src/space-model/dtos/space-model-param.ts new file mode 100644 index 0000000..2111546 --- /dev/null +++ b/src/space-model/dtos/space-model-param.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUUID } from 'class-validator'; +import { ProjectParam } from './project-param.dto'; +export class SpaceModelParam extends ProjectParam { + @ApiProperty({ + description: 'UUID of the Space', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + }) + @IsUUID() + spaceModelUuid: string; +} diff --git a/src/space-model/dtos/update-space-model.dto.ts b/src/space-model/dtos/update-space-model.dto.ts new file mode 100644 index 0000000..fc2f6aa --- /dev/null +++ b/src/space-model/dtos/update-space-model.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class UpdateSpaceModelDto { + @ApiProperty({ + description: 'Updated name of the space model', + example: 'New Space Model Name', + }) + @IsOptional() + @IsString() + modelName?: string; +} diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 0ce4083..dc1a11e 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -1,7 +1,9 @@ -import { SpaceModelRepository } from '@app/common/modules/space-model'; +import { + SpaceModelEntity, + SpaceModelRepository, +} from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { CreateSpaceModelDto } from '../dtos'; -import { ProjectRepository } from '@app/common/modules/project/repositiories'; +import { CreateSpaceModelDto, UpdateSpaceModelDto } from '../dtos'; import { ProjectParam } from 'src/community/dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SubSpaceModelService } from './subspace/subspace-model.service'; @@ -13,13 +15,16 @@ import { } from '@app/common/models/typeOrmCustom.model'; import { PageResponse } from '@app/common/dto/pagination.response.dto'; import { SpaceModelDto } from '@app/common/modules/space-model/dtos'; +import { SpaceModelParam } from '../dtos/space-model-param'; +import { ProjectService } from 'src/project/services'; +import { ProjectEntity } from '@app/common/modules/project/entities'; @Injectable() export class SpaceModelService { constructor( private readonly dataSource: DataSource, private readonly spaceModelRepository: SpaceModelRepository, - private readonly projectRepository: ProjectRepository, + private readonly projectService: ProjectService, private readonly subSpaceModelService: SubSpaceModelService, private readonly spaceProductModelService: SpaceProductModelService, ) {} @@ -44,7 +49,7 @@ export class SpaceModelService { ); if (isModelExist) { throw new HttpException( - `Model name "${modelName}" already exists in this project ${project.name}.`, + `Model name "${modelName}" already exists in this project ${params.projectUuid}.`, HttpStatus.CONFLICT, ); } @@ -125,20 +130,37 @@ export class SpaceModelService { } } - async validateProject(projectUuid: string) { - const project = await this.projectRepository.findOne({ - where: { - uuid: projectUuid, - }, - }); + async validateProject(projectUuid: string): Promise { + return await this.projectService.findOne(projectUuid); + } - if (!project) { + async update(dto: UpdateSpaceModelDto, param: SpaceModelParam) { + await this.validateProject(param.projectUuid); + const spaceModel = await this.validateSpaceModel(param.spaceModelUuid); + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + try { + const { modelName } = dto; + if (modelName) spaceModel.modelName = modelName; + + await queryRunner.manager.save(spaceModel); + + await queryRunner.commitTransaction(); + + return new SuccessResponseDto({ + message: 'SpaceModel updated successfully', + data: spaceModel, + }); + } catch (error) { + await queryRunner.rollbackTransaction(); throw new HttpException( - `Project with uuid ${projectUuid} not found`, - HttpStatus.NOT_FOUND, + error.message || 'Failed to update SpaceModel', + HttpStatus.INTERNAL_SERVER_ERROR, ); + } finally { + await queryRunner.release(); } - return project; } async validateName(modelName: string, projectUuid: string): Promise { @@ -147,4 +169,16 @@ export class SpaceModelService { }); return isModelExist; } + + async validateSpaceModel(uuid: string): Promise { + const spaceModel = await this.spaceModelRepository.findOne({ + where: { + uuid, + }, + }); + if (!spaceModel) { + throw new HttpException('space model not found', HttpStatus.NOT_FOUND); + } + return spaceModel; + } } From 23d3cd620c20a1c29bf853f909ff373d2bc9e295 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 18 Dec 2024 16:21:17 +0400 Subject: [PATCH 082/247] return new SubspaceModels onn space update --- .../dtos/update-space-model.dto.ts | 19 ++++++- .../services/space-model.service.ts | 9 ++++ .../subspace/subspace-model.service.ts | 51 +++++++++++++++---- .../subspace-product-item-model.service.ts | 4 +- .../subspace-product-model.service.ts | 24 ++++++--- 5 files changed, 86 insertions(+), 21 deletions(-) diff --git a/src/space-model/dtos/update-space-model.dto.ts b/src/space-model/dtos/update-space-model.dto.ts index fc2f6aa..09b196d 100644 --- a/src/space-model/dtos/update-space-model.dto.ts +++ b/src/space-model/dtos/update-space-model.dto.ts @@ -1,6 +1,20 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsOptional, IsString } from 'class-validator'; +import { IsArray, IsOptional, IsString, ValidateNested } from 'class-validator'; +import { CreateSubspaceModelDto } from './create-subspace-model.dto'; +import { Type } from 'class-transformer'; +export class UpdateSubspaceModelDto { + @ApiProperty({ + description: 'List of subspaces to add', + type: [CreateSubspaceModelDto], + required: false, + }) + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CreateSubspaceModelDto) + add?: CreateSubspaceModelDto[]; +} export class UpdateSpaceModelDto { @ApiProperty({ description: 'Updated name of the space model', @@ -9,4 +23,7 @@ export class UpdateSpaceModelDto { @IsOptional() @IsString() modelName?: string; + + @IsOptional() + subspaceModels?: UpdateSubspaceModelDto; } diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index dc1a11e..805ffa0 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -146,6 +146,15 @@ export class SpaceModelService { await queryRunner.manager.save(spaceModel); + if (dto.subspaceModels) { + const updatedSubspaces = + await this.subSpaceModelService.updateSubSpaceModels( + dto.subspaceModels, + spaceModel, + queryRunner, + ); + } + await queryRunner.commitTransaction(); return new SuccessResponseDto({ diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index 222dee4..6814362 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -3,7 +3,7 @@ import { SubspaceModelRepository, } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { CreateSubspaceModelDto } from '../../dtos'; +import { CreateSubspaceModelDto, UpdateSubspaceModelDto } from '../../dtos'; import { QueryRunner } from 'typeorm'; import { SubspaceProductModelService } from './subspace-product-model.service'; @@ -29,19 +29,25 @@ export class SubSpaceModelService { }), ); - await queryRunner.manager.save(subspaces); + const newSubspaces = await queryRunner.manager.save(subspaces); - await Promise.all( - subSpaceModelDtos.map((dto, index) => { - const subspaceModel = subspaces[index]; - return this.subSpaceProducetModelService.createSubspaceProductModels( - dto.spaceProductModels, - spaceModel, + const addedSubspaces = await Promise.all( + subSpaceModelDtos.map(async (dto, index) => { + const subspaceModel = newSubspaces[index]; + const productModels = + await this.subSpaceProducetModelService.createSubspaceProductModels( + dto.spaceProductModels, + spaceModel, + subspaceModel, + queryRunner, + ); + return { subspaceModel, - queryRunner, - ); + productModels, + }; }), ); + return addedSubspaces; } catch (error) { if (error instanceof HttpException) { throw error; @@ -61,7 +67,6 @@ export class SubSpaceModelService { HttpStatus.BAD_REQUEST, ); } - const incomingNames = subSpaceModelDtos.map((dto) => dto.subspaceName); this.validateName(incomingNames); } @@ -85,4 +90,28 @@ export class SubSpaceModelService { ); } } + + async updateSubSpaceModels( + dto: UpdateSubspaceModelDto, + spaceModel: SpaceModelEntity, + queryRunner: QueryRunner, + ) { + const subspaces: { new?: any } = {}; + try { + if (dto.add) { + const addedSubspaces = await this.createSubSpaceModels( + dto.add, + spaceModel, + queryRunner, + ); + subspaces.new = addedSubspaces; + } + return subspaces; + } catch (error) { + throw new HttpException( + error.message || 'Failed to update SpaceModel', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/space-model/services/subspace/subspace-product-item-model.service.ts b/src/space-model/services/subspace/subspace-product-item-model.service.ts index 393a5f3..85989f8 100644 --- a/src/space-model/services/subspace/subspace-product-item-model.service.ts +++ b/src/space-model/services/subspace/subspace-product-item-model.service.ts @@ -2,6 +2,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { QueryRunner } from 'typeorm'; import { SpaceModelEntity, + SubspaceProductItemModelEntity, SubspaceProductItemModelRepository, SubspaceProductModelEntity, } from '@app/common/modules/space-model'; @@ -21,7 +22,7 @@ export class SubspaceProductItemModelService extends BaseProductItemService { subspaceProductModel: SubspaceProductModelEntity, spaceModel: SpaceModelEntity, queryRunner: QueryRunner, - ) { + ): Promise { if (!subspaceProductModel) { throw new HttpException( 'The spaceProductModel parameter is required but was not provided.', @@ -38,6 +39,7 @@ export class SubspaceProductItemModelService extends BaseProductItemService { ); await queryRunner.manager.save(productItems); + return productItems; } catch (error) { if (error instanceof HttpException) { throw error; diff --git a/src/space-model/services/subspace/subspace-product-model.service.ts b/src/space-model/services/subspace/subspace-product-model.service.ts index 30f9062..8b3f9ac 100644 --- a/src/space-model/services/subspace/subspace-product-model.service.ts +++ b/src/space-model/services/subspace/subspace-product-model.service.ts @@ -27,6 +27,8 @@ export class SubspaceProductModelService extends BaseProductModelService { queryRunner: QueryRunner, ) { try { + if (!spaceProductModelDtos?.length) return; + const productModels = await Promise.all( spaceProductModelDtos.map(async (dto) => { this.validateProductCount(dto); @@ -44,17 +46,23 @@ export class SubspaceProductModelService extends BaseProductModelService { const savedProductModels = await queryRunner.manager.save(productModels); - await Promise.all( - spaceProductModelDtos.map((dto, index) => { + const newProductModels = await Promise.all( + spaceProductModelDtos.map(async (dto, index) => { const savedModel = savedProductModels[index]; - return this.subspaceProductItemModelService.createProdutItemModel( - dto.items, - savedModel, // Pass the saved model - spaceModel, - queryRunner, - ); + const productItemModels = + await this.subspaceProductItemModelService.createProdutItemModel( + dto.items, + savedModel, + spaceModel, + queryRunner, + ); + return { + productModel: savedModel, + productItemModels, + }; }), ); + return newProductModels; } catch (error) { if (error instanceof HttpException) { throw error; From a771fa8ee50064176296d89a295874653802474b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 19 Dec 2024 09:12:26 +0400 Subject: [PATCH 083/247] propagate add --- src/space-model/commands/index.ts | 1 + .../propogate-subspace-update-command.ts | 6 + src/space-model/dtos/index.ts | 1 + .../dtos/update-space-model.dto.ts | 17 +- .../dtos/update-subspace-model.dto.ts | 30 +++ src/space-model/handlers/index.ts | 1 + .../handlers/propate-subspace-handler.ts | 177 ++++++++++++++++++ src/space-model/interfaces/index.ts | 1 + .../interfaces/update-subspace.interface.ts | 19 ++ .../services/space-model.service.ts | 22 ++- .../subspace/subspace-model.service.ts | 11 +- src/space-model/space-model.module.ts | 19 +- 12 files changed, 291 insertions(+), 14 deletions(-) create mode 100644 src/space-model/commands/index.ts create mode 100644 src/space-model/commands/propogate-subspace-update-command.ts create mode 100644 src/space-model/dtos/update-subspace-model.dto.ts create mode 100644 src/space-model/handlers/index.ts create mode 100644 src/space-model/handlers/propate-subspace-handler.ts create mode 100644 src/space-model/interfaces/index.ts create mode 100644 src/space-model/interfaces/update-subspace.interface.ts diff --git a/src/space-model/commands/index.ts b/src/space-model/commands/index.ts new file mode 100644 index 0000000..716d458 --- /dev/null +++ b/src/space-model/commands/index.ts @@ -0,0 +1 @@ +export * from './propogate-subspace-update-command'; diff --git a/src/space-model/commands/propogate-subspace-update-command.ts b/src/space-model/commands/propogate-subspace-update-command.ts new file mode 100644 index 0000000..286b9bd --- /dev/null +++ b/src/space-model/commands/propogate-subspace-update-command.ts @@ -0,0 +1,6 @@ +import { ICommand } from '@nestjs/cqrs'; +import { UpdatedSubspaceModelInterface } from '../interfaces'; + +export class PropogateSubspaceCommand implements ICommand { + constructor(public readonly param: UpdatedSubspaceModelInterface) {} +} diff --git a/src/space-model/dtos/index.ts b/src/space-model/dtos/index.ts index cc4e0df..e7b2373 100644 --- a/src/space-model/dtos/index.ts +++ b/src/space-model/dtos/index.ts @@ -5,3 +5,4 @@ export * from './create-subspace-model.dto'; export * from './project-param.dto'; export * from './update-space-model.dto'; export * from './space-model-param'; +export * from './update-subspace-model.dto'; diff --git a/src/space-model/dtos/update-space-model.dto.ts b/src/space-model/dtos/update-space-model.dto.ts index 09b196d..daaa62b 100644 --- a/src/space-model/dtos/update-space-model.dto.ts +++ b/src/space-model/dtos/update-space-model.dto.ts @@ -2,8 +2,9 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsArray, IsOptional, IsString, ValidateNested } from 'class-validator'; import { CreateSubspaceModelDto } from './create-subspace-model.dto'; import { Type } from 'class-transformer'; +import { UpdateSubspaceModelDto } from './update-subspace-model.dto'; -export class UpdateSubspaceModelDto { +export class UpdateSubspacesModelDto { @ApiProperty({ description: 'List of subspaces to add', type: [CreateSubspaceModelDto], @@ -14,7 +15,19 @@ export class UpdateSubspaceModelDto { @ValidateNested({ each: true }) @Type(() => CreateSubspaceModelDto) add?: CreateSubspaceModelDto[]; + + @ApiProperty({ + description: 'List of subspaces to add', + type: [CreateSubspaceModelDto], + required: false, + }) + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => UpdateSubspaceModelDto) + update?: UpdateSubspaceModelDto[]; } + export class UpdateSpaceModelDto { @ApiProperty({ description: 'Updated name of the space model', @@ -25,5 +38,5 @@ export class UpdateSpaceModelDto { modelName?: string; @IsOptional() - subspaceModels?: UpdateSubspaceModelDto; + subspaceModels?: UpdateSubspacesModelDto; } diff --git a/src/space-model/dtos/update-subspace-model.dto.ts b/src/space-model/dtos/update-subspace-model.dto.ts new file mode 100644 index 0000000..da71afc --- /dev/null +++ b/src/space-model/dtos/update-subspace-model.dto.ts @@ -0,0 +1,30 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsNotEmpty, + IsString, + IsArray, + IsOptional, + ValidateNested, +} from 'class-validator'; +import { CreateSpaceProductModelDto } from './create-space-product-model.dto'; + +export class UpdateSubspaceModelDto { + @ApiProperty({ + description: 'Name of the subspace', + example: 'Living Room', + }) + @IsNotEmpty() + @IsString() + subspaceName?: string; + + @ApiProperty({ + description: 'List of products included in the model', + type: [CreateSpaceProductModelDto], + }) + @IsArray() + @IsOptional() + @ValidateNested({ each: true }) + @Type(() => CreateSpaceProductModelDto) + spaceProductModels?: CreateSpaceProductModelDto[]; +} diff --git a/src/space-model/handlers/index.ts b/src/space-model/handlers/index.ts new file mode 100644 index 0000000..7dfa7f5 --- /dev/null +++ b/src/space-model/handlers/index.ts @@ -0,0 +1 @@ +export * from './propate-subspace-handler'; diff --git a/src/space-model/handlers/propate-subspace-handler.ts b/src/space-model/handlers/propate-subspace-handler.ts new file mode 100644 index 0000000..e7a4db7 --- /dev/null +++ b/src/space-model/handlers/propate-subspace-handler.ts @@ -0,0 +1,177 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { PropogateSubspaceCommand } from '../commands'; +import { Logger } from '@nestjs/common'; +import { SpaceEntity, SpaceRepository } from '@app/common/modules/space'; +import { + SubspaceProductItemRepository, + SubspaceProductRepository, + SubspaceRepository, +} from '@app/common/modules/space/repositories/subspace.repository'; + +@CommandHandler(PropogateSubspaceCommand) +export class PropogateSubspaceHandler + implements ICommandHandler +{ + private readonly logger = new Logger(PropogateSubspaceHandler.name); + + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly subspaceRepository: SubspaceRepository, + private readonly productRepository: SubspaceProductRepository, + private readonly productItemRepository: SubspaceProductItemRepository, + ) {} + + async execute(command: PropogateSubspaceCommand): Promise { + try { + const newSubspaceModels = command.param?.new; + + if (!newSubspaceModels || newSubspaceModels.length === 0) { + this.logger.warn('No new subspace models provided.'); + return; + } + + const spaceModelUuid = + newSubspaceModels[0]?.subspaceModel?.spaceModel?.uuid; + + if (!spaceModelUuid) { + this.logger.error( + 'Space model UUID is missing in the command parameters.', + ); + return; + } + + const spaces = await this.getSpacesByModel(spaceModelUuid); + + if (spaces.length === 0) { + this.logger.warn(`No spaces found for model UUID: ${spaceModelUuid}`); + return; + } + + await this.processSubspaces(newSubspaceModels, spaces); + } catch (error) { + this.logger.error( + 'Error in PropogateSubspaceHandler execution', + error.stack, + ); + } + } + + private async processSubspaces( + newSubspaceModels: any[], + spaces: SpaceEntity[], + ) { + for (const newSubspaceModel of newSubspaceModels) { + for (const space of spaces) { + try { + const subspace = await this.createSubspace(newSubspaceModel, space); + + if (newSubspaceModel.productModels?.length > 0) { + await this.processProducts( + newSubspaceModel.productModels, + subspace, + ); + } + } catch (error) { + this.logger.error( + `Failed to create subspace for space ID: ${space.uuid}`, + error.stack, + ); + } + } + } + } + + private async createSubspace(newSubspaceModel: any, space: SpaceEntity) { + const subspace = this.subspaceRepository.create({ + subspaceName: newSubspaceModel.subspaceModel.subspaceName, + space, + subSpaceModel: newSubspaceModel.subspaceModel, + }); + + const createdSubspace = await this.subspaceRepository.save(subspace); + this.logger.log( + `Subspace created for space ${space.uuid} with name ${createdSubspace.subspaceName}`, + ); + return createdSubspace; + } + + private async processProducts(productModels: any[], subspace: any) { + for (const productModel of productModels) { + try { + const subspaceProduct = await this.createSubspaceProduct( + productModel, + subspace, + ); + + if (productModel.productItemModels?.length > 0) { + await this.processProductItems( + productModel.productItemModels, + subspaceProduct, + ); + } + } catch (error) { + this.logger.error( + `Failed to create product for subspace ID: ${subspace.id}`, + error.stack, + ); + } + } + } + + private async createSubspaceProduct(productModel: any, subspace: any) { + const subspaceProduct = this.productRepository.create({ + product: productModel.productModel.product, + subspace, + productCount: productModel.productModel.productCount, + model: productModel.productModel, + }); + + const createdSubspaceProduct = + await this.productRepository.save(subspaceProduct); + this.logger.log( + `Product added to subspace ${subspace.id} with count ${createdSubspaceProduct.productCount}`, + ); + return createdSubspaceProduct; + } + + private async processProductItems( + productItemModels: any[], + subspaceProduct: any, + ) { + for (const productItemModel of productItemModels) { + try { + const subspaceProductItem = this.productItemRepository.create({ + tag: productItemModel.tag, + subspaceProduct, + model: productItemModel, + }); + + await this.productItemRepository.save(subspaceProductItem); + this.logger.log( + `Product item added to subspace product ${subspaceProduct.id} with tag ${subspaceProductItem.tag}`, + ); + } catch (error) { + this.logger.error( + `Failed to create product item for subspace product ID: ${subspaceProduct.id}`, + error.stack, + ); + } + } + } + + private async getSpacesByModel(uuid: string): Promise { + try { + return await this.spaceRepository.find({ + where: { + spaceModel: { uuid }, + }, + }); + } catch (error) { + this.logger.error( + `Failed to fetch spaces for model UUID: ${uuid}`, + error.stack, + ); + throw error; + } + } +} diff --git a/src/space-model/interfaces/index.ts b/src/space-model/interfaces/index.ts new file mode 100644 index 0000000..606eb46 --- /dev/null +++ b/src/space-model/interfaces/index.ts @@ -0,0 +1 @@ +export * from './update-subspace.interface' \ No newline at end of file diff --git a/src/space-model/interfaces/update-subspace.interface.ts b/src/space-model/interfaces/update-subspace.interface.ts new file mode 100644 index 0000000..4ccc588 --- /dev/null +++ b/src/space-model/interfaces/update-subspace.interface.ts @@ -0,0 +1,19 @@ +import { + SubspaceModelEntity, + SubspaceProductItemModelEntity, + SubspaceProductModelEntity, +} from '@app/common/modules/space-model'; + +export interface AddSubspaceModelInterface { + subspaceModel: SubspaceModelEntity; + productModels: ProductModelInterface[]; +} + +export interface ProductModelInterface { + productModel: SubspaceProductModelEntity; + productItemModels: SubspaceProductItemModelEntity[]; +} + +export interface UpdatedSubspaceModelInterface { + new?: AddSubspaceModelInterface[]; +} diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 805ffa0..6b0c5db 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -18,6 +18,9 @@ import { SpaceModelDto } from '@app/common/modules/space-model/dtos'; import { SpaceModelParam } from '../dtos/space-model-param'; import { ProjectService } from 'src/project/services'; import { ProjectEntity } from '@app/common/modules/project/entities'; +import { UpdatedSubspaceModelInterface } from '../interfaces'; +import { CommandBus } from '@nestjs/cqrs'; +import { PropogateSubspaceCommand } from '../commands'; @Injectable() export class SpaceModelService { @@ -27,6 +30,7 @@ export class SpaceModelService { private readonly projectService: ProjectService, private readonly subSpaceModelService: SubSpaceModelService, private readonly spaceProductModelService: SpaceProductModelService, + private commandBus: CommandBus, ) {} async createSpaceModel( @@ -142,21 +146,27 @@ export class SpaceModelService { await queryRunner.startTransaction(); try { const { modelName } = dto; + let updatedSubspaces: UpdatedSubspaceModelInterface; if (modelName) spaceModel.modelName = modelName; await queryRunner.manager.save(spaceModel); if (dto.subspaceModels) { - const updatedSubspaces = - await this.subSpaceModelService.updateSubSpaceModels( - dto.subspaceModels, - spaceModel, - queryRunner, - ); + updatedSubspaces = await this.subSpaceModelService.modifySubSpaceModels( + dto.subspaceModels, + spaceModel, + queryRunner, + ); } await queryRunner.commitTransaction(); + if (updatedSubspaces) { + await this.commandBus.execute( + new PropogateSubspaceCommand(updatedSubspaces), + ); + } + return new SuccessResponseDto({ message: 'SpaceModel updated successfully', data: spaceModel, diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index 6814362..d49ff83 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -3,9 +3,10 @@ import { SubspaceModelRepository, } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { CreateSubspaceModelDto, UpdateSubspaceModelDto } from '../../dtos'; +import { CreateSubspaceModelDto, UpdateSubspacesModelDto } from '../../dtos'; import { QueryRunner } from 'typeorm'; import { SubspaceProductModelService } from './subspace-product-model.service'; +import { UpdatedSubspaceModelInterface } from 'src/space-model/interfaces'; @Injectable() export class SubSpaceModelService { @@ -91,12 +92,12 @@ export class SubSpaceModelService { } } - async updateSubSpaceModels( - dto: UpdateSubspaceModelDto, + async modifySubSpaceModels( + dto: UpdateSubspacesModelDto, spaceModel: SpaceModelEntity, queryRunner: QueryRunner, ) { - const subspaces: { new?: any } = {}; + const subspaces: UpdatedSubspaceModelInterface = {}; try { if (dto.add) { const addedSubspaces = await this.createSubSpaceModels( @@ -105,6 +106,8 @@ export class SubSpaceModelService { queryRunner, ); subspaces.new = addedSubspaces; + } else if (dto.update) { + } return subspaces; } catch (error) { diff --git a/src/space-model/space-model.module.ts b/src/space-model/space-model.module.ts index ae275e2..e1e4b56 100644 --- a/src/space-model/space-model.module.ts +++ b/src/space-model/space-model.module.ts @@ -20,13 +20,25 @@ import { import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProductRepository } from '@app/common/modules/product/repositories'; import { SubspaceProductModelService } from './services/subspace/subspace-product-model.service'; +import { PropogateSubspaceHandler } from './handlers'; +import { CqrsModule } from '@nestjs/cqrs'; +import { SpaceRepository } from '@app/common/modules/space'; +import { + SubspaceProductItemRepository, + SubspaceProductRepository, + SubspaceRepository, +} from '@app/common/modules/space/repositories/subspace.repository'; + +const CommandHandlers = [PropogateSubspaceHandler]; @Module({ - imports: [ConfigModule, SpaceRepositoryModule], + imports: [ConfigModule, SpaceRepositoryModule, CqrsModule], controllers: [SpaceModelController], providers: [ + ...CommandHandlers, SpaceModelService, SpaceModelRepository, + SpaceRepository, ProjectRepository, SubSpaceModelService, SpaceProductModelService, @@ -39,7 +51,10 @@ import { SubspaceProductModelService } from './services/subspace/subspace-produc SubspaceProductItemModelRepository, SubspaceProductModelService, SubspaceProductModelRepository, + SubspaceRepository, + SubspaceProductRepository, + SubspaceProductItemRepository, ], - exports: [], + exports: [CqrsModule], }) export class SpaceModelModule {} From 35888a1fcea63eadbea80e2ed144eeb9fd1e7295 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 19 Dec 2024 10:22:33 +0400 Subject: [PATCH 084/247] added disabled to subspaces --- .../entities/subspace-model/subspace-model.entity.ts | 6 ++++++ .../subspace-model/subspace-product-item-model.entity.ts | 6 ++++++ .../subspace-model/subspace-product-model.entity.ts | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts index 89fcf63..f166c73 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts @@ -35,6 +35,12 @@ export class SubspaceModelEntity extends AbstractEntity { }) public spaces: SubspaceEntity[]; + @Column({ + nullable: false, + default: false, + }) + public disabled: boolean; + @OneToMany( () => SubspaceProductModelEntity, (productModel) => productModel.subspaceModel, diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts index 2b6d305..59d6db6 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts @@ -11,6 +11,12 @@ export class SubspaceProductItemModelEntity extends AbstractEntity SubspaceProductModelEntity, (productModel) => productModel.itemModels, diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts index 843c70d..960c6fe 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts @@ -14,6 +14,12 @@ export class SubspaceProductModelEntity extends AbstractEntity SubspaceModelEntity, (spaceModel) => spaceModel.productModels, From 9063c65e688e5b266ac372e0c209ef4b5fdb778f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 19 Dec 2024 10:23:08 +0400 Subject: [PATCH 085/247] added subspace delete --- .../propogate-subspace-update-command.ts | 4 +- .../delete-subspace-model.dto.ts | 12 +++ .../dtos/subspaces-model-dtos/index.ts | 1 + .../dtos/update-space-model.dto.ts | 12 +++ .../dtos/update-subspace-model.dto.ts | 4 + .../interfaces/update-subspace.interface.ts | 13 ++- .../services/space-model.service.ts | 4 +- .../subspace/subspace-model.service.ts | 94 ++++++++++++++++++- 8 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 src/space-model/dtos/subspaces-model-dtos/delete-subspace-model.dto.ts create mode 100644 src/space-model/dtos/subspaces-model-dtos/index.ts diff --git a/src/space-model/commands/propogate-subspace-update-command.ts b/src/space-model/commands/propogate-subspace-update-command.ts index 286b9bd..96276ee 100644 --- a/src/space-model/commands/propogate-subspace-update-command.ts +++ b/src/space-model/commands/propogate-subspace-update-command.ts @@ -1,6 +1,6 @@ import { ICommand } from '@nestjs/cqrs'; -import { UpdatedSubspaceModelInterface } from '../interfaces'; +import { IModifySubspaceModelInterface } from '../interfaces'; export class PropogateSubspaceCommand implements ICommand { - constructor(public readonly param: UpdatedSubspaceModelInterface) {} + constructor(public readonly param: IModifySubspaceModelInterface) {} } diff --git a/src/space-model/dtos/subspaces-model-dtos/delete-subspace-model.dto.ts b/src/space-model/dtos/subspaces-model-dtos/delete-subspace-model.dto.ts new file mode 100644 index 0000000..62fe84e --- /dev/null +++ b/src/space-model/dtos/subspaces-model-dtos/delete-subspace-model.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class DeleteSubspaceModelDto { + @ApiProperty({ + description: 'Uuid of the subspace model need to be deleted', + example: '982fc3a3-64dc-4afb-a5b5-65ee8fef0424', + }) + @IsNotEmpty() + @IsString() + subspaceUuid: string; +} diff --git a/src/space-model/dtos/subspaces-model-dtos/index.ts b/src/space-model/dtos/subspaces-model-dtos/index.ts new file mode 100644 index 0000000..e70ef7b --- /dev/null +++ b/src/space-model/dtos/subspaces-model-dtos/index.ts @@ -0,0 +1 @@ +export * from './delete-subspace-model.dto'; diff --git a/src/space-model/dtos/update-space-model.dto.ts b/src/space-model/dtos/update-space-model.dto.ts index daaa62b..62a6bc1 100644 --- a/src/space-model/dtos/update-space-model.dto.ts +++ b/src/space-model/dtos/update-space-model.dto.ts @@ -3,6 +3,7 @@ import { IsArray, IsOptional, IsString, ValidateNested } from 'class-validator'; import { CreateSubspaceModelDto } from './create-subspace-model.dto'; import { Type } from 'class-transformer'; import { UpdateSubspaceModelDto } from './update-subspace-model.dto'; +import { DeleteSubspaceModelDto } from './subspaces-model-dtos'; export class UpdateSubspacesModelDto { @ApiProperty({ @@ -26,6 +27,17 @@ export class UpdateSubspacesModelDto { @ValidateNested({ each: true }) @Type(() => UpdateSubspaceModelDto) update?: UpdateSubspaceModelDto[]; + + @ApiProperty({ + description: 'List of subspaces to delete', + type: [DeleteSubspaceModelDto], + required: false, + }) + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => DeleteSubspaceModelDto) + delete?: DeleteSubspaceModelDto[]; } export class UpdateSpaceModelDto { diff --git a/src/space-model/dtos/update-subspace-model.dto.ts b/src/space-model/dtos/update-subspace-model.dto.ts index da71afc..ee25bd2 100644 --- a/src/space-model/dtos/update-subspace-model.dto.ts +++ b/src/space-model/dtos/update-subspace-model.dto.ts @@ -18,6 +18,10 @@ export class UpdateSubspaceModelDto { @IsString() subspaceName?: string; + @IsNotEmpty() + @IsString() + subspaceUuid: string; + @ApiProperty({ description: 'List of products included in the model', type: [CreateSpaceProductModelDto], diff --git a/src/space-model/interfaces/update-subspace.interface.ts b/src/space-model/interfaces/update-subspace.interface.ts index 4ccc588..4d58469 100644 --- a/src/space-model/interfaces/update-subspace.interface.ts +++ b/src/space-model/interfaces/update-subspace.interface.ts @@ -14,6 +14,17 @@ export interface ProductModelInterface { productItemModels: SubspaceProductItemModelEntity[]; } -export interface UpdatedSubspaceModelInterface { +export interface IModifySubspaceModelInterface { new?: AddSubspaceModelInterface[]; + update?: IUpdateSubspaceModelInterface[]; + delete?: IDeletedSubsaceModelInterface[]; +} + +export interface IUpdateSubspaceModelInterface { + subspaceName?: string; + uuid: string; +} + +export interface IDeletedSubsaceModelInterface { + uuid: string; } diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 6b0c5db..fff944b 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -18,7 +18,7 @@ import { SpaceModelDto } from '@app/common/modules/space-model/dtos'; import { SpaceModelParam } from '../dtos/space-model-param'; import { ProjectService } from 'src/project/services'; import { ProjectEntity } from '@app/common/modules/project/entities'; -import { UpdatedSubspaceModelInterface } from '../interfaces'; +import { IModifySubspaceModelInterface } from '../interfaces'; import { CommandBus } from '@nestjs/cqrs'; import { PropogateSubspaceCommand } from '../commands'; @@ -146,7 +146,7 @@ export class SpaceModelService { await queryRunner.startTransaction(); try { const { modelName } = dto; - let updatedSubspaces: UpdatedSubspaceModelInterface; + let updatedSubspaces: IModifySubspaceModelInterface; if (modelName) spaceModel.modelName = modelName; await queryRunner.manager.save(spaceModel); diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index d49ff83..f69d859 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -3,10 +3,19 @@ import { SubspaceModelRepository, } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { CreateSubspaceModelDto, UpdateSubspacesModelDto } from '../../dtos'; +import { + CreateSubspaceModelDto, + UpdateSubspaceModelDto, + UpdateSubspacesModelDto, +} from '../../dtos'; import { QueryRunner } from 'typeorm'; import { SubspaceProductModelService } from './subspace-product-model.service'; -import { UpdatedSubspaceModelInterface } from 'src/space-model/interfaces'; +import { + IDeletedSubsaceModelInterface, + IModifySubspaceModelInterface, + IUpdateSubspaceModelInterface, +} from 'src/space-model/interfaces'; +import { DeleteSubspaceModelDto } from 'src/space-model/dtos/subspaces-model-dtos'; @Injectable() export class SubSpaceModelService { @@ -61,6 +70,79 @@ export class SubSpaceModelService { } } + async updateSubspaceModels( + updateDtos: UpdateSubspaceModelDto[], + queryRunner: QueryRunner, + ) { + const updateResults: IUpdateSubspaceModelInterface[] = []; + + for (const dto of updateDtos) { + try { + const subspaceModel = await this.findOne(dto.subspaceUuid); + const updateResult: IUpdateSubspaceModelInterface = { + uuid: dto.subspaceUuid, + }; + + if (dto.subspaceName) { + subspaceModel.subspaceName = dto.subspaceName; + await queryRunner.manager.update( + this.subspaceModelRepository.target, + { uuid: dto.subspaceUuid }, + { subspaceName: dto.subspaceName }, + ); + updateResult.subspaceName = dto.subspaceName; + } + + updateResults.push(updateResult); + } catch (error) { + console.error( + `SubspaceModel with ${dto.subspaceUuid} not able to update ${error}`, + ); + } + } + + return updateResults; + } + + async deleteSubspaceModels( + dtos: DeleteSubspaceModelDto[], + queryRunner: QueryRunner, + ) { + const deleteResults: IDeletedSubsaceModelInterface[] = []; + try { + for (const dto of dtos) { + await this.findOne(dto.subspaceUuid); + + await queryRunner.manager.update( + this.subspaceModelRepository.target, + { uuid: dto.subspaceUuid }, + { disabled: true }, + ); + + deleteResults.push({ uuid: dto.subspaceUuid }); + } + return deleteResults; + } catch (error) { + console.error(`Bulk delete operation failed: ${error.message}`); + throw new Error('Bulk delete operation failed.'); + } + } + + async findOne(subspaceUuid: string) { + const subspace = await this.subspaceModelRepository.findOne({ + where: { + uuid: subspaceUuid, + }, + }); + if (!subspace) { + throw new HttpException( + `SubspaceModel with ${subspaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + return subspace; + } + private validateInputDtos(subSpaceModelDtos: CreateSubspaceModelDto[]) { if (subSpaceModelDtos.length === 0) { throw new HttpException( @@ -97,7 +179,7 @@ export class SubSpaceModelService { spaceModel: SpaceModelEntity, queryRunner: QueryRunner, ) { - const subspaces: UpdatedSubspaceModelInterface = {}; + const subspaces: IModifySubspaceModelInterface = {}; try { if (dto.add) { const addedSubspaces = await this.createSubSpaceModels( @@ -107,7 +189,11 @@ export class SubSpaceModelService { ); subspaces.new = addedSubspaces; } else if (dto.update) { - + const updatedSubspaces = await this.updateSubspaceModels( + dto.update, + queryRunner, + ); + subspaces.update = updatedSubspaces; } return subspaces; } catch (error) { From 23be781d2eff8c33d2b7b0a68ef5a713015aea96 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 19 Dec 2024 10:25:25 +0400 Subject: [PATCH 086/247] added disabled to all space entities --- .../space-model/entities/space-model.entity.ts | 6 ++++++ .../entities/space-product-item-model.entity.ts | 6 ++++++ .../entities/space-product-model.entity.ts | 6 ++++++ .../src/modules/space/entities/space-link.entity.ts | 6 ++++++ .../space/entities/space-product-item.entity.ts | 6 ++++++ .../modules/space/entities/space-product.entity.ts | 6 ++++++ .../common/src/modules/space/entities/space.entity.ts | 6 ++++++ .../entities/subspace/subspace-product-item.entity.ts | 6 ++++++ .../entities/subspace/subspace-product.entity.ts | 11 +++++++---- .../space/entities/subspace/subspace.entity.ts | 7 ++++++- 10 files changed, 61 insertions(+), 5 deletions(-) diff --git a/libs/common/src/modules/space-model/entities/space-model.entity.ts b/libs/common/src/modules/space-model/entities/space-model.entity.ts index 591d04f..4c90bcc 100644 --- a/libs/common/src/modules/space-model/entities/space-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-model.entity.ts @@ -28,6 +28,12 @@ export class SpaceModelEntity extends AbstractEntity { }) public modelName: string; + @Column({ + nullable: false, + default: false, + }) + public disabled: boolean; + @ManyToOne(() => ProjectEntity, (project) => project.spaceModels, { nullable: false, onDelete: 'CASCADE', diff --git a/libs/common/src/modules/space-model/entities/space-product-item-model.entity.ts b/libs/common/src/modules/space-model/entities/space-product-item-model.entity.ts index 062418f..1d7bd09 100644 --- a/libs/common/src/modules/space-model/entities/space-product-item-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-product-item-model.entity.ts @@ -20,6 +20,12 @@ export class SpaceProductItemModelEntity extends AbstractEntity SpaceProductItemEntity, (spaceProductItem) => spaceProductItem.spaceProductItemModel, diff --git a/libs/common/src/modules/space-model/entities/space-product-model.entity.ts b/libs/common/src/modules/space-model/entities/space-product-model.entity.ts index 13b2b33..9fdcd83 100644 --- a/libs/common/src/modules/space-model/entities/space-product-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-product-model.entity.ts @@ -30,6 +30,12 @@ export class SpaceProductModelEntity extends AbstractEntity SpaceProductItemModelEntity, (item) => item.spaceProductModel, diff --git a/libs/common/src/modules/space/entities/space-link.entity.ts b/libs/common/src/modules/space/entities/space-link.entity.ts index a62ce4f..da11eb7 100644 --- a/libs/common/src/modules/space/entities/space-link.entity.ts +++ b/libs/common/src/modules/space/entities/space-link.entity.ts @@ -13,6 +13,12 @@ export class SpaceLinkEntity extends AbstractEntity { @JoinColumn({ name: 'end_space_id' }) public endSpace: SpaceEntity; + @Column({ + nullable: false, + default: false, + }) + public disabled: boolean; + @Column({ nullable: false, enum: Object.values(Direction), diff --git a/libs/common/src/modules/space/entities/space-product-item.entity.ts b/libs/common/src/modules/space/entities/space-product-item.entity.ts index c53dfd4..ebbe572 100644 --- a/libs/common/src/modules/space/entities/space-product-item.entity.ts +++ b/libs/common/src/modules/space/entities/space-product-item.entity.ts @@ -16,6 +16,12 @@ export class SpaceProductItemEntity extends AbstractEntity }) public spaceProduct: SpaceProductEntity; + @Column({ + nullable: false, + default: false, + }) + public disabled: boolean; + @ManyToOne( () => SpaceProductItemModelEntity, (spaceProductItemModel) => spaceProductItemModel.items, diff --git a/libs/common/src/modules/space/entities/space-product.entity.ts b/libs/common/src/modules/space/entities/space-product.entity.ts index 5f8a062..c268302 100644 --- a/libs/common/src/modules/space/entities/space-product.entity.ts +++ b/libs/common/src/modules/space/entities/space-product.entity.ts @@ -27,6 +27,12 @@ export class SpaceProductEntity extends AbstractEntity { }) productCount: number; + @Column({ + nullable: false, + default: false, + }) + public disabled: boolean; + @OneToMany(() => SpaceProductItemEntity, (item) => item.spaceProduct, { cascade: true, }) diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 63beed5..d6133da 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -60,6 +60,12 @@ export class SpaceEntity extends AbstractEntity { @OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.space) userSpaces: UserSpaceEntity[]; + @Column({ + nullable: false, + default: false, + }) + public disabled: boolean; + @OneToMany(() => SubspaceEntity, (subspace) => subspace.space, { nullable: true, }) diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts index 1ec7958..51e206f 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts @@ -20,6 +20,12 @@ export class SubspaceProductItemEntity extends AbstractEntity SubspaceProductItemModelEntity, (model) => model.items, { nullable: true, }) diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts index 87ed807..501f539 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts @@ -3,10 +3,7 @@ import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'; import { SubspaceEntity } from './subspace.entity'; import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; import { SubspaceProductItemEntity } from './subspace-product-item.entity'; -import { - SubspaceProductItemModelEntity, - SubspaceProductModelEntity, -} from '@app/common/modules/space-model'; +import { SubspaceProductModelEntity } from '@app/common/modules/space-model'; import { SpaceProductModelDto } from '../../dtos'; @Entity({ name: 'subspace-product' }) @@ -24,6 +21,12 @@ export class SubspaceProductEntity extends AbstractEntity }) productCount: number; + @Column({ + nullable: false, + default: false, + }) + public disabled: boolean; + @ManyToOne(() => SubspaceEntity, (subspace) => subspace.subspaceProducts, { nullable: false, }) diff --git a/libs/common/src/modules/space/entities/subspace/subspace.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace.entity.ts index bc6ff06..6ad7751 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace.entity.ts @@ -22,11 +22,16 @@ export class SubspaceEntity extends AbstractEntity { @ManyToOne(() => SpaceEntity, (space) => space.subspaces, { nullable: false, - onDelete: 'CASCADE', }) @JoinColumn({ name: 'space_uuid' }) space: SpaceEntity; + @Column({ + nullable: false, + default: false, + }) + public disabled: boolean; + @OneToMany(() => DeviceEntity, (device) => device.subspace, { nullable: true, }) From aefe788dd02beaf375102436b5b66df7aa896821 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 19 Dec 2024 10:29:21 +0400 Subject: [PATCH 087/247] added device-to-tag --- .../src/modules/device/entities/device.entity.ts | 12 +++++++++++- .../space/entities/space-product-item.entity.ts | 8 +++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index 9a75950..ade7d99 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -6,10 +6,15 @@ import { Unique, Index, JoinColumn, + OneToOne, } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceDto, DeviceUserPermissionDto } from '../dtos/device.dto'; -import { SpaceEntity, SubspaceEntity } from '../../space/entities'; +import { + SpaceEntity, + SpaceProductItemEntity, + SubspaceEntity, +} from '../../space/entities'; import { ProductEntity } from '../../product/entities'; import { UserEntity } from '../../user/entities'; import { DeviceNotificationDto } from '../dtos'; @@ -74,6 +79,11 @@ export class DeviceEntity extends AbstractEntity { @OneToMany(() => SceneDeviceEntity, (sceneDevice) => sceneDevice.device, {}) sceneDevices: SceneDeviceEntity[]; + @OneToOne(() => SpaceProductItemEntity, (tag) => tag.device, { + nullable: true, + }) + public tag?: SpaceProductItemEntity; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/space/entities/space-product-item.entity.ts b/libs/common/src/modules/space/entities/space-product-item.entity.ts index ebbe572..d9add42 100644 --- a/libs/common/src/modules/space/entities/space-product-item.entity.ts +++ b/libs/common/src/modules/space/entities/space-product-item.entity.ts @@ -1,8 +1,9 @@ -import { Column, Entity, ManyToOne } from 'typeorm'; +import { Column, Entity, ManyToOne, OneToOne } from 'typeorm'; import { SpaceProductEntity } from './space-product.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceProductItemDto } from '../dtos'; import { SpaceProductItemModelEntity } from '../../space-model'; +import { DeviceEntity } from '../../device/entities'; @Entity({ name: 'space-product-item' }) export class SpaceProductItemEntity extends AbstractEntity { @@ -30,4 +31,9 @@ export class SpaceProductItemEntity extends AbstractEntity }, ) public spaceProductItemModel?: SpaceProductItemModelEntity; + + @OneToOne(() => DeviceEntity, (device) => device.tag, { + nullable: true, + }) + public device?: DeviceEntity; } From 5e089f76ac0ecfe9b2735da9f5a7b50dcf657a2a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 19 Dec 2024 11:21:33 +0400 Subject: [PATCH 088/247] for now --- .../handlers/propate-subspace-handler.ts | 52 ++++++++++++++++--- .../interfaces/update-subspace.interface.ts | 1 + .../subspace/subspace-model.service.ts | 36 +++++++++---- 3 files changed, 72 insertions(+), 17 deletions(-) diff --git a/src/space-model/handlers/propate-subspace-handler.ts b/src/space-model/handlers/propate-subspace-handler.ts index e7a4db7..158bad3 100644 --- a/src/space-model/handlers/propate-subspace-handler.ts +++ b/src/space-model/handlers/propate-subspace-handler.ts @@ -7,6 +7,7 @@ import { SubspaceProductRepository, SubspaceRepository, } from '@app/common/modules/space/repositories/subspace.repository'; +import { IUpdateSubspaceModelInterface } from '../interfaces'; @CommandHandler(PropogateSubspaceCommand) export class PropogateSubspaceHandler @@ -24,14 +25,14 @@ export class PropogateSubspaceHandler async execute(command: PropogateSubspaceCommand): Promise { try { const newSubspaceModels = command.param?.new; + const updateSubspaceModels = command.param?.update; - if (!newSubspaceModels || newSubspaceModels.length === 0) { - this.logger.warn('No new subspace models provided.'); + if (!newSubspaceModels && !updateSubspaceModels) { + this.logger.warn('No new or updated subspace models provided.'); return; } - const spaceModelUuid = - newSubspaceModels[0]?.subspaceModel?.spaceModel?.uuid; + const spaceModelUuid = command.param.spaceModelUuid; if (!spaceModelUuid) { this.logger.error( @@ -47,7 +48,13 @@ export class PropogateSubspaceHandler return; } - await this.processSubspaces(newSubspaceModels, spaces); + if (newSubspaceModels && newSubspaceModels.length > 0) { + await this.processNewSubspaces(newSubspaceModels, spaces); + } + + if (updateSubspaceModels && updateSubspaceModels.length > 0) { + await this.updateSubspaces(updateSubspaceModels); + } } catch (error) { this.logger.error( 'Error in PropogateSubspaceHandler execution', @@ -56,7 +63,40 @@ export class PropogateSubspaceHandler } } - private async processSubspaces( + private async updateSubspaces(models: IUpdateSubspaceModelInterface[]) { + try { + for (const model of models) { + try { + const subspaceModelUuid = model.uuid; + if (model.subspaceName) { + const subspaces = await this.subspaceRepository.find({ + where: { + subSpaceModel: { + uuid: subspaceModelUuid, + }, + }, + }); + + if (subspaces.length > 0) { + await this.subspaceRepository.update(subspaces, { + subspaceName: model.subspaceName, + }); + } + } + } catch (innerError) { + this.logger.error( + `Error updating subspace model with UUID: ${model.uuid}`, + innerError.stack, + ); + } + } + } catch (error) { + this.logger.error('Error in updateSubspaces method', error.stack); + throw new Error('Failed to update subspaces.'); + } + } + + private async processNewSubspaces( newSubspaceModels: any[], spaces: SpaceEntity[], ) { diff --git a/src/space-model/interfaces/update-subspace.interface.ts b/src/space-model/interfaces/update-subspace.interface.ts index 4d58469..a49491f 100644 --- a/src/space-model/interfaces/update-subspace.interface.ts +++ b/src/space-model/interfaces/update-subspace.interface.ts @@ -15,6 +15,7 @@ export interface ProductModelInterface { } export interface IModifySubspaceModelInterface { + spaceModelUuid: string; new?: AddSubspaceModelInterface[]; update?: IUpdateSubspaceModelInterface[]; delete?: IDeletedSubsaceModelInterface[]; diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index f69d859..bcf5433 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -75,9 +75,8 @@ export class SubSpaceModelService { queryRunner: QueryRunner, ) { const updateResults: IUpdateSubspaceModelInterface[] = []; - - for (const dto of updateDtos) { - try { + try { + for (const dto of updateDtos) { const subspaceModel = await this.findOne(dto.subspaceUuid); const updateResult: IUpdateSubspaceModelInterface = { uuid: dto.subspaceUuid, @@ -94,14 +93,18 @@ export class SubSpaceModelService { } updateResults.push(updateResult); - } catch (error) { - console.error( - `SubspaceModel with ${dto.subspaceUuid} not able to update ${error}`, - ); } - } - return updateResults; + return updateResults; + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( + error.message || 'Failed to update SpaceModels', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } } async deleteSubspaceModels( @@ -179,7 +182,9 @@ export class SubSpaceModelService { spaceModel: SpaceModelEntity, queryRunner: QueryRunner, ) { - const subspaces: IModifySubspaceModelInterface = {}; + const subspaces: IModifySubspaceModelInterface = { + spaceModelUuid: spaceModel.uuid, + }; try { if (dto.add) { const addedSubspaces = await this.createSubSpaceModels( @@ -194,11 +199,20 @@ export class SubSpaceModelService { queryRunner, ); subspaces.update = updatedSubspaces; + } else if (dto.delete) { + const deletedSubspaces = await this.deleteSubspaceModels( + dto.delete, + queryRunner, + ); + subspaces.delete = deletedSubspaces; } return subspaces; } catch (error) { + if (error instanceof HttpException) { + throw error; + } throw new HttpException( - error.message || 'Failed to update SpaceModel', + error.message || 'Failed to modify SpaceModels', HttpStatus.INTERNAL_SERVER_ERROR, ); } From f4741caa06471caa7f4e3348fd527e33eb5a070d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 19 Dec 2024 11:52:56 +0400 Subject: [PATCH 089/247] revert back change --- libs/common/src/modules/user/entities/user.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 04697fa..097b154 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -117,7 +117,7 @@ export class UserEntity extends AbstractEntity { public visitorPasswords: VisitorPasswordEntity[]; @ManyToOne(() => RoleTypeEntity, (roleType) => roleType.users, { - nullable: true, + nullable: false, }) public roleType: RoleTypeEntity; @OneToOne(() => InviteUserEntity, (inviteUser) => inviteUser.user, { From 66364811f6c18ec82672cebd33c4dbf6448554bf Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 19 Dec 2024 11:57:06 +0400 Subject: [PATCH 090/247] added project name to community name --- .../space/entities/subspace/subspace-product.entity.ts | 5 +---- src/project/handler/create-orphan-space.handler.service.ts | 6 ++++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts index 87ed807..ea98520 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts @@ -3,10 +3,7 @@ import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'; import { SubspaceEntity } from './subspace.entity'; import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; import { SubspaceProductItemEntity } from './subspace-product-item.entity'; -import { - SubspaceProductItemModelEntity, - SubspaceProductModelEntity, -} from '@app/common/modules/space-model'; +import { SubspaceProductModelEntity } from '@app/common/modules/space-model'; import { SpaceProductModelDto } from '../../dtos'; @Entity({ name: 'subspace-product' }) diff --git a/src/project/handler/create-orphan-space.handler.service.ts b/src/project/handler/create-orphan-space.handler.service.ts index 8935afe..662b93e 100644 --- a/src/project/handler/create-orphan-space.handler.service.ts +++ b/src/project/handler/create-orphan-space.handler.service.ts @@ -23,13 +23,15 @@ export class CreateOrphanSpaceHandler async execute(command: CreateOrphanSpaceCommand): Promise { try { const { project } = command; + const orphanCommunityName = `${ORPHAN_COMMUNITY_NAME}-${project.name}`; + let orphanCommunity = await this.communityRepository.findOne({ - where: { name: ORPHAN_COMMUNITY_NAME, project }, + where: { name: orphanCommunityName, project }, }); if (!orphanCommunity) { orphanCommunity = this.communityRepository.create({ - name: ORPHAN_COMMUNITY_NAME, + name: orphanCommunityName, description: ORPHAN_COMMUNITY_DESCRIPTION, project, }); From 84ed3b03c17f81f4fd9c0a4e627494b02b9fe8e7 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 19 Dec 2024 12:24:13 +0400 Subject: [PATCH 091/247] bugfix check array empty --- src/space/services/space-device.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index b856be0..75746ca 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -26,9 +26,9 @@ export class SpaceDeviceService { spaceUuid, ); - if (!Array.isArray(space.devices)) { + if (space.devices.length === 0) { throw new HttpException( - 'The space does not contain any devices or the devices property is invalid.', + 'The space does not contain any devices.', HttpStatus.BAD_REQUEST, ); } From 7f19073ed6493f95592a9f7d7c7aa69bb7594e49 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 19 Dec 2024 15:48:10 +0400 Subject: [PATCH 092/247] propagate delete subspace model --- .../handlers/propate-subspace-handler.ts | 75 +++++++++++++------ .../services/space-product-model.service.ts | 2 +- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/src/space-model/handlers/propate-subspace-handler.ts b/src/space-model/handlers/propate-subspace-handler.ts index 158bad3..82431bd 100644 --- a/src/space-model/handlers/propate-subspace-handler.ts +++ b/src/space-model/handlers/propate-subspace-handler.ts @@ -7,7 +7,10 @@ import { SubspaceProductRepository, SubspaceRepository, } from '@app/common/modules/space/repositories/subspace.repository'; -import { IUpdateSubspaceModelInterface } from '../interfaces'; +import { + IDeletedSubsaceModelInterface, + IUpdateSubspaceModelInterface, +} from '../interfaces'; @CommandHandler(PropogateSubspaceCommand) export class PropogateSubspaceHandler @@ -26,6 +29,7 @@ export class PropogateSubspaceHandler try { const newSubspaceModels = command.param?.new; const updateSubspaceModels = command.param?.update; + const deleteSubspaceModels = command.param?.delete; if (!newSubspaceModels && !updateSubspaceModels) { this.logger.warn('No new or updated subspace models provided.'); @@ -55,6 +59,10 @@ export class PropogateSubspaceHandler if (updateSubspaceModels && updateSubspaceModels.length > 0) { await this.updateSubspaces(updateSubspaceModels); } + + if (deleteSubspaceModels && deleteSubspaceModels.length > 0) { + await this.deleteSubspaces(deleteSubspaceModels); + } } catch (error) { this.logger.error( 'Error in PropogateSubspaceHandler execution', @@ -63,36 +71,55 @@ export class PropogateSubspaceHandler } } - private async updateSubspaces(models: IUpdateSubspaceModelInterface[]) { + private async updateSubspaces( + models: IUpdateSubspaceModelInterface[], + ): Promise { try { - for (const model of models) { - try { - const subspaceModelUuid = model.uuid; - if (model.subspaceName) { - const subspaces = await this.subspaceRepository.find({ - where: { - subSpaceModel: { - uuid: subspaceModelUuid, - }, - }, - }); + const updatePromises = []; - if (subspaces.length > 0) { - await this.subspaceRepository.update(subspaces, { - subspaceName: model.subspaceName, - }); - } - } - } catch (innerError) { - this.logger.error( - `Error updating subspace model with UUID: ${model.uuid}`, - innerError.stack, + for (const model of models) { + const { uuid: subspaceModelUuid, subspaceName } = model; + + if (subspaceName) { + updatePromises.push( + this.subspaceRepository + .createQueryBuilder() + .update() + .set({ subspaceName }) + .where('subSpaceModelUuid = :uuid', { uuid: subspaceModelUuid }) + .execute(), ); } } + + await Promise.all(updatePromises); } catch (error) { this.logger.error('Error in updateSubspaces method', error.stack); - throw new Error('Failed to update subspaces.'); + } + } + + private async deleteSubspaces(models: IDeletedSubsaceModelInterface[]) { + try { + const updatePromises = []; + + for (const model of models) { + const { uuid: subspaceModelUuid } = model; + + if (subspaceModelUuid) { + updatePromises.push( + this.subspaceRepository + .createQueryBuilder() + .update() + .set({ disabled: true }) + .where('subSpaceModelUuid = :uuid', { uuid: subspaceModelUuid }) + .execute(), + ); + } + } + + await Promise.all(updatePromises); + } catch (error) { + this.logger.error('Error in delete subspace models', error.stack); } } diff --git a/src/space-model/services/space-product-model.service.ts b/src/space-model/services/space-product-model.service.ts index 0f1e93e..36695be 100644 --- a/src/space-model/services/space-product-model.service.ts +++ b/src/space-model/services/space-product-model.service.ts @@ -47,7 +47,7 @@ export class SpaceProductModelService extends BaseProductModelService { const savedModel = savedProductModels[index]; return this.spaceProductItemModelService.createProdutItemModel( dto.items, - savedModel, // Pass the saved model + savedModel, spaceModel, queryRunner, ); From 7a82d88aa54a201cace2d01be0a7756f482cb728 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 19 Dec 2024 16:18:55 +0400 Subject: [PATCH 093/247] changed name of dtos --- .../dtos/create-space-model.dto.ts | 2 +- .../dtos/create-space-product-model.dto.ts | 28 +++++++++++++++++++ src/space-model/dtos/index.ts | 3 +- .../create-subspace-model.dto.ts | 2 +- .../dtos/subspaces-model-dtos/index.ts | 2 ++ .../update-subspace-model.dto.ts | 2 +- .../dtos/update-space-model.dto.ts | 12 ++++---- .../subspace/subspace-model.service.ts | 4 +-- 8 files changed, 43 insertions(+), 12 deletions(-) rename src/space-model/dtos/{ => subspaces-model-dtos}/create-subspace-model.dto.ts (89%) rename src/space-model/dtos/{ => subspaces-model-dtos}/update-subspace-model.dto.ts (89%) diff --git a/src/space-model/dtos/create-space-model.dto.ts b/src/space-model/dtos/create-space-model.dto.ts index 9506728..2988e53 100644 --- a/src/space-model/dtos/create-space-model.dto.ts +++ b/src/space-model/dtos/create-space-model.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString, IsArray, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; -import { CreateSubspaceModelDto } from './create-subspace-model.dto'; +import { CreateSubspaceModelDto } from './subspaces-model-dtos/create-subspace-model.dto'; import { CreateSpaceProductModelDto } from './create-space-product-model.dto'; export class CreateSpaceModelDto { diff --git a/src/space-model/dtos/create-space-product-model.dto.ts b/src/space-model/dtos/create-space-product-model.dto.ts index a4379a1..2dab9aa 100644 --- a/src/space-model/dtos/create-space-product-model.dto.ts +++ b/src/space-model/dtos/create-space-product-model.dto.ts @@ -37,3 +37,31 @@ export class CreateSpaceProductModelDto { @Type(() => CreateProductItemModelDto) items: CreateProductItemModelDto[]; } + +export class UpdateSpaceProductModelDto { + @ApiProperty({ + description: 'ID of the product model', + example: 'product-uuid', + }) + @IsNotEmpty() + @IsString() + productModelUuid: string; + + @ApiProperty({ + description: 'Number of products in the model', + example: 3, + }) + @IsNotEmpty() + @IsInt() + productCount: number; + + @ApiProperty({ + description: 'Specific names for each product item', + type: [CreateProductItemModelDto], + }) + @IsArray() + @ArrayNotEmpty() + @ValidateNested({ each: true }) + @Type(() => CreateProductItemModelDto) + items: CreateProductItemModelDto[]; +} diff --git a/src/space-model/dtos/index.ts b/src/space-model/dtos/index.ts index e7b2373..0f46c63 100644 --- a/src/space-model/dtos/index.ts +++ b/src/space-model/dtos/index.ts @@ -1,8 +1,7 @@ export * from './create-space-model.dto'; export * from './create-space-product-item-model.dto'; export * from './create-space-product-model.dto'; -export * from './create-subspace-model.dto'; export * from './project-param.dto'; export * from './update-space-model.dto'; export * from './space-model-param'; -export * from './update-subspace-model.dto'; +export * from './subspaces-model-dtos'; diff --git a/src/space-model/dtos/create-subspace-model.dto.ts b/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts similarity index 89% rename from src/space-model/dtos/create-subspace-model.dto.ts rename to src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts index f8dbcdd..d14cb91 100644 --- a/src/space-model/dtos/create-subspace-model.dto.ts +++ b/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts @@ -6,7 +6,7 @@ import { IsString, ValidateNested, } from 'class-validator'; -import { CreateSpaceProductModelDto } from './create-space-product-model.dto'; +import { CreateSpaceProductModelDto } from '../create-space-product-model.dto'; import { Type } from 'class-transformer'; export class CreateSubspaceModelDto { diff --git a/src/space-model/dtos/subspaces-model-dtos/index.ts b/src/space-model/dtos/subspaces-model-dtos/index.ts index e70ef7b..698a520 100644 --- a/src/space-model/dtos/subspaces-model-dtos/index.ts +++ b/src/space-model/dtos/subspaces-model-dtos/index.ts @@ -1 +1,3 @@ export * from './delete-subspace-model.dto'; +export * from './create-subspace-model.dto'; +export * from './update-subspace-model.dto'; diff --git a/src/space-model/dtos/update-subspace-model.dto.ts b/src/space-model/dtos/subspaces-model-dtos/update-subspace-model.dto.ts similarity index 89% rename from src/space-model/dtos/update-subspace-model.dto.ts rename to src/space-model/dtos/subspaces-model-dtos/update-subspace-model.dto.ts index ee25bd2..962e979 100644 --- a/src/space-model/dtos/update-subspace-model.dto.ts +++ b/src/space-model/dtos/subspaces-model-dtos/update-subspace-model.dto.ts @@ -7,7 +7,7 @@ import { IsOptional, ValidateNested, } from 'class-validator'; -import { CreateSpaceProductModelDto } from './create-space-product-model.dto'; +import { CreateSpaceProductModelDto } from '../create-space-product-model.dto'; export class UpdateSubspaceModelDto { @ApiProperty({ diff --git a/src/space-model/dtos/update-space-model.dto.ts b/src/space-model/dtos/update-space-model.dto.ts index 62a6bc1..7a7051b 100644 --- a/src/space-model/dtos/update-space-model.dto.ts +++ b/src/space-model/dtos/update-space-model.dto.ts @@ -1,11 +1,13 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsArray, IsOptional, IsString, ValidateNested } from 'class-validator'; -import { CreateSubspaceModelDto } from './create-subspace-model.dto'; +import { CreateSubspaceModelDto } from './subspaces-model-dtos/create-subspace-model.dto'; import { Type } from 'class-transformer'; -import { UpdateSubspaceModelDto } from './update-subspace-model.dto'; -import { DeleteSubspaceModelDto } from './subspaces-model-dtos'; +import { + DeleteSubspaceModelDto, + UpdateSubspaceModelDto, +} from './subspaces-model-dtos'; -export class UpdateSubspacesModelDto { +export class ModifySubspacesModelDto { @ApiProperty({ description: 'List of subspaces to add', type: [CreateSubspaceModelDto], @@ -50,5 +52,5 @@ export class UpdateSpaceModelDto { modelName?: string; @IsOptional() - subspaceModels?: UpdateSubspacesModelDto; + subspaceModels?: ModifySubspacesModelDto; } diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index bcf5433..305323d 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -6,7 +6,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { CreateSubspaceModelDto, UpdateSubspaceModelDto, - UpdateSubspacesModelDto, + ModifySubspacesModelDto, } from '../../dtos'; import { QueryRunner } from 'typeorm'; import { SubspaceProductModelService } from './subspace-product-model.service'; @@ -178,7 +178,7 @@ export class SubSpaceModelService { } async modifySubSpaceModels( - dto: UpdateSubspacesModelDto, + dto: ModifySubspacesModelDto, spaceModel: SpaceModelEntity, queryRunner: QueryRunner, ) { From 038f93f16a49023d55f1a7ebcec91a5eaaa1ea8f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 20 Dec 2024 08:03:25 +0400 Subject: [PATCH 094/247] fixed dtos --- .../dtos/create-space-product-model.dto.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/space-model/dtos/create-space-product-model.dto.ts b/src/space-model/dtos/create-space-product-model.dto.ts index 2dab9aa..03da688 100644 --- a/src/space-model/dtos/create-space-product-model.dto.ts +++ b/src/space-model/dtos/create-space-product-model.dto.ts @@ -6,6 +6,7 @@ import { ValidateNested, IsInt, ArrayNotEmpty, + IsOptional, } from 'class-validator'; import { Type } from 'class-transformer'; import { CreateProductItemModelDto } from './create-space-product-item-model.dto'; @@ -65,3 +66,23 @@ export class UpdateSpaceProductModelDto { @Type(() => CreateProductItemModelDto) items: CreateProductItemModelDto[]; } + +export class ModifyProductItemModelDto { + @IsArray() + @ApiProperty({ + description: 'Create the product model ', + type: [CreateSpaceProductModelDto], + }) + @ValidateNested({ each: true }) + @IsOptional() + add?: CreateSpaceProductModelDto[]; + + @IsArray() + @ApiProperty({ + description: 'Update the product model ', + type: [UpdateSpaceProductModelDto], + }) + @ValidateNested({ each: true }) + @IsOptional() + update?: UpdateSpaceProductModelDto[]; +} From 9bb59f04742f8ce64b5e7428040e0ac64837cec4 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 20 Dec 2024 08:05:04 +0400 Subject: [PATCH 095/247] added product model dtos --- .../dtos/create-space-product-model.dto.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/space-model/dtos/create-space-product-model.dto.ts b/src/space-model/dtos/create-space-product-model.dto.ts index 03da688..3d88ca4 100644 --- a/src/space-model/dtos/create-space-product-model.dto.ts +++ b/src/space-model/dtos/create-space-product-model.dto.ts @@ -67,7 +67,17 @@ export class UpdateSpaceProductModelDto { items: CreateProductItemModelDto[]; } -export class ModifyProductItemModelDto { +export class DeleteProductModelDto { + @ApiProperty({ + description: 'ID of the product model', + example: 'product-uuid', + }) + @IsNotEmpty() + @IsString() + productModelUuid: string; +} + +export class ModifyProductModelDto { @IsArray() @ApiProperty({ description: 'Create the product model ', @@ -85,4 +95,12 @@ export class ModifyProductItemModelDto { @ValidateNested({ each: true }) @IsOptional() update?: UpdateSpaceProductModelDto[]; + + @IsArray() + @ApiProperty({ + description: 'Update the product model ', + type: [UpdateSpaceProductModelDto], + }) + @IsOptional() + delete?: DeleteProductModelDto[]; } From 7bfa0c32a8ad10b62dfba2eeb99c0ab560781547 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 20 Dec 2024 08:08:17 +0400 Subject: [PATCH 096/247] changed dto name --- .../common/services/base-product-model.service.ts | 4 ++-- src/space-model/dtos/create-space-model.dto.ts | 8 ++++---- src/space-model/dtos/index.ts | 2 +- .../create-space-product-model.dto.ts | 8 ++++---- src/space-model/dtos/product-model-dtos/index.ts | 1 + .../subspaces-model-dtos/create-subspace-model.dto.ts | 8 ++++---- .../subspaces-model-dtos/update-subspace-model.dto.ts | 8 ++++---- src/space-model/services/space-product-model.service.ts | 4 ++-- .../services/subspace/subspace-product-model.service.ts | 4 ++-- 9 files changed, 24 insertions(+), 23 deletions(-) rename src/space-model/dtos/{ => product-model-dtos}/create-space-product-model.dto.ts (91%) create mode 100644 src/space-model/dtos/product-model-dtos/index.ts diff --git a/src/space-model/common/services/base-product-model.service.ts b/src/space-model/common/services/base-product-model.service.ts index b83511a..c3230a3 100644 --- a/src/space-model/common/services/base-product-model.service.ts +++ b/src/space-model/common/services/base-product-model.service.ts @@ -1,12 +1,12 @@ import { HttpException, HttpStatus } from '@nestjs/common'; -import { CreateSpaceProductModelDto } from 'src/space-model/dtos'; +import { CreateProductModelDto } from '../../dtos'; import { ProductService } from '../../../product/services'; export abstract class BaseProductModelService { constructor(private readonly productService: ProductService) {} protected async validateProductCount( - dto: CreateSpaceProductModelDto, + dto: CreateProductModelDto, ): Promise { const productItemCount = dto.items.length; if (dto.productCount !== productItemCount) { diff --git a/src/space-model/dtos/create-space-model.dto.ts b/src/space-model/dtos/create-space-model.dto.ts index 2988e53..31ff764 100644 --- a/src/space-model/dtos/create-space-model.dto.ts +++ b/src/space-model/dtos/create-space-model.dto.ts @@ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString, IsArray, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; import { CreateSubspaceModelDto } from './subspaces-model-dtos/create-subspace-model.dto'; -import { CreateSpaceProductModelDto } from './create-space-product-model.dto'; +import { CreateProductModelDto } from './product-model-dtos'; export class CreateSpaceModelDto { @ApiProperty({ @@ -24,10 +24,10 @@ export class CreateSpaceModelDto { @ApiProperty({ description: 'List of products included in the model', - type: [CreateSpaceProductModelDto], + type: [CreateProductModelDto], }) @IsArray() @ValidateNested({ each: true }) - @Type(() => CreateSpaceProductModelDto) - spaceProductModels?: CreateSpaceProductModelDto[]; + @Type(() => CreateProductModelDto) + spaceProductModels?: CreateProductModelDto[]; } diff --git a/src/space-model/dtos/index.ts b/src/space-model/dtos/index.ts index 0f46c63..20aaebc 100644 --- a/src/space-model/dtos/index.ts +++ b/src/space-model/dtos/index.ts @@ -1,6 +1,6 @@ export * from './create-space-model.dto'; export * from './create-space-product-item-model.dto'; -export * from './create-space-product-model.dto'; +export * from './product-model-dtos'; export * from './project-param.dto'; export * from './update-space-model.dto'; export * from './space-model-param'; diff --git a/src/space-model/dtos/create-space-product-model.dto.ts b/src/space-model/dtos/product-model-dtos/create-space-product-model.dto.ts similarity index 91% rename from src/space-model/dtos/create-space-product-model.dto.ts rename to src/space-model/dtos/product-model-dtos/create-space-product-model.dto.ts index 3d88ca4..2ff2a7f 100644 --- a/src/space-model/dtos/create-space-product-model.dto.ts +++ b/src/space-model/dtos/product-model-dtos/create-space-product-model.dto.ts @@ -9,9 +9,9 @@ import { IsOptional, } from 'class-validator'; import { Type } from 'class-transformer'; -import { CreateProductItemModelDto } from './create-space-product-item-model.dto'; +import { CreateProductItemModelDto } from '../create-space-product-item-model.dto'; -export class CreateSpaceProductModelDto { +export class CreateProductModelDto { @ApiProperty({ description: 'ID of the product associated with the model', example: 'product-uuid', @@ -81,11 +81,11 @@ export class ModifyProductModelDto { @IsArray() @ApiProperty({ description: 'Create the product model ', - type: [CreateSpaceProductModelDto], + type: [CreateProductModelDto], }) @ValidateNested({ each: true }) @IsOptional() - add?: CreateSpaceProductModelDto[]; + add?: CreateProductModelDto[]; @IsArray() @ApiProperty({ diff --git a/src/space-model/dtos/product-model-dtos/index.ts b/src/space-model/dtos/product-model-dtos/index.ts new file mode 100644 index 0000000..3fdad23 --- /dev/null +++ b/src/space-model/dtos/product-model-dtos/index.ts @@ -0,0 +1 @@ +export * from './create-space-product-model.dto'; diff --git a/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts b/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts index d14cb91..ce5d2d1 100644 --- a/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts +++ b/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts @@ -6,7 +6,7 @@ import { IsString, ValidateNested, } from 'class-validator'; -import { CreateSpaceProductModelDto } from '../create-space-product-model.dto'; +import { CreateProductModelDto } from '../product-model-dtos'; import { Type } from 'class-transformer'; export class CreateSubspaceModelDto { @@ -20,11 +20,11 @@ export class CreateSubspaceModelDto { @ApiProperty({ description: 'List of products included in the model', - type: [CreateSpaceProductModelDto], + type: [CreateProductModelDto], }) @IsArray() @IsOptional() @ValidateNested({ each: true }) - @Type(() => CreateSpaceProductModelDto) - spaceProductModels?: CreateSpaceProductModelDto[]; + @Type(() => CreateProductModelDto) + spaceProductModels?: CreateProductModelDto[]; } diff --git a/src/space-model/dtos/subspaces-model-dtos/update-subspace-model.dto.ts b/src/space-model/dtos/subspaces-model-dtos/update-subspace-model.dto.ts index 962e979..eb5305c 100644 --- a/src/space-model/dtos/subspaces-model-dtos/update-subspace-model.dto.ts +++ b/src/space-model/dtos/subspaces-model-dtos/update-subspace-model.dto.ts @@ -7,7 +7,7 @@ import { IsOptional, ValidateNested, } from 'class-validator'; -import { CreateSpaceProductModelDto } from '../create-space-product-model.dto'; +import { CreateProductModelDto } from '../product-model-dtos'; export class UpdateSubspaceModelDto { @ApiProperty({ @@ -24,11 +24,11 @@ export class UpdateSubspaceModelDto { @ApiProperty({ description: 'List of products included in the model', - type: [CreateSpaceProductModelDto], + type: [CreateProductModelDto], }) @IsArray() @IsOptional() @ValidateNested({ each: true }) - @Type(() => CreateSpaceProductModelDto) - spaceProductModels?: CreateSpaceProductModelDto[]; + @Type(() => CreateProductModelDto) + spaceProductModels?: CreateProductModelDto[]; } diff --git a/src/space-model/services/space-product-model.service.ts b/src/space-model/services/space-product-model.service.ts index 36695be..ccfa780 100644 --- a/src/space-model/services/space-product-model.service.ts +++ b/src/space-model/services/space-product-model.service.ts @@ -1,6 +1,6 @@ import { QueryRunner } from 'typeorm'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { CreateSpaceProductModelDto } from '../dtos'; +import { CreateProductModelDto } from '../dtos'; import { SpaceProductItemModelService } from './space-product-item-model.service'; import { BaseProductModelService } from '../common'; import { ProductService } from 'src/product/services'; @@ -20,7 +20,7 @@ export class SpaceProductModelService extends BaseProductModelService { } async createSpaceProductModels( - spaceProductModelDtos: CreateSpaceProductModelDto[], + spaceProductModelDtos: CreateProductModelDto[], spaceModel: SpaceModelEntity, queryRunner: QueryRunner, ) { diff --git a/src/space-model/services/subspace/subspace-product-model.service.ts b/src/space-model/services/subspace/subspace-product-model.service.ts index 8b3f9ac..9dfe733 100644 --- a/src/space-model/services/subspace/subspace-product-model.service.ts +++ b/src/space-model/services/subspace/subspace-product-model.service.ts @@ -5,7 +5,7 @@ import { } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { SubspaceProductItemModelService } from './subspace-product-item-model.service'; -import { CreateSpaceProductModelDto } from '../../dtos'; +import { CreateProductModelDto } from '../../dtos'; import { QueryRunner } from 'typeorm'; import { BaseProductModelService } from '../../common'; import { ProductService } from 'src/product/services'; @@ -21,7 +21,7 @@ export class SubspaceProductModelService extends BaseProductModelService { } async createSubspaceProductModels( - spaceProductModelDtos: CreateSpaceProductModelDto[], + spaceProductModelDtos: CreateProductModelDto[], spaceModel: SpaceModelEntity, subspaceModel: SubspaceModelEntity, queryRunner: QueryRunner, From bb63ac08e37182c520552eed5d93c01658155e65 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 20 Dec 2024 08:18:25 +0400 Subject: [PATCH 097/247] updated product model dtos --- .../base-product-model.dto.ts | 12 ++ .../create-product-model.dto.ts | 39 +++++++ .../create-space-product-model.dto.ts | 106 ------------------ .../delete-product-model.dto.ts | 3 + .../dtos/product-model-dtos/index.ts | 5 +- .../product-model-modification.dto.ts | 34 ++++++ .../update-product-model.dto.ts | 32 ++++++ 7 files changed, 124 insertions(+), 107 deletions(-) create mode 100644 src/space-model/dtos/product-model-dtos/base-product-model.dto.ts create mode 100644 src/space-model/dtos/product-model-dtos/create-product-model.dto.ts delete mode 100644 src/space-model/dtos/product-model-dtos/create-space-product-model.dto.ts create mode 100644 src/space-model/dtos/product-model-dtos/delete-product-model.dto.ts create mode 100644 src/space-model/dtos/product-model-dtos/product-model-modification.dto.ts create mode 100644 src/space-model/dtos/product-model-dtos/update-product-model.dto.ts diff --git a/src/space-model/dtos/product-model-dtos/base-product-model.dto.ts b/src/space-model/dtos/product-model-dtos/base-product-model.dto.ts new file mode 100644 index 0000000..4a27559 --- /dev/null +++ b/src/space-model/dtos/product-model-dtos/base-product-model.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class BaseProductModelDto { + @ApiProperty({ + description: 'ID of the product model', + example: 'product-uuid', + }) + @IsNotEmpty() + @IsString() + productModelUuid: string; +} diff --git a/src/space-model/dtos/product-model-dtos/create-product-model.dto.ts b/src/space-model/dtos/product-model-dtos/create-product-model.dto.ts new file mode 100644 index 0000000..947fc59 --- /dev/null +++ b/src/space-model/dtos/product-model-dtos/create-product-model.dto.ts @@ -0,0 +1,39 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IsNotEmpty, + IsString, + IsArray, + ValidateNested, + IsInt, + ArrayNotEmpty, +} from 'class-validator'; +import { Type } from 'class-transformer'; +import { CreateProductItemModelDto } from '../create-space-product-item-model.dto'; + +export class CreateProductModelDto { + @ApiProperty({ + description: 'ID of the product associated with the model', + example: 'product-uuid', + }) + @IsNotEmpty() + @IsString() + productUuid: string; + + @ApiProperty({ + description: 'Number of products in the model', + example: 3, + }) + @IsNotEmpty() + @IsInt() + productCount: number; + + @ApiProperty({ + description: 'Specific names for each product item', + type: [CreateProductItemModelDto], + }) + @IsArray() + @ArrayNotEmpty() + @ValidateNested({ each: true }) + @Type(() => CreateProductItemModelDto) + items: CreateProductItemModelDto[]; +} diff --git a/src/space-model/dtos/product-model-dtos/create-space-product-model.dto.ts b/src/space-model/dtos/product-model-dtos/create-space-product-model.dto.ts deleted file mode 100644 index 2ff2a7f..0000000 --- a/src/space-model/dtos/product-model-dtos/create-space-product-model.dto.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { - IsNotEmpty, - IsString, - IsArray, - ValidateNested, - IsInt, - ArrayNotEmpty, - IsOptional, -} from 'class-validator'; -import { Type } from 'class-transformer'; -import { CreateProductItemModelDto } from '../create-space-product-item-model.dto'; - -export class CreateProductModelDto { - @ApiProperty({ - description: 'ID of the product associated with the model', - example: 'product-uuid', - }) - @IsNotEmpty() - @IsString() - productUuid: string; - - @ApiProperty({ - description: 'Number of products in the model', - example: 3, - }) - @IsNotEmpty() - @IsInt() - productCount: number; - - @ApiProperty({ - description: 'Specific names for each product item', - type: [CreateProductItemModelDto], - }) - @IsArray() - @ArrayNotEmpty() - @ValidateNested({ each: true }) - @Type(() => CreateProductItemModelDto) - items: CreateProductItemModelDto[]; -} - -export class UpdateSpaceProductModelDto { - @ApiProperty({ - description: 'ID of the product model', - example: 'product-uuid', - }) - @IsNotEmpty() - @IsString() - productModelUuid: string; - - @ApiProperty({ - description: 'Number of products in the model', - example: 3, - }) - @IsNotEmpty() - @IsInt() - productCount: number; - - @ApiProperty({ - description: 'Specific names for each product item', - type: [CreateProductItemModelDto], - }) - @IsArray() - @ArrayNotEmpty() - @ValidateNested({ each: true }) - @Type(() => CreateProductItemModelDto) - items: CreateProductItemModelDto[]; -} - -export class DeleteProductModelDto { - @ApiProperty({ - description: 'ID of the product model', - example: 'product-uuid', - }) - @IsNotEmpty() - @IsString() - productModelUuid: string; -} - -export class ModifyProductModelDto { - @IsArray() - @ApiProperty({ - description: 'Create the product model ', - type: [CreateProductModelDto], - }) - @ValidateNested({ each: true }) - @IsOptional() - add?: CreateProductModelDto[]; - - @IsArray() - @ApiProperty({ - description: 'Update the product model ', - type: [UpdateSpaceProductModelDto], - }) - @ValidateNested({ each: true }) - @IsOptional() - update?: UpdateSpaceProductModelDto[]; - - @IsArray() - @ApiProperty({ - description: 'Update the product model ', - type: [UpdateSpaceProductModelDto], - }) - @IsOptional() - delete?: DeleteProductModelDto[]; -} diff --git a/src/space-model/dtos/product-model-dtos/delete-product-model.dto.ts b/src/space-model/dtos/product-model-dtos/delete-product-model.dto.ts new file mode 100644 index 0000000..5653c8d --- /dev/null +++ b/src/space-model/dtos/product-model-dtos/delete-product-model.dto.ts @@ -0,0 +1,3 @@ +import { BaseProductModelDto } from './base-product-model.dto'; + +export class DeleteProductModelDto extends BaseProductModelDto {} diff --git a/src/space-model/dtos/product-model-dtos/index.ts b/src/space-model/dtos/product-model-dtos/index.ts index 3fdad23..b2dbd90 100644 --- a/src/space-model/dtos/product-model-dtos/index.ts +++ b/src/space-model/dtos/product-model-dtos/index.ts @@ -1 +1,4 @@ -export * from './create-space-product-model.dto'; +export * from './create-product-model.dto'; +export * from './delete-product-model.dto'; +export * from './update-product-model.dto'; +export * from './product-model-modification.dto'; diff --git a/src/space-model/dtos/product-model-dtos/product-model-modification.dto.ts b/src/space-model/dtos/product-model-dtos/product-model-modification.dto.ts new file mode 100644 index 0000000..8bf9133 --- /dev/null +++ b/src/space-model/dtos/product-model-dtos/product-model-modification.dto.ts @@ -0,0 +1,34 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, ValidateNested, IsOptional } from 'class-validator'; + +import { CreateProductModelDto } from './create-product-model.dto'; +import { DeleteProductModelDto } from './delete-product-model.dto'; +import { UpdateProductModelDto } from './update-product-model.dto'; + +export class ProductModelModificationDto { + @IsArray() + @ApiProperty({ + description: 'Create the product model ', + type: [CreateProductModelDto], + }) + @ValidateNested({ each: true }) + @IsOptional() + add?: CreateProductModelDto[]; + + @IsArray() + @ApiProperty({ + description: 'Update the product model ', + type: [UpdateProductModelDto], + }) + @ValidateNested({ each: true }) + @IsOptional() + update?: UpdateProductModelDto[]; + + @IsArray() + @ApiProperty({ + description: 'Delete the product model ', + type: [DeleteProductModelDto], + }) + @IsOptional() + delete?: DeleteProductModelDto[]; +} diff --git a/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts b/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts new file mode 100644 index 0000000..0f0d64d --- /dev/null +++ b/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts @@ -0,0 +1,32 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsNotEmpty, + IsString, + IsInt, + IsArray, + ArrayNotEmpty, + ValidateNested, +} from 'class-validator'; +import { CreateProductItemModelDto } from '../create-space-product-item-model.dto'; +import { BaseProductModelDto } from './base-product-model.dto'; + +export class UpdateProductModelDto extends BaseProductModelDto { + @ApiProperty({ + description: 'Number of products in the model', + example: 3, + }) + @IsNotEmpty() + @IsInt() + productCount: number; + + @ApiProperty({ + description: 'Specific names for each product item', + type: [CreateProductItemModelDto], + }) + @IsArray() + @ArrayNotEmpty() + @ValidateNested({ each: true }) + @Type(() => CreateProductItemModelDto) + items: CreateProductItemModelDto[]; +} From e18214c717e775bca1db5f7049718656302e36a4 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 20 Dec 2024 08:45:52 +0400 Subject: [PATCH 098/247] defined product model dto --- src/space-model/dtos/index.ts | 2 +- .../base-product-item-model.dto.ts | 12 +++++++ .../create-product-item-model.dto.ts} | 0 .../delete-product-item-model.dto.ts | 3 ++ .../dtos/product-item-model-dtos/index.ts | 4 +++ .../product-item-model-modification.ts | 34 +++++++++++++++++++ .../update-prodct-item-model.dto.ts | 13 +++++++ .../create-product-model.dto.ts | 2 +- .../update-product-model.dto.ts | 11 +++--- 9 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 src/space-model/dtos/product-item-model-dtos/base-product-item-model.dto.ts rename src/space-model/dtos/{create-space-product-item-model.dto.ts => product-item-model-dtos/create-product-item-model.dto.ts} (100%) create mode 100644 src/space-model/dtos/product-item-model-dtos/delete-product-item-model.dto.ts create mode 100644 src/space-model/dtos/product-item-model-dtos/index.ts create mode 100644 src/space-model/dtos/product-item-model-dtos/product-item-model-modification.ts create mode 100644 src/space-model/dtos/product-item-model-dtos/update-prodct-item-model.dto.ts diff --git a/src/space-model/dtos/index.ts b/src/space-model/dtos/index.ts index 20aaebc..045d4aa 100644 --- a/src/space-model/dtos/index.ts +++ b/src/space-model/dtos/index.ts @@ -1,5 +1,5 @@ export * from './create-space-model.dto'; -export * from './create-space-product-item-model.dto'; +export * from './product-item-model-dtos'; export * from './product-model-dtos'; export * from './project-param.dto'; export * from './update-space-model.dto'; diff --git a/src/space-model/dtos/product-item-model-dtos/base-product-item-model.dto.ts b/src/space-model/dtos/product-item-model-dtos/base-product-item-model.dto.ts new file mode 100644 index 0000000..dc01713 --- /dev/null +++ b/src/space-model/dtos/product-item-model-dtos/base-product-item-model.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class BaseProductItemModelDto { + @ApiProperty({ + description: 'ID of the product item model', + example: 'product-item-uuid', + }) + @IsNotEmpty() + @IsString() + productModelUuid: string; +} diff --git a/src/space-model/dtos/create-space-product-item-model.dto.ts b/src/space-model/dtos/product-item-model-dtos/create-product-item-model.dto.ts similarity index 100% rename from src/space-model/dtos/create-space-product-item-model.dto.ts rename to src/space-model/dtos/product-item-model-dtos/create-product-item-model.dto.ts diff --git a/src/space-model/dtos/product-item-model-dtos/delete-product-item-model.dto.ts b/src/space-model/dtos/product-item-model-dtos/delete-product-item-model.dto.ts new file mode 100644 index 0000000..971a0d0 --- /dev/null +++ b/src/space-model/dtos/product-item-model-dtos/delete-product-item-model.dto.ts @@ -0,0 +1,3 @@ +import { BaseProductItemModelDto } from './base-product-item-model.dto'; + +export class DeleteProductItemModelDto extends BaseProductItemModelDto {} diff --git a/src/space-model/dtos/product-item-model-dtos/index.ts b/src/space-model/dtos/product-item-model-dtos/index.ts new file mode 100644 index 0000000..5225122 --- /dev/null +++ b/src/space-model/dtos/product-item-model-dtos/index.ts @@ -0,0 +1,4 @@ +export * from './create-product-item-model.dto'; +export * from './update-prodct-item-model.dto'; +export * from './delete-product-item-model.dto'; +export * from './product-item-model-modification' \ No newline at end of file diff --git a/src/space-model/dtos/product-item-model-dtos/product-item-model-modification.ts b/src/space-model/dtos/product-item-model-dtos/product-item-model-modification.ts new file mode 100644 index 0000000..04ca2d6 --- /dev/null +++ b/src/space-model/dtos/product-item-model-dtos/product-item-model-modification.ts @@ -0,0 +1,34 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, ValidateNested, IsOptional } from 'class-validator'; + +import { CreateProductItemModelDto } from './create-product-item-model.dto'; +import { UpdateProductItemModelDto } from './update-prodct-item-model.dto'; +import { DeleteProductItemModelDto } from './delete-product-item-model.dto'; + +export class ProductItemModelModificationDto { + @IsArray() + @ApiProperty({ + description: 'Create the product item model ', + type: [CreateProductItemModelDto], + }) + @ValidateNested({ each: true }) + @IsOptional() + add?: CreateProductItemModelDto[]; + + @IsArray() + @ApiProperty({ + description: 'Update the product item model ', + type: [UpdateProductItemModelDto], + }) + @ValidateNested({ each: true }) + @IsOptional() + update?: UpdateProductItemModelDto[]; + + @IsArray() + @ApiProperty({ + description: 'Delete the product item model ', + type: [DeleteProductItemModelDto], + }) + @IsOptional() + delete?: DeleteProductItemModelDto[]; +} diff --git a/src/space-model/dtos/product-item-model-dtos/update-prodct-item-model.dto.ts b/src/space-model/dtos/product-item-model-dtos/update-prodct-item-model.dto.ts new file mode 100644 index 0000000..ef88432 --- /dev/null +++ b/src/space-model/dtos/product-item-model-dtos/update-prodct-item-model.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; +import { BaseProductItemModelDto } from './base-product-item-model.dto'; + +export class UpdateProductItemModelDto extends BaseProductItemModelDto { + @ApiProperty({ + description: 'Specific name for the product item', + example: 'Light 1', + }) + @IsNotEmpty() + @IsString() + tag: string; +} diff --git a/src/space-model/dtos/product-model-dtos/create-product-model.dto.ts b/src/space-model/dtos/product-model-dtos/create-product-model.dto.ts index 947fc59..0b4882e 100644 --- a/src/space-model/dtos/product-model-dtos/create-product-model.dto.ts +++ b/src/space-model/dtos/product-model-dtos/create-product-model.dto.ts @@ -8,7 +8,7 @@ import { ArrayNotEmpty, } from 'class-validator'; import { Type } from 'class-transformer'; -import { CreateProductItemModelDto } from '../create-space-product-item-model.dto'; +import { CreateProductItemModelDto } from '../product-item-model-dtos/create-product-item-model.dto'; export class CreateProductModelDto { @ApiProperty({ diff --git a/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts b/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts index 0f0d64d..60e7baf 100644 --- a/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts +++ b/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts @@ -2,14 +2,13 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsNotEmpty, - IsString, IsInt, IsArray, ArrayNotEmpty, ValidateNested, } from 'class-validator'; -import { CreateProductItemModelDto } from '../create-space-product-item-model.dto'; import { BaseProductModelDto } from './base-product-model.dto'; +import { ProductItemModelModificationDto } from '../product-item-model-dtos'; export class UpdateProductModelDto extends BaseProductModelDto { @ApiProperty({ @@ -21,12 +20,12 @@ export class UpdateProductModelDto extends BaseProductModelDto { productCount: number; @ApiProperty({ - description: 'Specific names for each product item', - type: [CreateProductItemModelDto], + description: 'Update product item', + type: [ProductItemModelModificationDto], }) @IsArray() @ArrayNotEmpty() @ValidateNested({ each: true }) - @Type(() => CreateProductItemModelDto) - items: CreateProductItemModelDto[]; + @Type(() => ProductItemModelModificationDto) + items: ProductItemModelModificationDto[]; } From 61c17c2e95284dc2355589d7ceacc2be3bddf0aa Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 20 Dec 2024 10:35:11 +0400 Subject: [PATCH 099/247] product item model --- .../base-product-item-model.service.ts | 19 +++ .../interfaces/update-subspace.interface.ts | 6 + .../subspace-product-item-model.service.ts | 146 ++++++++++++++++-- .../subspace-product-model.service.ts | 2 +- 4 files changed, 159 insertions(+), 14 deletions(-) diff --git a/src/space-model/common/services/base-product-item-model.service.ts b/src/space-model/common/services/base-product-item-model.service.ts index 80c8119..4d67e6d 100644 --- a/src/space-model/common/services/base-product-item-model.service.ts +++ b/src/space-model/common/services/base-product-item-model.service.ts @@ -57,4 +57,23 @@ export abstract class BaseProductItemService { ); } } + + protected async saveProductItems( + productItems: T[], + targetRepository: any, + queryRunner: QueryRunner, + ): Promise { + try { + const savedItem = await queryRunner.manager.save( + targetRepository, + productItems, + ); + return savedItem; + } catch (error) { + throw new HttpException( + error.message || 'An error occurred while creating product items.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/space-model/interfaces/update-subspace.interface.ts b/src/space-model/interfaces/update-subspace.interface.ts index a49491f..05982a4 100644 --- a/src/space-model/interfaces/update-subspace.interface.ts +++ b/src/space-model/interfaces/update-subspace.interface.ts @@ -21,6 +21,12 @@ export interface IModifySubspaceModelInterface { delete?: IDeletedSubsaceModelInterface[]; } +export interface IModifiedProductItemsModelsInterface { + new?: SubspaceProductItemModelEntity[]; + update?: SubspaceProductItemModelEntity[]; + delete?: string[]; +} + export interface IUpdateSubspaceModelInterface { subspaceName?: string; uuid: string; diff --git a/src/space-model/services/subspace/subspace-product-item-model.service.ts b/src/space-model/services/subspace/subspace-product-item-model.service.ts index 85989f8..cdd0688 100644 --- a/src/space-model/services/subspace/subspace-product-item-model.service.ts +++ b/src/space-model/services/subspace/subspace-product-item-model.service.ts @@ -7,7 +7,13 @@ import { SubspaceProductModelEntity, } from '@app/common/modules/space-model'; import { BaseProductItemService } from '../../common'; -import { CreateProductItemModelDto } from '../../dtos'; +import { + CreateProductItemModelDto, + DeleteProductItemModelDto, + ProductItemModelModificationDto, + UpdateProductItemModelDto, +} from '../../dtos'; +import { IModifiedProductItemsModelsInterface } from 'src/space-model/interfaces'; @Injectable() export class SubspaceProductItemModelService extends BaseProductItemService { @@ -17,7 +23,7 @@ export class SubspaceProductItemModelService extends BaseProductItemService { super(); } - async createProdutItemModel( + async createProductItemModel( itemModelDtos: CreateProductItemModelDto[], subspaceProductModel: SubspaceProductModelEntity, spaceModel: SpaceModelEntity, @@ -29,7 +35,9 @@ export class SubspaceProductItemModelService extends BaseProductItemService { HttpStatus.BAD_REQUEST, ); } + await this.validateTags(itemModelDtos, queryRunner, spaceModel); + try { const productItems = itemModelDtos.map((dto) => queryRunner.manager.create(this.subspaceProductItemRepository.target, { @@ -37,19 +45,131 @@ export class SubspaceProductItemModelService extends BaseProductItemService { subspaceProductModel, }), ); - - await queryRunner.manager.save(productItems); - return productItems; + return await this.saveProductItems( + productItems, + this.subspaceProductItemRepository, + queryRunner, + ); } catch (error) { - if (error instanceof HttpException) { - throw error; - } - - throw new HttpException( - error.message || - 'An unexpected error occurred while creating product items.', - HttpStatus.INTERNAL_SERVER_ERROR, + this.handleException( + error, + 'An unexpected error occurred while creating product items.', ); } } + + async updateProductItemModel( + dtos: UpdateProductItemModelDto[], + spaceModel: SpaceModelEntity, + queryRunner: QueryRunner, + ): Promise { + try { + await this.validateTags(dtos, queryRunner, spaceModel); + + const productItemModels = await Promise.all( + dtos.map(async (dto) => { + const productItemModel = await this.findOne(dto.productModelUuid); + productItemModel.tag = dto.tag; + return productItemModel; + }), + ); + + return (await this.saveProductItems( + productItemModels, + this.subspaceProductItemRepository, + queryRunner, + )) as SubspaceProductItemModelEntity[]; + } catch (error) { + this.handleException( + error, + 'Failed to save Subspace Product Item Model.', + ); + } + } + + async deleteProductModel( + dtos: DeleteProductItemModelDto[], + queryRunner: QueryRunner, + ): Promise { + try { + const productItemModels = await Promise.all( + dtos.map(async (dto) => { + const productItemModel = await this.findOne(dto.productModelUuid); + productItemModel.disabled = true; + return productItemModel; + }), + ); + + const response = (await this.saveProductItems( + productItemModels, + this.subspaceProductItemRepository, + queryRunner, + )) as SubspaceProductItemModelEntity[]; + + return response.map((item) => item.uuid); + } catch (error) { + this.handleException(error, 'Failed to modify SpaceModels.'); + } + } + + async findOne(uuid: string): Promise { + const productItemModel = await this.subspaceProductItemRepository.findOne({ + where: { uuid }, + }); + + if (!productItemModel) { + throw new HttpException( + `Product item model not found for ${uuid}`, + HttpStatus.NOT_FOUND, + ); + } + + return productItemModel; + } + + async modifyProductItemModel( + dto: ProductItemModelModificationDto, + productModel: SubspaceProductModelEntity, + spaceModel: SpaceModelEntity, + queryRunner: QueryRunner, + ): Promise { + const productItemModels: IModifiedProductItemsModelsInterface = {}; + + try { + if (dto.add) { + productItemModels.new = await this.createProductItemModel( + dto.add, + productModel, + spaceModel, + queryRunner, + ); + } + if (dto.update) { + productItemModels.update = await this.updateProductItemModel( + dto.update, + spaceModel, + queryRunner, + ); + } + if (dto.delete) { + productItemModels.delete = await this.deleteProductModel( + dto.delete, + queryRunner, + ); + } + return productItemModels; + } catch (error) { + this.handleException(error, 'Failed to modify SpaceModels.'); + } + } + + private handleException(error: unknown, defaultMessage: string): never { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( + (error as Error).message || defaultMessage, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } } diff --git a/src/space-model/services/subspace/subspace-product-model.service.ts b/src/space-model/services/subspace/subspace-product-model.service.ts index 9dfe733..4e145bc 100644 --- a/src/space-model/services/subspace/subspace-product-model.service.ts +++ b/src/space-model/services/subspace/subspace-product-model.service.ts @@ -50,7 +50,7 @@ export class SubspaceProductModelService extends BaseProductModelService { spaceProductModelDtos.map(async (dto, index) => { const savedModel = savedProductModels[index]; const productItemModels = - await this.subspaceProductItemModelService.createProdutItemModel( + await this.subspaceProductItemModelService.createProductItemModel( dto.items, savedModel, spaceModel, From 786c56524cca124faa45d3d021967f8fdf6039a0 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 20 Dec 2024 10:43:20 +0400 Subject: [PATCH 100/247] makew project-community relation not nullable --- libs/common/src/modules/community/entities/community.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/modules/community/entities/community.entity.ts b/libs/common/src/modules/community/entities/community.entity.ts index 8809f94..a94e586 100644 --- a/libs/common/src/modules/community/entities/community.entity.ts +++ b/libs/common/src/modules/community/entities/community.entity.ts @@ -34,7 +34,7 @@ export class CommunityEntity extends AbstractEntity { externalId: string; @ManyToOne(() => ProjectEntity, (project) => project.communities, { - nullable: true, + nullable: false, }) project: ProjectEntity; } From c78eeff7e6abbe0059fb6beec65fc20b5c15fa0b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 20 Dec 2024 14:16:23 +0400 Subject: [PATCH 101/247] update product model --- .../update-product-model.dto.ts | 4 +- .../interfaces/update-subspace.interface.ts | 4 + .../subspace-product-item-model.service.ts | 2 +- .../subspace-product-model.service.ts | 79 +++++++++++++++++-- 4 files changed, 81 insertions(+), 8 deletions(-) diff --git a/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts b/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts index 60e7baf..224330e 100644 --- a/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts +++ b/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts @@ -12,7 +12,7 @@ import { ProductItemModelModificationDto } from '../product-item-model-dtos'; export class UpdateProductModelDto extends BaseProductModelDto { @ApiProperty({ - description: 'Number of products in the model', + description: 'Number of products to be modified in the model', example: 3, }) @IsNotEmpty() @@ -27,5 +27,5 @@ export class UpdateProductModelDto extends BaseProductModelDto { @ArrayNotEmpty() @ValidateNested({ each: true }) @Type(() => ProductItemModelModificationDto) - items: ProductItemModelModificationDto[]; + items: ProductItemModelModificationDto; } diff --git a/src/space-model/interfaces/update-subspace.interface.ts b/src/space-model/interfaces/update-subspace.interface.ts index 05982a4..7433c2d 100644 --- a/src/space-model/interfaces/update-subspace.interface.ts +++ b/src/space-model/interfaces/update-subspace.interface.ts @@ -35,3 +35,7 @@ export interface IUpdateSubspaceModelInterface { export interface IDeletedSubsaceModelInterface { uuid: string; } + +export interface IModifiedProductModelsInterface { + add?: ProductModelInterface[]; +} diff --git a/src/space-model/services/subspace/subspace-product-item-model.service.ts b/src/space-model/services/subspace/subspace-product-item-model.service.ts index cdd0688..27fc2b9 100644 --- a/src/space-model/services/subspace/subspace-product-item-model.service.ts +++ b/src/space-model/services/subspace/subspace-product-item-model.service.ts @@ -159,7 +159,7 @@ export class SubspaceProductItemModelService extends BaseProductItemService { } return productItemModels; } catch (error) { - this.handleException(error, 'Failed to modify SpaceModels.'); + this.handleException(error, 'Failed to modify Product Item Models.'); } } diff --git a/src/space-model/services/subspace/subspace-product-model.service.ts b/src/space-model/services/subspace/subspace-product-model.service.ts index 4e145bc..9743aca 100644 --- a/src/space-model/services/subspace/subspace-product-model.service.ts +++ b/src/space-model/services/subspace/subspace-product-model.service.ts @@ -1,14 +1,24 @@ import { SpaceModelEntity, SubspaceModelEntity, + SubspaceProductModelEntity, SubspaceProductModelRepository, } from '@app/common/modules/space-model'; -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { SubspaceProductItemModelService } from './subspace-product-item-model.service'; -import { CreateProductModelDto } from '../../dtos'; import { QueryRunner } from 'typeorm'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; + +import { SubspaceProductItemModelService } from './subspace-product-item-model.service'; +import { + CreateProductModelDto, + ProductModelModificationDto, + UpdateProductModelDto, +} from '../../dtos'; import { BaseProductModelService } from '../../common'; import { ProductService } from 'src/product/services'; +import { + IModifiedProductModelsInterface, + ProductModelInterface, +} from '../../interfaces'; @Injectable() export class SubspaceProductModelService extends BaseProductModelService { @@ -25,7 +35,7 @@ export class SubspaceProductModelService extends BaseProductModelService { spaceModel: SpaceModelEntity, subspaceModel: SubspaceModelEntity, queryRunner: QueryRunner, - ) { + ): Promise { try { if (!spaceProductModelDtos?.length) return; @@ -59,7 +69,7 @@ export class SubspaceProductModelService extends BaseProductModelService { return { productModel: savedModel, productItemModels, - }; + } as ProductModelInterface; }), ); return newProductModels; @@ -74,4 +84,63 @@ export class SubspaceProductModelService extends BaseProductModelService { ); } } + + async updateSubspaceProductModels(dtos: UpdateProductModelDto[]) { + try { + for (const dto of dtos) { + await this.findOne(dto.productModelUuid); + const newCount = dto.productCount; + if ( + dto.items.add.length + + dto.items.delete.length + + dto.items.delete.length !== + newCount + ) { + throw new HttpException( + `Invalid list of items`, + HttpStatus.BAD_REQUEST, + ); + } + } + } catch (error) {} + } + + async modifySubspaceProductModels( + dto: ProductModelModificationDto, + spaceModel: SpaceModelEntity, + subspaceModel: SubspaceModelEntity, + queryRunner: QueryRunner, + ) { + const productItemModels: IModifiedProductModelsInterface = {}; + try { + productItemModels.add = await this.createSubspaceProductModels( + dto.add, + spaceModel, + subspaceModel, + queryRunner, + ); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( + 'Failed to modify Subspace product model', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async findOne(uuid: string): Promise { + const productModel = await this.subpaceProductModelRepository.findOne({ + where: { + uuid, + }, + }); + if (!productModel) + throw new HttpException( + `Subspace Product model with uuid ${uuid} not found`, + HttpStatus.NOT_FOUND, + ); + return productModel; + } } From 9245c47ef5673cab9275ebc3f573585825e21145 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 20 Dec 2024 14:27:25 +0400 Subject: [PATCH 102/247] removed count from all entities --- .../dtos/space-product-model.dto.ts | 6 +----- .../subspace-product-model.dto.ts | 6 +----- .../entities/space-product-model.entity.ts | 6 ------ .../subspace-product-model.entity.ts | 6 ------ .../modules/space/dtos/space-product.dto.ts | 6 +----- .../space/entities/space-product.entity.ts | 6 ------ .../subspace/subspace-product.entity.ts | 6 ------ .../services/base-product-model.service.ts | 14 ------------- .../create-product-model.dto.ts | 9 --------- .../update-product-model.dto.ts | 16 +-------------- .../handlers/propate-subspace-handler.ts | 3 +-- .../services/space-product-model.service.ts | 2 -- .../subspace-product-model.service.ts | 14 ------------- src/space/dtos/add.space.dto.ts | 6 ------ .../space-products/space-products.service.ts | 20 ++----------------- .../subspace/subspace-product.service.ts | 13 ------------ 16 files changed, 7 insertions(+), 132 deletions(-) diff --git a/libs/common/src/modules/space-model/dtos/space-product-model.dto.ts b/libs/common/src/modules/space-model/dtos/space-product-model.dto.ts index 7952a23..aa6a3a3 100644 --- a/libs/common/src/modules/space-model/dtos/space-product-model.dto.ts +++ b/libs/common/src/modules/space-model/dtos/space-product-model.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; +import { IsNotEmpty, IsString } from 'class-validator'; import { SpaceProductItemModelDto } from './space-product-item-model.dto'; export class SpaceProductModelDto { @@ -7,10 +7,6 @@ export class SpaceProductModelDto { @IsNotEmpty() uuid: string; - @IsNumber() - @IsNotEmpty() - productCount: number; - @IsString() @IsNotEmpty() productUuid: string; diff --git a/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-model.dto.ts b/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-model.dto.ts index 4eebaae..c76d8d1 100644 --- a/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-model.dto.ts +++ b/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-model.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; +import { IsNotEmpty, IsString } from 'class-validator'; import { SubspaceProductItemModelDto } from './subspace-product-item-model.dto'; export class SubpaceProductModelDto { @@ -7,10 +7,6 @@ export class SubpaceProductModelDto { @IsNotEmpty() uuid: string; - @IsNumber() - @IsNotEmpty() - productCount: number; - @IsString() @IsNotEmpty() productUuid: string; diff --git a/libs/common/src/modules/space-model/entities/space-product-model.entity.ts b/libs/common/src/modules/space-model/entities/space-product-model.entity.ts index 9fdcd83..b520313 100644 --- a/libs/common/src/modules/space-model/entities/space-product-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-product-model.entity.ts @@ -8,12 +8,6 @@ import { SpaceProductEntity } from '../../space/entities'; @Entity({ name: 'space-product-model' }) export class SpaceProductModelEntity extends AbstractEntity { - @Column({ - nullable: false, - type: 'int', - }) - productCount: number; - @ManyToOne( () => SpaceModelEntity, (spaceModel) => spaceModel.spaceProductModels, diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts index 960c6fe..8aa53fb 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts @@ -8,12 +8,6 @@ import { SubspaceProductItemModelEntity } from './subspace-product-item-model.en @Entity({ name: 'subspace-product-model' }) export class SubspaceProductModelEntity extends AbstractEntity { - @Column({ - nullable: false, - type: 'int', - }) - productCount: number; - @Column({ nullable: false, default: false, diff --git a/libs/common/src/modules/space/dtos/space-product.dto.ts b/libs/common/src/modules/space/dtos/space-product.dto.ts index a57d29e..92f5f71 100644 --- a/libs/common/src/modules/space/dtos/space-product.dto.ts +++ b/libs/common/src/modules/space/dtos/space-product.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsString, IsNotEmpty, IsNumber } from 'class-validator'; +import { IsString, IsNotEmpty } from 'class-validator'; import { SpaceProductItemDto } from './space-product-item.dto'; export class SpaceProductModelDto { @@ -7,10 +7,6 @@ export class SpaceProductModelDto { @IsNotEmpty() uuid: string; - @IsNumber() - @IsNotEmpty() - productCount: number; - @IsString() @IsNotEmpty() productUuid: string; diff --git a/libs/common/src/modules/space/entities/space-product.entity.ts b/libs/common/src/modules/space/entities/space-product.entity.ts index c268302..ee2cb4f 100644 --- a/libs/common/src/modules/space/entities/space-product.entity.ts +++ b/libs/common/src/modules/space/entities/space-product.entity.ts @@ -21,12 +21,6 @@ export class SpaceProductEntity extends AbstractEntity { @JoinColumn({ name: 'product_uuid' }) product: ProductEntity; - @Column({ - nullable: false, - type: 'int', - }) - productCount: number; - @Column({ nullable: false, default: false, diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts index 501f539..77cafa3 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts @@ -15,12 +15,6 @@ export class SubspaceProductEntity extends AbstractEntity }) public uuid: string; - @Column({ - nullable: false, - type: 'int', - }) - productCount: number; - @Column({ nullable: false, default: false, diff --git a/src/space-model/common/services/base-product-model.service.ts b/src/space-model/common/services/base-product-model.service.ts index c3230a3..6ff8d91 100644 --- a/src/space-model/common/services/base-product-model.service.ts +++ b/src/space-model/common/services/base-product-model.service.ts @@ -1,22 +1,8 @@ -import { HttpException, HttpStatus } from '@nestjs/common'; -import { CreateProductModelDto } from '../../dtos'; import { ProductService } from '../../../product/services'; export abstract class BaseProductModelService { constructor(private readonly productService: ProductService) {} - protected async validateProductCount( - dto: CreateProductModelDto, - ): Promise { - const productItemCount = dto.items.length; - if (dto.productCount !== productItemCount) { - throw new HttpException( - `Product count (${dto.productCount}) does not match the number of items (${productItemCount}) for product ID ${dto.productUuid}.`, - HttpStatus.BAD_REQUEST, - ); - } - } - protected async getProduct(productId: string) { const product = await this.productService.findOne(productId); return product.data; diff --git a/src/space-model/dtos/product-model-dtos/create-product-model.dto.ts b/src/space-model/dtos/product-model-dtos/create-product-model.dto.ts index 0b4882e..b40e9d7 100644 --- a/src/space-model/dtos/product-model-dtos/create-product-model.dto.ts +++ b/src/space-model/dtos/product-model-dtos/create-product-model.dto.ts @@ -4,7 +4,6 @@ import { IsString, IsArray, ValidateNested, - IsInt, ArrayNotEmpty, } from 'class-validator'; import { Type } from 'class-transformer'; @@ -19,14 +18,6 @@ export class CreateProductModelDto { @IsString() productUuid: string; - @ApiProperty({ - description: 'Number of products in the model', - example: 3, - }) - @IsNotEmpty() - @IsInt() - productCount: number; - @ApiProperty({ description: 'Specific names for each product item', type: [CreateProductItemModelDto], diff --git a/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts b/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts index 224330e..9310b46 100644 --- a/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts +++ b/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts @@ -1,24 +1,10 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; -import { - IsNotEmpty, - IsInt, - IsArray, - ArrayNotEmpty, - ValidateNested, -} from 'class-validator'; +import { IsArray, ArrayNotEmpty, ValidateNested } from 'class-validator'; import { BaseProductModelDto } from './base-product-model.dto'; import { ProductItemModelModificationDto } from '../product-item-model-dtos'; export class UpdateProductModelDto extends BaseProductModelDto { - @ApiProperty({ - description: 'Number of products to be modified in the model', - example: 3, - }) - @IsNotEmpty() - @IsInt() - productCount: number; - @ApiProperty({ description: 'Update product item', type: [ProductItemModelModificationDto], diff --git a/src/space-model/handlers/propate-subspace-handler.ts b/src/space-model/handlers/propate-subspace-handler.ts index 82431bd..d3ff2a1 100644 --- a/src/space-model/handlers/propate-subspace-handler.ts +++ b/src/space-model/handlers/propate-subspace-handler.ts @@ -189,14 +189,13 @@ export class PropogateSubspaceHandler const subspaceProduct = this.productRepository.create({ product: productModel.productModel.product, subspace, - productCount: productModel.productModel.productCount, model: productModel.productModel, }); const createdSubspaceProduct = await this.productRepository.save(subspaceProduct); this.logger.log( - `Product added to subspace ${subspace.id} with count ${createdSubspaceProduct.productCount}`, + `Product added to subspace ${subspace.id} with count ${createdSubspaceProduct.items.length}`, ); return createdSubspaceProduct; } diff --git a/src/space-model/services/space-product-model.service.ts b/src/space-model/services/space-product-model.service.ts index ccfa780..ba14317 100644 --- a/src/space-model/services/space-product-model.service.ts +++ b/src/space-model/services/space-product-model.service.ts @@ -27,13 +27,11 @@ export class SpaceProductModelService extends BaseProductModelService { try { const productModels = await Promise.all( spaceProductModelDtos.map(async (dto) => { - this.validateProductCount(dto); const product = await this.getProduct(dto.productUuid); return queryRunner.manager.create( this.spaceProductModelRepository.target, { product, - productCount: dto.productCount, spaceModel, }, ); diff --git a/src/space-model/services/subspace/subspace-product-model.service.ts b/src/space-model/services/subspace/subspace-product-model.service.ts index 9743aca..8b9d05f 100644 --- a/src/space-model/services/subspace/subspace-product-model.service.ts +++ b/src/space-model/services/subspace/subspace-product-model.service.ts @@ -41,13 +41,11 @@ export class SubspaceProductModelService extends BaseProductModelService { const productModels = await Promise.all( spaceProductModelDtos.map(async (dto) => { - this.validateProductCount(dto); const product = await this.getProduct(dto.productUuid); return queryRunner.manager.create( this.subpaceProductModelRepository.target, { product, - productCount: dto.productCount, subspaceModel, }, ); @@ -89,18 +87,6 @@ export class SubspaceProductModelService extends BaseProductModelService { try { for (const dto of dtos) { await this.findOne(dto.productModelUuid); - const newCount = dto.productCount; - if ( - dto.items.add.length + - dto.items.delete.length + - dto.items.delete.length !== - newCount - ) { - throw new HttpException( - `Invalid list of items`, - HttpStatus.BAD_REQUEST, - ); - } } } catch (error) {} } diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index dba8e92..7c10c59 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -30,12 +30,6 @@ export class ProductAssignmentDto { @IsNotEmpty() productId: string; - @ApiProperty({ - description: 'Number of items to assign for the product', - example: 3, - }) - count: number; - @ApiProperty({ description: 'Specific names for each product item', type: [CreateSpaceProductItemDto], diff --git a/src/space/services/space-products/space-products.service.ts b/src/space/services/space-products/space-products.service.ts index 2fe75cb..6c5f1bd 100644 --- a/src/space/services/space-products/space-products.service.ts +++ b/src/space/services/space-products/space-products.service.ts @@ -31,7 +31,6 @@ export class SpaceProductService { queryRunner.manager.create(SpaceProductEntity, { space: space, product: spaceProductModel.product, - productCount: spaceProductModel.productCount, spaceProductModel: spaceProductModel, }), ); @@ -150,16 +149,13 @@ export class SpaceProductService { ): Promise { const updatedProducts = []; - for (const { productId, count } of uniqueProducts) { + for (const { productId } of uniqueProducts) { productEntities.get(productId); const existingProduct = existingSpaceProducts.find( (spaceProduct) => spaceProduct.product.uuid === productId, ); - if (existingProduct && existingProduct.productCount !== count) { - existingProduct.productCount = count; - updatedProducts.push(existingProduct); - } + updatedProducts.push(existingProduct); } if (updatedProducts.length > 0) { @@ -180,13 +176,11 @@ export class SpaceProductService { for (const uniqueSpaceProduct of uniqueSpaceProducts) { const product = productEntities.get(uniqueSpaceProduct.productId); await this.getProduct(uniqueSpaceProduct.productId); - this.validateProductCount(uniqueSpaceProduct); newProducts.push( queryRunner.manager.create(SpaceProductEntity, { space, product, - productCount: uniqueSpaceProduct.count, }), ); } @@ -209,16 +203,6 @@ export class SpaceProductService { return newProducts; } - private validateProductCount(dto: ProductAssignmentDto) { - const productItemCount = dto.items.length; - if (dto.count !== productItemCount) { - throw new HttpException( - `Product count (${dto.count}) does not match the number of items (${productItemCount}) for product ID ${dto.productId}.`, - HttpStatus.BAD_REQUEST, - ); - } - } - async getProduct(productId: string): Promise { const product = await this.productService.findOne(productId); return product.data; diff --git a/src/space/services/subspace/subspace-product.service.ts b/src/space/services/subspace/subspace-product.service.ts index e789311..48852d7 100644 --- a/src/space/services/subspace/subspace-product.service.ts +++ b/src/space/services/subspace/subspace-product.service.ts @@ -64,7 +64,6 @@ export class SubspaceProductService { return { subspace, product: productModel.product, - productCount: productModel.productCount, model: productModel, }; } @@ -78,13 +77,10 @@ export class SubspaceProductService { try { const newSpaceProducts = await Promise.all( productDtos.map(async (dto) => { - this.validateProductCount(dto); - const product = await this.getProduct(dto.productId); return queryRunner.manager.create(SubspaceProductEntity, { subspace, product, - productCount: dto.count, }); }), ); @@ -116,13 +112,4 @@ export class SubspaceProductService { const product = await this.productService.findOne(productId); return product.data; } - - async validateProductCount(dto: ProductAssignmentDto) { - if (dto.count !== dto.items.length) { - throw new HttpException( - 'Producy item and count doesnot match', - HttpStatus.BAD_REQUEST, - ); - } - } } From 1df462712b91954982c1bb32ab41096795de0260 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 22 Dec 2024 13:26:03 +0400 Subject: [PATCH 103/247] updated subspace models update/delete --- .../update-subspace-model.dto.ts | 11 +- .../interfaces/update-subspace.interface.ts | 8 + .../subspace/subspace-model.service.ts | 189 ++++++++++++------ .../subspace-product-item-model.service.ts | 22 +- .../subspace-product-model.service.ts | 147 ++++++++++++-- 5 files changed, 290 insertions(+), 87 deletions(-) diff --git a/src/space-model/dtos/subspaces-model-dtos/update-subspace-model.dto.ts b/src/space-model/dtos/subspaces-model-dtos/update-subspace-model.dto.ts index eb5305c..93ca12b 100644 --- a/src/space-model/dtos/subspaces-model-dtos/update-subspace-model.dto.ts +++ b/src/space-model/dtos/subspaces-model-dtos/update-subspace-model.dto.ts @@ -7,7 +7,7 @@ import { IsOptional, ValidateNested, } from 'class-validator'; -import { CreateProductModelDto } from '../product-model-dtos'; +import { ProductModelModificationDto } from '../product-model-dtos'; export class UpdateSubspaceModelDto { @ApiProperty({ @@ -23,12 +23,11 @@ export class UpdateSubspaceModelDto { subspaceUuid: string; @ApiProperty({ - description: 'List of products included in the model', - type: [CreateProductModelDto], + description: 'Products models modified in the model', + type: ProductModelModificationDto, }) - @IsArray() @IsOptional() @ValidateNested({ each: true }) - @Type(() => CreateProductModelDto) - spaceProductModels?: CreateProductModelDto[]; + @Type(() => ProductModelModificationDto) + updatedProductModels?: ProductModelModificationDto; } diff --git a/src/space-model/interfaces/update-subspace.interface.ts b/src/space-model/interfaces/update-subspace.interface.ts index 7433c2d..5898d34 100644 --- a/src/space-model/interfaces/update-subspace.interface.ts +++ b/src/space-model/interfaces/update-subspace.interface.ts @@ -30,12 +30,20 @@ export interface IModifiedProductItemsModelsInterface { export interface IUpdateSubspaceModelInterface { subspaceName?: string; uuid: string; + productModels?: IModifiedProductItemsModelsInterface[]; } export interface IDeletedSubsaceModelInterface { uuid: string; } +export interface IUpdatedProductModelInterface { + productModelUuid: string; + productModifiedItemModel: IModifiedProductItemsModelsInterface; +} + export interface IModifiedProductModelsInterface { add?: ProductModelInterface[]; + update?: IUpdatedProductModelInterface[]; + delete?: string[]; } diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index 305323d..caefce2 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -72,62 +72,126 @@ export class SubSpaceModelService { async updateSubspaceModels( updateDtos: UpdateSubspaceModelDto[], + spaceModel: SpaceModelEntity, queryRunner: QueryRunner, - ) { - const updateResults: IUpdateSubspaceModelInterface[] = []; + ): Promise { try { - for (const dto of updateDtos) { - const subspaceModel = await this.findOne(dto.subspaceUuid); - const updateResult: IUpdateSubspaceModelInterface = { - uuid: dto.subspaceUuid, - }; + const updateResults: IUpdateSubspaceModelInterface[] = []; - if (dto.subspaceName) { - subspaceModel.subspaceName = dto.subspaceName; - await queryRunner.manager.update( - this.subspaceModelRepository.target, - { uuid: dto.subspaceUuid }, - { subspaceName: dto.subspaceName }, + const updatePromises = updateDtos.map(async (dto) => { + try { + const subspaceModel = await this.findOne(dto.subspaceUuid); + + if (!subspaceModel) { + throw new HttpException( + `Subspace model with UUID ${dto.subspaceUuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + + const updateResult: IUpdateSubspaceModelInterface = { + uuid: dto.subspaceUuid, + }; + + if (dto.subspaceName) { + await this.updateSubspaceName( + dto.subspaceUuid, + dto.subspaceName, + queryRunner, + ); + subspaceModel.subspaceName = dto.subspaceName; + updateResult.subspaceName = dto.subspaceName; + } + + if (dto.updatedProductModels) { + await this.subSpaceProducetModelService.modifySubspaceProductModels( + dto.updatedProductModels, + spaceModel, + subspaceModel, + queryRunner, + ); + } + + updateResults.push(updateResult); + } catch (error) { + throw new HttpException( + `Failed to update subspace model with UUID ${dto.subspaceUuid}: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, ); - updateResult.subspaceName = dto.subspaceName; } + }); - updateResults.push(updateResult); - } + await Promise.all(updatePromises); return updateResults; } catch (error) { - if (error instanceof HttpException) { - throw error; - } throw new HttpException( - error.message || 'Failed to update SpaceModels', + error.message || 'Failed to update subspace models.', HttpStatus.INTERNAL_SERVER_ERROR, ); } } - async deleteSubspaceModels( - dtos: DeleteSubspaceModelDto[], + private async updateSubspaceName( + uuid: string, + subspaceName: string, queryRunner: QueryRunner, - ) { - const deleteResults: IDeletedSubsaceModelInterface[] = []; + ): Promise { + await queryRunner.manager.update( + this.subspaceModelRepository.target, + { uuid }, + { subspaceName }, + ); + } + + async deleteSubspaceModels( + deleteDtos: DeleteSubspaceModelDto[], + queryRunner: QueryRunner, + ): Promise { try { - for (const dto of dtos) { - await this.findOne(dto.subspaceUuid); + const deleteResults: IDeletedSubsaceModelInterface[] = []; - await queryRunner.manager.update( - this.subspaceModelRepository.target, - { uuid: dto.subspaceUuid }, - { disabled: true }, - ); + const deletePromises = deleteDtos.map(async (dto) => { + try { + const subspaceModel = await this.findOne(dto.subspaceUuid); + + if (!subspaceModel) { + throw new HttpException( + `Subspace model with UUID ${dto.subspaceUuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + + await queryRunner.manager.update( + this.subspaceModelRepository.target, + { uuid: dto.subspaceUuid }, + { disabled: true }, + ); + + if (subspaceModel.productModels.length > 0) { + await this.subSpaceProducetModelService.disableProductModels( + subspaceModel.productModels, + queryRunner, + ); + } + + deleteResults.push({ uuid: dto.subspaceUuid }); + } catch (error) { + throw new HttpException( + `Failed to delete subspace model with UUID ${dto.subspaceUuid}: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + }); + + await Promise.all(deletePromises); - deleteResults.push({ uuid: dto.subspaceUuid }); - } return deleteResults; } catch (error) { - console.error(`Bulk delete operation failed: ${error.message}`); - throw new Error('Bulk delete operation failed.'); + throw new HttpException( + 'Bulk delete operation failed. Please try again.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @@ -136,6 +200,7 @@ export class SubSpaceModelService { where: { uuid: subspaceUuid, }, + relations: ['productModels', 'productModels.itemModels'], }); if (!subspace) { throw new HttpException( @@ -181,36 +246,48 @@ export class SubSpaceModelService { dto: ModifySubspacesModelDto, spaceModel: SpaceModelEntity, queryRunner: QueryRunner, - ) { + ): Promise { const subspaces: IModifySubspaceModelInterface = { spaceModelUuid: spaceModel.uuid, }; + try { + const actions = []; + if (dto.add) { - const addedSubspaces = await this.createSubSpaceModels( - dto.add, - spaceModel, - queryRunner, + actions.push( + this.createSubSpaceModels(dto.add, spaceModel, queryRunner).then( + (addedSubspaces) => { + subspaces.new = addedSubspaces; + }, + ), ); - subspaces.new = addedSubspaces; - } else if (dto.update) { - const updatedSubspaces = await this.updateSubspaceModels( - dto.update, - queryRunner, - ); - subspaces.update = updatedSubspaces; - } else if (dto.delete) { - const deletedSubspaces = await this.deleteSubspaceModels( - dto.delete, - queryRunner, - ); - subspaces.delete = deletedSubspaces; } + + if (dto.update) { + actions.push( + this.updateSubspaceModels(dto.update, spaceModel, queryRunner).then( + (updatedSubspaces) => { + subspaces.update = updatedSubspaces; + }, + ), + ); + } + + if (dto.delete) { + actions.push( + this.deleteSubspaceModels(dto.delete, queryRunner).then( + (deletedSubspaces) => { + subspaces.delete = deletedSubspaces; + }, + ), + ); + } + + await Promise.all(actions); + return subspaces; } catch (error) { - if (error instanceof HttpException) { - throw error; - } throw new HttpException( error.message || 'Failed to modify SpaceModels', HttpStatus.INTERNAL_SERVER_ERROR, diff --git a/src/space-model/services/subspace/subspace-product-item-model.service.ts b/src/space-model/services/subspace/subspace-product-item-model.service.ts index 27fc2b9..592e45c 100644 --- a/src/space-model/services/subspace/subspace-product-item-model.service.ts +++ b/src/space-model/services/subspace/subspace-product-item-model.service.ts @@ -92,6 +92,7 @@ export class SubspaceProductItemModelService extends BaseProductItemService { queryRunner: QueryRunner, ): Promise { try { + if (dtos.length === 0) return; const productItemModels = await Promise.all( dtos.map(async (dto) => { const productItemModel = await this.findOne(dto.productModelUuid); @@ -100,18 +101,29 @@ export class SubspaceProductItemModelService extends BaseProductItemService { }), ); - const response = (await this.saveProductItems( + const disabledItemModels = await this.disableProductItemModels( productItemModels, - this.subspaceProductItemRepository, queryRunner, - )) as SubspaceProductItemModelEntity[]; + ); - return response.map((item) => item.uuid); + return disabledItemModels.map((item) => item.uuid); } catch (error) { - this.handleException(error, 'Failed to modify SpaceModels.'); + this.handleException(error, 'Failed to delete SpaceModels.'); } } + async disableProductItemModels( + productItemModels: SubspaceProductItemModelEntity[], + queryRunner: QueryRunner, + ): Promise { + productItemModels.forEach((model) => (model.disabled = true)); + return (await this.saveProductItems( + productItemModels, + this.subspaceProductItemRepository, + queryRunner, + )) as SubspaceProductItemModelEntity[]; + } + async findOne(uuid: string): Promise { const productItemModel = await this.subspaceProductItemRepository.findOne({ where: { uuid }, diff --git a/src/space-model/services/subspace/subspace-product-model.service.ts b/src/space-model/services/subspace/subspace-product-model.service.ts index 8b9d05f..724234d 100644 --- a/src/space-model/services/subspace/subspace-product-model.service.ts +++ b/src/space-model/services/subspace/subspace-product-model.service.ts @@ -4,12 +4,13 @@ import { SubspaceProductModelEntity, SubspaceProductModelRepository, } from '@app/common/modules/space-model'; -import { QueryRunner } from 'typeorm'; +import { In, QueryRunner } from 'typeorm'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { SubspaceProductItemModelService } from './subspace-product-item-model.service'; import { CreateProductModelDto, + DeleteProductModelDto, ProductModelModificationDto, UpdateProductModelDto, } from '../../dtos'; @@ -17,6 +18,7 @@ import { BaseProductModelService } from '../../common'; import { ProductService } from 'src/product/services'; import { IModifiedProductModelsInterface, + IUpdatedProductModelInterface, ProductModelInterface, } from '../../interfaces'; @@ -83,34 +85,138 @@ export class SubspaceProductModelService extends BaseProductModelService { } } - async updateSubspaceProductModels(dtos: UpdateProductModelDto[]) { - try { - for (const dto of dtos) { - await this.findOne(dto.productModelUuid); - } - } catch (error) {} - } - - async modifySubspaceProductModels( - dto: ProductModelModificationDto, + async updateSubspaceProductModels( + dtos: UpdateProductModelDto[], spaceModel: SpaceModelEntity, - subspaceModel: SubspaceModelEntity, queryRunner: QueryRunner, - ) { - const productItemModels: IModifiedProductModelsInterface = {}; + ): Promise { try { - productItemModels.add = await this.createSubspaceProductModels( - dto.add, - spaceModel, - subspaceModel, - queryRunner, + const updatedProductModels = await Promise.all( + dtos.map(async (dto) => { + const productModel = await this.findOne(dto.productModelUuid); + const productModifiedItemModel = + await this.subspaceProductItemModelService.modifyProductItemModel( + dto.items, + productModel, + spaceModel, + queryRunner, + ); + return { + productModelUuid: productModel.uuid, + productModifiedItemModel, + }; + }), ); + + return updatedProductModels; } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException( - 'Failed to modify Subspace product model', + 'Failed to update Subspace product model', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async removeSubspaceProductModels( + deleteDtos: DeleteProductModelDto[], + transactionRunner: QueryRunner, + ): Promise { + try { + const productModels = await Promise.all( + deleteDtos.map((dto) => this.findOne(dto.productModelUuid)), + ); + + return await this.disableProductModels(productModels, transactionRunner); + } catch (error) { + throw new HttpException( + 'Failed to remove subspace product models', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async disableProductModels( + productModels: SubspaceProductModelEntity[], + transactionRunner: QueryRunner, + ): Promise { + try { + const productModelUuids = productModels.map((model) => model.uuid); + + await transactionRunner.manager.update( + this.subpaceProductModelRepository.target, + { uuid: In(productModelUuids) }, + { disabled: true }, + ); + + const itemModelDisables = productModels.map((model) => + model.itemModels.length > 0 + ? this.subspaceProductItemModelService.disableProductItemModels( + model.itemModels, + transactionRunner, + ) + : Promise.resolve(), + ); + + await Promise.all(itemModelDisables); + + return productModelUuids; + } catch (error) { + throw new HttpException( + 'Failed to disable product models', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async modifySubspaceProductModels( + modificationDto: ProductModelModificationDto, + spaceEntity: SpaceModelEntity, + subspaceEntity: SubspaceModelEntity, + transactionRunner: QueryRunner, + ): Promise { + const modifiedProductModels: IModifiedProductModelsInterface = {}; + + try { + await Promise.all([ + modificationDto.add + ? this.createSubspaceProductModels( + modificationDto.add, + spaceEntity, + subspaceEntity, + transactionRunner, + ).then((addedModels) => { + modifiedProductModels.add = addedModels; + }) + : Promise.resolve(), + modificationDto.update + ? this.updateSubspaceProductModels( + modificationDto.update, + spaceEntity, + transactionRunner, + ).then((updatedModels) => { + modifiedProductModels.update = updatedModels; + }) + : Promise.resolve(), + modificationDto.delete + ? this.removeSubspaceProductModels( + modificationDto.delete, + transactionRunner, + ).then((deletedModels) => { + modifiedProductModels.delete = deletedModels; + }) + : Promise.resolve(), + ]); + + return modifiedProductModels; + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( + 'Failed to modify subspace product models', HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -121,6 +227,7 @@ export class SubspaceProductModelService extends BaseProductModelService { where: { uuid, }, + relations: ['itemModels'], }); if (!productModel) throw new HttpException( From 2dd48b7bc2bdfaa60016ca3ab52cfc0eece18490 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 23 Dec 2024 08:32:02 +0400 Subject: [PATCH 104/247] clean model entities --- libs/common/src/common.module.ts | 9 ---- libs/common/src/database/database.module.ts | 10 +--- .../product/entities/product.entity.ts | 16 ++---- .../src/modules/space-model/dtos/index.ts | 2 - .../dtos/space-product-item-model.dto.ts | 15 ------ .../dtos/space-product-model.dto.ts | 19 ------- .../space-model/dtos/subspace-model/index.ts | 2 - .../subspace-product-item-model.dto.ts | 15 ------ .../subspace-product-model.dto.ts | 19 ------- .../modules/space-model/dtos/tag-model.dto.ts | 21 ++++++++ .../src/modules/space-model/entities/index.ts | 3 +- .../entities/space-model.entity.ts | 14 ++---- .../space-product-item-model.entity.ts | 35 ------------- .../entities/space-product-model.entity.ts | 50 ------------------- .../entities/subspace-model/index.ts | 3 +- .../subspace-model/subspace-model.entity.ts | 12 ++--- .../subspace-product-item-model.entity.ts | 33 ------------ .../subspace-product-model.entity.ts | 44 ---------------- .../space-model/entities/tag-model.entity.ts | 29 +++++++++++ .../repositories/space-model.repository.ts | 33 ++---------- .../space-model.repository.module.ts | 14 +----- .../entities/space-product-item.entity.ts | 10 ---- .../space/entities/space-product.entity.ts | 11 ---- .../subspace/subspace-product-item.entity.ts | 6 --- .../subspace/subspace-product.entity.ts | 11 +--- 25 files changed, 70 insertions(+), 366 deletions(-) delete mode 100644 libs/common/src/modules/space-model/dtos/space-product-item-model.dto.ts delete mode 100644 libs/common/src/modules/space-model/dtos/space-product-model.dto.ts delete mode 100644 libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-item-model.dto.ts delete mode 100644 libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-model.dto.ts create mode 100644 libs/common/src/modules/space-model/dtos/tag-model.dto.ts delete mode 100644 libs/common/src/modules/space-model/entities/space-product-item-model.entity.ts delete mode 100644 libs/common/src/modules/space-model/entities/space-product-model.entity.ts delete mode 100644 libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts delete mode 100644 libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts create mode 100644 libs/common/src/modules/space-model/entities/tag-model.entity.ts diff --git a/libs/common/src/common.module.ts b/libs/common/src/common.module.ts index 582510d..d63ff67 100644 --- a/libs/common/src/common.module.ts +++ b/libs/common/src/common.module.ts @@ -12,10 +12,7 @@ import { SceneDeviceRepository } from './modules/scene-device/repositories'; import { SpaceProductItemRepository, SpaceRepository } from './modules/space'; import { SpaceModelRepository, - SpaceProductModelRepository, SubspaceModelRepository, - SubspaceProductItemModelRepository, - SubspaceProductModelRepository, } from './modules/space-model'; import { SubspaceRepository } from './modules/space/repositories/subspace.repository'; @Module({ @@ -28,10 +25,7 @@ import { SubspaceRepository } from './modules/space/repositories/subspace.reposi SpaceRepository, SubspaceRepository, SubspaceModelRepository, - SubspaceProductModelRepository, - SubspaceProductItemModelRepository, SpaceModelRepository, - SpaceProductModelRepository, SpaceProductItemRepository, ], exports: [ @@ -45,10 +39,7 @@ import { SubspaceRepository } from './modules/space/repositories/subspace.reposi SpaceRepository, SubspaceRepository, SubspaceModelRepository, - SubspaceProductModelRepository, - SubspaceProductItemModelRepository, SpaceModelRepository, - SpaceProductModelRepository, ], imports: [ ConfigModule.forRoot({ diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 5357904..8fa7f07 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -32,11 +32,8 @@ import { SpaceProductEntity } from '../modules/space/entities/space-product.enti import { ProjectEntity } from '../modules/project/entities'; import { SpaceModelEntity, - SpaceProductItemModelEntity, - SpaceProductModelEntity, SubspaceModelEntity, - SubspaceProductItemModelEntity, - SubspaceProductModelEntity, + TagModel, } from '../modules/space-model/entities'; import { InviteUserEntity, @@ -82,13 +79,10 @@ import { SceneIconEntity, SceneDeviceEntity, SpaceModelEntity, - SpaceProductModelEntity, - SpaceProductItemModelEntity, SubspaceModelEntity, + TagModel, SpaceProductEntity, SpaceProductItemEntity, - SubspaceProductModelEntity, - SubspaceProductItemModelEntity, SubspaceProductEntity, SubspaceProductItemEntity, InviteUserEntity, diff --git a/libs/common/src/modules/product/entities/product.entity.ts b/libs/common/src/modules/product/entities/product.entity.ts index 926d179..6de8ced 100644 --- a/libs/common/src/modules/product/entities/product.entity.ts +++ b/libs/common/src/modules/product/entities/product.entity.ts @@ -3,8 +3,7 @@ import { ProductDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceEntity } from '../../device/entities'; import { SpaceProductEntity } from '../../space/entities/space-product.entity'; -import { SpaceProductModelEntity } from '../../space-model/entities'; -import { SubspaceProductModelEntity } from '../../space-model/entities/subspace-model/subspace-product-model.entity'; +import { TagModel } from '../../space-model'; @Entity({ name: 'product' }) export class ProductEntity extends AbstractEntity { @@ -32,17 +31,8 @@ export class ProductEntity extends AbstractEntity { @OneToMany(() => SpaceProductEntity, (spaceProduct) => spaceProduct.product) spaceProducts: SpaceProductEntity[]; - @OneToMany( - () => SpaceProductModelEntity, - (spaceProductModel) => spaceProductModel.product, - ) - spaceProductModels: SpaceProductModelEntity[]; - - @OneToMany( - () => SubspaceProductModelEntity, - (subspaceProductModel) => subspaceProductModel.product, - ) - subpaceProductModels: SubspaceProductModelEntity[]; + @OneToMany(() => TagModel, (tag) => tag.product) + tagModels: TagModel[]; @OneToMany( () => DeviceEntity, diff --git a/libs/common/src/modules/space-model/dtos/index.ts b/libs/common/src/modules/space-model/dtos/index.ts index 9ba8130..242564d 100644 --- a/libs/common/src/modules/space-model/dtos/index.ts +++ b/libs/common/src/modules/space-model/dtos/index.ts @@ -1,4 +1,2 @@ export * from './subspace-model'; export * from './space-model.dto'; -export * from './space-product-item-model.dto'; -export * from './space-product-model.dto'; diff --git a/libs/common/src/modules/space-model/dtos/space-product-item-model.dto.ts b/libs/common/src/modules/space-model/dtos/space-product-item-model.dto.ts deleted file mode 100644 index 5eb9bca..0000000 --- a/libs/common/src/modules/space-model/dtos/space-product-item-model.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { IsString, IsNotEmpty } from 'class-validator'; - -export class SpaceProductItemModelDto { - @IsString() - @IsNotEmpty() - uuid: string; - - @IsString() - @IsNotEmpty() - tag: string; - - @IsString() - @IsNotEmpty() - spaceProductModelUuid: string; -} diff --git a/libs/common/src/modules/space-model/dtos/space-product-model.dto.ts b/libs/common/src/modules/space-model/dtos/space-product-model.dto.ts deleted file mode 100644 index aa6a3a3..0000000 --- a/libs/common/src/modules/space-model/dtos/space-product-model.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; -import { SpaceProductItemModelDto } from './space-product-item-model.dto'; - -export class SpaceProductModelDto { - @IsString() - @IsNotEmpty() - uuid: string; - - @IsString() - @IsNotEmpty() - productUuid: string; - - @ApiProperty({ - description: 'List of individual items with specific names for the product', - type: [SpaceProductItemModelDto], - }) - items: SpaceProductItemModelDto[]; -} diff --git a/libs/common/src/modules/space-model/dtos/subspace-model/index.ts b/libs/common/src/modules/space-model/dtos/subspace-model/index.ts index 70337b6..fb0ac5a 100644 --- a/libs/common/src/modules/space-model/dtos/subspace-model/index.ts +++ b/libs/common/src/modules/space-model/dtos/subspace-model/index.ts @@ -1,3 +1 @@ export * from './subspace-model.dto'; -export * from './subspace-product-item-model.dto'; -export * from './subspace-product-model.dto'; diff --git a/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-item-model.dto.ts b/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-item-model.dto.ts deleted file mode 100644 index 479642b..0000000 --- a/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-item-model.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { IsString, IsNotEmpty } from 'class-validator'; - -export class SubspaceProductItemModelDto { - @IsString() - @IsNotEmpty() - uuid: string; - - @IsString() - @IsNotEmpty() - tag: string; - - @IsString() - @IsNotEmpty() - subspaceProductModelUuid: string; -} diff --git a/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-model.dto.ts b/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-model.dto.ts deleted file mode 100644 index c76d8d1..0000000 --- a/libs/common/src/modules/space-model/dtos/subspace-model/subspace-product-model.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; -import { SubspaceProductItemModelDto } from './subspace-product-item-model.dto'; - -export class SubpaceProductModelDto { - @IsString() - @IsNotEmpty() - uuid: string; - - @IsString() - @IsNotEmpty() - productUuid: string; - - @ApiProperty({ - description: 'List of individual items with specific names for the product', - type: [SubspaceProductItemModelDto], - }) - items: SubspaceProductItemModelDto[]; -} diff --git a/libs/common/src/modules/space-model/dtos/tag-model.dto.ts b/libs/common/src/modules/space-model/dtos/tag-model.dto.ts new file mode 100644 index 0000000..f98f160 --- /dev/null +++ b/libs/common/src/modules/space-model/dtos/tag-model.dto.ts @@ -0,0 +1,21 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +export class TagModelDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public name: string; + + @IsString() + @IsNotEmpty() + public productUuid: string; + + @IsString() + spaceModelUuid: string; + + @IsString() + subspaceModelUuid: string; +} diff --git a/libs/common/src/modules/space-model/entities/index.ts b/libs/common/src/modules/space-model/entities/index.ts index ec19308..f4fffbd 100644 --- a/libs/common/src/modules/space-model/entities/index.ts +++ b/libs/common/src/modules/space-model/entities/index.ts @@ -1,4 +1,3 @@ export * from './space-model.entity'; -export * from './space-product-item-model.entity'; -export * from './space-product-model.entity'; export * from './subspace-model'; +export * from './tag-model.entity'; diff --git a/libs/common/src/modules/space-model/entities/space-model.entity.ts b/libs/common/src/modules/space-model/entities/space-model.entity.ts index 4c90bcc..b108729 100644 --- a/libs/common/src/modules/space-model/entities/space-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-model.entity.ts @@ -9,9 +9,9 @@ import { import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceModelDto } from '../dtos'; import { SubspaceModelEntity } from './subspace-model'; -import { SpaceProductModelEntity } from './space-product-model.entity'; import { ProjectEntity } from '../../project/entities'; import { SpaceEntity } from '../../space/entities'; +import { TagModel } from './tag-model.entity'; @Entity({ name: 'space-model' }) @Unique(['modelName', 'project']) @@ -51,17 +51,11 @@ export class SpaceModelEntity extends AbstractEntity { ) public subspaceModels: SubspaceModelEntity[]; - @OneToMany( - () => SpaceProductModelEntity, - (productModel) => productModel.spaceModel, - { - nullable: true, - }, - ) - public spaceProductModels: SpaceProductModelEntity[]; - @OneToMany(() => SpaceEntity, (space) => space.spaceModel, { cascade: true, }) public spaces: SpaceEntity[]; + + @OneToMany(() => TagModel, (tag) => tag.spaceModel) + tags: TagModel[]; } diff --git a/libs/common/src/modules/space-model/entities/space-product-item-model.entity.ts b/libs/common/src/modules/space-model/entities/space-product-item-model.entity.ts deleted file mode 100644 index 1d7bd09..0000000 --- a/libs/common/src/modules/space-model/entities/space-product-item-model.entity.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Entity, Column, ManyToOne, OneToMany } from 'typeorm'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { SpaceProductItemModelDto } from '../dtos'; -import { SpaceProductModelEntity } from './space-product-model.entity'; -import { SpaceProductItemEntity } from '../../space/entities'; - -@Entity({ name: 'space-product-item-model' }) -export class SpaceProductItemModelEntity extends AbstractEntity { - @Column({ - nullable: false, - }) - public tag: string; - - @ManyToOne( - () => SpaceProductModelEntity, - (spaceProductModel) => spaceProductModel.items, - { - nullable: false, - }, - ) - public spaceProductModel: SpaceProductModelEntity; - - @Column({ - nullable: false, - default: false, - }) - public disabled: boolean; - - @OneToMany( - () => SpaceProductItemEntity, - (spaceProductItem) => spaceProductItem.spaceProductItemModel, - { cascade: true }, - ) - public items: SpaceProductItemEntity[]; -} diff --git a/libs/common/src/modules/space-model/entities/space-product-model.entity.ts b/libs/common/src/modules/space-model/entities/space-product-model.entity.ts deleted file mode 100644 index b520313..0000000 --- a/libs/common/src/modules/space-model/entities/space-product-model.entity.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Entity, Column, ManyToOne, OneToMany } from 'typeorm'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { ProductEntity } from '../../product/entities'; -import { SpaceModelEntity } from './space-model.entity'; -import { SpaceProductItemModelEntity } from './space-product-item-model.entity'; -import { SpaceProductModelDto } from '../dtos'; -import { SpaceProductEntity } from '../../space/entities'; - -@Entity({ name: 'space-product-model' }) -export class SpaceProductModelEntity extends AbstractEntity { - @ManyToOne( - () => SpaceModelEntity, - (spaceModel) => spaceModel.spaceProductModels, - { - nullable: false, - onDelete: 'CASCADE', - }, - ) - public spaceModel: SpaceModelEntity; - - @ManyToOne(() => ProductEntity, (product) => product.spaceProductModels, { - nullable: false, - onDelete: 'CASCADE', - }) - public product: ProductEntity; - - @Column({ - nullable: false, - default: false, - }) - public disabled: boolean; - - @OneToMany( - () => SpaceProductItemModelEntity, - (item) => item.spaceProductModel, - { - cascade: true, - }, - ) - public items: SpaceProductItemModelEntity[]; - - @OneToMany( - () => SpaceProductEntity, - (spaceProduct) => spaceProduct.spaceProductModel, - { - cascade: true, - }, - ) - public spaceProducts: SpaceProductEntity[]; -} diff --git a/libs/common/src/modules/space-model/entities/subspace-model/index.ts b/libs/common/src/modules/space-model/entities/subspace-model/index.ts index e39403f..262490e 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model/index.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/index.ts @@ -1,3 +1,2 @@ export * from './subspace-model.entity'; -export * from './subspace-product-item-model.entity'; -export * from './subspace-product-model.entity'; + \ No newline at end of file diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts index f166c73..becb2f8 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts @@ -3,7 +3,7 @@ import { Column, Entity, ManyToOne, OneToMany, Unique } from 'typeorm'; import { SubSpaceModelDto } from '../../dtos'; import { SpaceModelEntity } from '../space-model.entity'; import { SubspaceEntity } from '@app/common/modules/space/entities'; -import { SubspaceProductModelEntity } from './subspace-product-model.entity'; +import { TagModel } from '../tag-model.entity'; @Entity({ name: 'subspace-model' }) @Unique(['subspaceName', 'spaceModel']) @@ -41,12 +41,6 @@ export class SubspaceModelEntity extends AbstractEntity { }) public disabled: boolean; - @OneToMany( - () => SubspaceProductModelEntity, - (productModel) => productModel.subspaceModel, - { - nullable: true, - }, - ) - public productModels: SubspaceProductModelEntity[]; + @OneToMany(() => TagModel, (tag) => tag.subspaceModel) + tags: TagModel[]; } diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts deleted file mode 100644 index 59d6db6..0000000 --- a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-item-model.entity.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; -import { Entity, Column, ManyToOne, OneToMany } from 'typeorm'; -import { SubspaceProductItemModelDto } from '../../dtos'; -import { SubspaceProductModelEntity } from './subspace-product-model.entity'; -import { SubspaceProductItemEntity } from '@app/common/modules/space/entities'; - -@Entity({ name: 'subspace-product-item-model' }) -export class SubspaceProductItemModelEntity extends AbstractEntity { - @Column({ - nullable: false, - }) - public tag: string; - - @Column({ - nullable: false, - default: false, - }) - public disabled: boolean; - - @ManyToOne( - () => SubspaceProductModelEntity, - (productModel) => productModel.itemModels, - { - nullable: false, - }, - ) - public subspaceProductModel: SubspaceProductModelEntity; - - @OneToMany(() => SubspaceProductItemEntity, (item) => item.subspaceProduct, { - nullable: true, - }) - items: SubspaceProductItemEntity[]; -} diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts deleted file mode 100644 index 8aa53fb..0000000 --- a/libs/common/src/modules/space-model/entities/subspace-model/subspace-product-model.entity.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Entity, Column, ManyToOne, OneToMany } from 'typeorm'; -import { SubpaceProductModelDto } from '../../dtos'; -import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; -import { SubspaceModelEntity } from './subspace-model.entity'; -import { ProductEntity } from '@app/common/modules/product/entities'; -import { SubspaceProductEntity } from '@app/common/modules/space/entities'; -import { SubspaceProductItemModelEntity } from './subspace-product-item-model.entity'; - -@Entity({ name: 'subspace-product-model' }) -export class SubspaceProductModelEntity extends AbstractEntity { - @Column({ - nullable: false, - default: false, - }) - public disabled: boolean; - - @ManyToOne( - () => SubspaceModelEntity, - (spaceModel) => spaceModel.productModels, - { - nullable: false, - }, - ) - public subspaceModel: SubspaceModelEntity; - - @ManyToOne(() => ProductEntity, (product) => product.subpaceProductModels, { - nullable: false, - }) - public product: ProductEntity; - - @OneToMany(() => SubspaceProductEntity, (product) => product.model, { - nullable: true, - }) - public subspaceProducts: SubspaceProductEntity[]; - - @OneToMany( - () => SubspaceProductItemModelEntity, - (product) => product.subspaceProductModel, - { - nullable: true, - }, - ) - public itemModels: SubspaceProductItemModelEntity[]; -} diff --git a/libs/common/src/modules/space-model/entities/tag-model.entity.ts b/libs/common/src/modules/space-model/entities/tag-model.entity.ts new file mode 100644 index 0000000..499ec58 --- /dev/null +++ b/libs/common/src/modules/space-model/entities/tag-model.entity.ts @@ -0,0 +1,29 @@ +import { Column, Entity, JoinColumn, ManyToOne, Unique } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { TagModelDto } from '../dtos/tag-model.dto'; +import { SpaceModelEntity } from './space-model.entity'; +import { SubspaceModelEntity } from './subspace-model'; +import { ProductEntity } from '../../product/entities'; + +@Entity({ name: 'tag_model' }) +@Unique(['tag', 'product', 'spaceModel', 'subspaceModel']) +export class TagModel extends AbstractEntity { + @Column({ type: 'varchar', length: 255 }) + tag: string; + + @ManyToOne(() => ProductEntity, (product) => product.tagModels, { + nullable: false, + }) + @JoinColumn({ name: 'product_id' }) + product: ProductEntity; + + @ManyToOne(() => SpaceModelEntity, (space) => space.tags, { nullable: true }) + @JoinColumn({ name: 'space_id' }) + spaceModel: SpaceModelEntity; + + @ManyToOne(() => SubspaceModelEntity, (subspace) => subspace.tags, { + nullable: true, + }) + @JoinColumn({ name: 'subspace_id' }) + subspaceModel: SubspaceModelEntity; +} diff --git a/libs/common/src/modules/space-model/repositories/space-model.repository.ts b/libs/common/src/modules/space-model/repositories/space-model.repository.ts index fc92f14..4af0d57 100644 --- a/libs/common/src/modules/space-model/repositories/space-model.repository.ts +++ b/libs/common/src/modules/space-model/repositories/space-model.repository.ts @@ -1,13 +1,6 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; -import { - SpaceModelEntity, - SpaceProductItemModelEntity, - SpaceProductModelEntity, - SubspaceModelEntity, - SubspaceProductItemModelEntity, - SubspaceProductModelEntity, -} from '../entities'; +import { SpaceModelEntity, SubspaceModelEntity, TagModel } from '../entities'; @Injectable() export class SpaceModelRepository extends Repository { @@ -23,28 +16,8 @@ export class SubspaceModelRepository extends Repository { } @Injectable() -export class SubspaceProductModelRepository extends Repository { +export class TagModelRepository extends Repository { constructor(private dataSource: DataSource) { - super(SubspaceProductModelEntity, dataSource.createEntityManager()); - } -} - -@Injectable() -export class SubspaceProductItemModelRepository extends Repository { - constructor(private dataSource: DataSource) { - super(SubspaceProductItemModelEntity, dataSource.createEntityManager()); - } -} - -@Injectable() -export class SpaceProductModelRepository extends Repository { - constructor(private dataSource: DataSource) { - super(SpaceProductModelEntity, dataSource.createEntityManager()); - } -} -@Injectable() -export class SpaceProductItemModelRepository extends Repository { - constructor(private dataSource: DataSource) { - super(SpaceProductItemModelEntity, dataSource.createEntityManager()); + super(TagModel, dataSource.createEntityManager()); } } diff --git a/libs/common/src/modules/space-model/space-model.repository.module.ts b/libs/common/src/modules/space-model/space-model.repository.module.ts index 573bba0..9a35d88 100644 --- a/libs/common/src/modules/space-model/space-model.repository.module.ts +++ b/libs/common/src/modules/space-model/space-model.repository.module.ts @@ -1,10 +1,5 @@ import { TypeOrmModule } from '@nestjs/typeorm'; -import { - SpaceModelEntity, - SpaceProductItemModelEntity, - SpaceProductModelEntity, - SubspaceModelEntity, -} from './entities'; +import { SpaceModelEntity, SubspaceModelEntity, TagModel } from './entities'; import { Module } from '@nestjs/common'; @Module({ @@ -12,12 +7,7 @@ import { Module } from '@nestjs/common'; exports: [], controllers: [], imports: [ - TypeOrmModule.forFeature([ - SpaceModelEntity, - SubspaceModelEntity, - SpaceProductModelEntity, - SpaceProductItemModelEntity, - ]), + TypeOrmModule.forFeature([SpaceModelEntity, SubspaceModelEntity, TagModel]), ], }) export class SpaceModelRepositoryModule {} diff --git a/libs/common/src/modules/space/entities/space-product-item.entity.ts b/libs/common/src/modules/space/entities/space-product-item.entity.ts index d9add42..8f93264 100644 --- a/libs/common/src/modules/space/entities/space-product-item.entity.ts +++ b/libs/common/src/modules/space/entities/space-product-item.entity.ts @@ -2,7 +2,6 @@ import { Column, Entity, ManyToOne, OneToOne } from 'typeorm'; import { SpaceProductEntity } from './space-product.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceProductItemDto } from '../dtos'; -import { SpaceProductItemModelEntity } from '../../space-model'; import { DeviceEntity } from '../../device/entities'; @Entity({ name: 'space-product-item' }) @@ -23,15 +22,6 @@ export class SpaceProductItemEntity extends AbstractEntity }) public disabled: boolean; - @ManyToOne( - () => SpaceProductItemModelEntity, - (spaceProductItemModel) => spaceProductItemModel.items, - { - nullable: true, - }, - ) - public spaceProductItemModel?: SpaceProductItemModelEntity; - @OneToOne(() => DeviceEntity, (device) => device.tag, { nullable: true, }) diff --git a/libs/common/src/modules/space/entities/space-product.entity.ts b/libs/common/src/modules/space/entities/space-product.entity.ts index ee2cb4f..4e333cd 100644 --- a/libs/common/src/modules/space/entities/space-product.entity.ts +++ b/libs/common/src/modules/space/entities/space-product.entity.ts @@ -3,7 +3,6 @@ import { SpaceEntity } from './space.entity'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { ProductEntity } from '../../product/entities'; import { SpaceProductItemEntity } from './space-product-item.entity'; -import { SpaceProductModelEntity } from '../../space-model'; @Entity({ name: 'space-product' }) export class SpaceProductEntity extends AbstractEntity { @@ -32,16 +31,6 @@ export class SpaceProductEntity extends AbstractEntity { }) public items: SpaceProductItemEntity[]; - @ManyToOne( - () => SpaceProductModelEntity, - (spaceProductModel) => spaceProductModel.spaceProducts, - { - nullable: true, - }, - ) - @JoinColumn({ name: 'space_product_model_uuid' }) - public spaceProductModel?: SpaceProductModelEntity; - constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts index 51e206f..eae8a75 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts @@ -2,7 +2,6 @@ import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.e import { SpaceProductItemDto } from '../../dtos'; import { Column, Entity, ManyToOne } from 'typeorm'; import { SubspaceProductEntity } from './subspace-product.entity'; -import { SubspaceProductItemModelEntity } from '@app/common/modules/space-model'; @Entity({ name: 'subspace-product-item' }) export class SubspaceProductItemEntity extends AbstractEntity { @@ -26,11 +25,6 @@ export class SubspaceProductItemEntity extends AbstractEntity SubspaceProductItemModelEntity, (model) => model.items, { - nullable: true, - }) - model: SubspaceProductItemModelEntity; - constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts index 77cafa3..b5a16cc 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts @@ -3,7 +3,6 @@ import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'; import { SubspaceEntity } from './subspace.entity'; import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; import { SubspaceProductItemEntity } from './subspace-product-item.entity'; -import { SubspaceProductModelEntity } from '@app/common/modules/space-model'; import { SpaceProductModelDto } from '../../dtos'; @Entity({ name: 'subspace-product' }) @@ -26,7 +25,7 @@ export class SubspaceProductEntity extends AbstractEntity }) public subspace: SubspaceEntity; - @ManyToOne(() => ProductEntity, (product) => product.subpaceProductModels, { + @ManyToOne(() => ProductEntity, (product) => product.spaceProducts, { nullable: false, }) public product: ProductEntity; @@ -36,12 +35,4 @@ export class SubspaceProductEntity extends AbstractEntity }) public items: SubspaceProductItemEntity[]; - @ManyToOne( - () => SubspaceProductModelEntity, - (model) => model.subspaceProducts, - { - nullable: true, - }, - ) - model: SubspaceProductModelEntity; } From 5d4f04611f3865b9360de9e45d7156ef73b95839 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 23 Dec 2024 08:44:10 +0400 Subject: [PATCH 105/247] service clean up --- .../handlers/propate-subspace-handler.ts | 77 +----- .../interfaces/update-subspace.interface.ts | 14 +- src/space-model/services/index.ts | 2 - .../services/space-model.service.ts | 12 +- .../space-product-item-model.service.ts | 47 ---- .../services/space-product-model.service.ts | 65 ----- src/space-model/services/subspace/index.ts | 2 - .../subspace/subspace-model.service.ts | 27 +- .../subspace-product-item-model.service.ts | 187 -------------- .../subspace-product-model.service.ts | 239 ------------------ src/space-model/space-model.module.ts | 21 +- .../space-product-items.service.ts | 31 --- .../space-products/space-products.service.ts | 34 --- src/space/services/space.service.ts | 6 - .../subspace/subspace-product-item.service.ts | 42 --- .../subspace/subspace-product.service.ts | 50 ---- .../services/subspace/subspace.service.ts | 12 +- 17 files changed, 8 insertions(+), 860 deletions(-) delete mode 100644 src/space-model/services/space-product-item-model.service.ts delete mode 100644 src/space-model/services/space-product-model.service.ts delete mode 100644 src/space-model/services/subspace/subspace-product-item-model.service.ts delete mode 100644 src/space-model/services/subspace/subspace-product-model.service.ts diff --git a/src/space-model/handlers/propate-subspace-handler.ts b/src/space-model/handlers/propate-subspace-handler.ts index d3ff2a1..7d205f9 100644 --- a/src/space-model/handlers/propate-subspace-handler.ts +++ b/src/space-model/handlers/propate-subspace-handler.ts @@ -2,11 +2,7 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { PropogateSubspaceCommand } from '../commands'; import { Logger } from '@nestjs/common'; import { SpaceEntity, SpaceRepository } from '@app/common/modules/space'; -import { - SubspaceProductItemRepository, - SubspaceProductRepository, - SubspaceRepository, -} from '@app/common/modules/space/repositories/subspace.repository'; +import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; import { IDeletedSubsaceModelInterface, IUpdateSubspaceModelInterface, @@ -21,8 +17,6 @@ export class PropogateSubspaceHandler constructor( private readonly spaceRepository: SpaceRepository, private readonly subspaceRepository: SubspaceRepository, - private readonly productRepository: SubspaceProductRepository, - private readonly productItemRepository: SubspaceProductItemRepository, ) {} async execute(command: PropogateSubspaceCommand): Promise { @@ -130,13 +124,9 @@ export class PropogateSubspaceHandler for (const newSubspaceModel of newSubspaceModels) { for (const space of spaces) { try { - const subspace = await this.createSubspace(newSubspaceModel, space); + await this.createSubspace(newSubspaceModel, space); if (newSubspaceModel.productModels?.length > 0) { - await this.processProducts( - newSubspaceModel.productModels, - subspace, - ); } } catch (error) { this.logger.error( @@ -162,69 +152,6 @@ export class PropogateSubspaceHandler return createdSubspace; } - private async processProducts(productModels: any[], subspace: any) { - for (const productModel of productModels) { - try { - const subspaceProduct = await this.createSubspaceProduct( - productModel, - subspace, - ); - - if (productModel.productItemModels?.length > 0) { - await this.processProductItems( - productModel.productItemModels, - subspaceProduct, - ); - } - } catch (error) { - this.logger.error( - `Failed to create product for subspace ID: ${subspace.id}`, - error.stack, - ); - } - } - } - - private async createSubspaceProduct(productModel: any, subspace: any) { - const subspaceProduct = this.productRepository.create({ - product: productModel.productModel.product, - subspace, - model: productModel.productModel, - }); - - const createdSubspaceProduct = - await this.productRepository.save(subspaceProduct); - this.logger.log( - `Product added to subspace ${subspace.id} with count ${createdSubspaceProduct.items.length}`, - ); - return createdSubspaceProduct; - } - - private async processProductItems( - productItemModels: any[], - subspaceProduct: any, - ) { - for (const productItemModel of productItemModels) { - try { - const subspaceProductItem = this.productItemRepository.create({ - tag: productItemModel.tag, - subspaceProduct, - model: productItemModel, - }); - - await this.productItemRepository.save(subspaceProductItem); - this.logger.log( - `Product item added to subspace product ${subspaceProduct.id} with tag ${subspaceProductItem.tag}`, - ); - } catch (error) { - this.logger.error( - `Failed to create product item for subspace product ID: ${subspaceProduct.id}`, - error.stack, - ); - } - } - } - private async getSpacesByModel(uuid: string): Promise { try { return await this.spaceRepository.find({ diff --git a/src/space-model/interfaces/update-subspace.interface.ts b/src/space-model/interfaces/update-subspace.interface.ts index 5898d34..10332ad 100644 --- a/src/space-model/interfaces/update-subspace.interface.ts +++ b/src/space-model/interfaces/update-subspace.interface.ts @@ -1,18 +1,10 @@ -import { - SubspaceModelEntity, - SubspaceProductItemModelEntity, - SubspaceProductModelEntity, -} from '@app/common/modules/space-model'; +import { SubspaceModelEntity } from '@app/common/modules/space-model'; export interface AddSubspaceModelInterface { subspaceModel: SubspaceModelEntity; - productModels: ProductModelInterface[]; } -export interface ProductModelInterface { - productModel: SubspaceProductModelEntity; - productItemModels: SubspaceProductItemModelEntity[]; -} +export interface ProductModelInterface {} export interface IModifySubspaceModelInterface { spaceModelUuid: string; @@ -22,8 +14,6 @@ export interface IModifySubspaceModelInterface { } export interface IModifiedProductItemsModelsInterface { - new?: SubspaceProductItemModelEntity[]; - update?: SubspaceProductItemModelEntity[]; delete?: string[]; } diff --git a/src/space-model/services/index.ts b/src/space-model/services/index.ts index 5c39727..9999493 100644 --- a/src/space-model/services/index.ts +++ b/src/space-model/services/index.ts @@ -1,4 +1,2 @@ export * from './space-model.service'; -export * from './space-product-item-model.service'; -export * from './space-product-model.service'; export * from './subspace'; diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index fff944b..e6156d9 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -7,7 +7,6 @@ import { CreateSpaceModelDto, UpdateSpaceModelDto } from '../dtos'; import { ProjectParam } from 'src/community/dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SubSpaceModelService } from './subspace/subspace-model.service'; -import { SpaceProductModelService } from './space-product-model.service'; import { DataSource } from 'typeorm'; import { TypeORMCustomModel, @@ -29,7 +28,6 @@ export class SpaceModelService { private readonly spaceModelRepository: SpaceModelRepository, private readonly projectService: ProjectService, private readonly subSpaceModelService: SubSpaceModelService, - private readonly spaceProductModelService: SpaceProductModelService, private commandBus: CommandBus, ) {} @@ -37,8 +35,7 @@ export class SpaceModelService { createSpaceModelDto: CreateSpaceModelDto, params: ProjectParam, ) { - const { modelName, subspaceModels, spaceProductModels } = - createSpaceModelDto; + const { modelName, subspaceModels } = createSpaceModelDto; const project = await this.validateProject(params.projectUuid); const queryRunner = this.dataSource.createQueryRunner(); @@ -72,13 +69,6 @@ export class SpaceModelService { ); } - if (spaceProductModels) { - await this.spaceProductModelService.createSpaceProductModels( - spaceProductModels, - savedSpaceModel, - queryRunner, - ); - } await queryRunner.commitTransaction(); return new SuccessResponseDto({ diff --git a/src/space-model/services/space-product-item-model.service.ts b/src/space-model/services/space-product-item-model.service.ts deleted file mode 100644 index c69cae8..0000000 --- a/src/space-model/services/space-product-item-model.service.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - SpaceModelEntity, - SpaceProductItemModelRepository, - SpaceProductModelEntity, -} from '@app/common/modules/space-model'; -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { CreateProductItemModelDto } from '../dtos'; -import { QueryRunner } from 'typeorm'; -import { BaseProductItemService } from '../common'; - -@Injectable() -export class SpaceProductItemModelService extends BaseProductItemService { - constructor( - private readonly spaceProductItemRepository: SpaceProductItemModelRepository, - ) { - super(); - } - - async createProdutItemModel( - itemModelDtos: CreateProductItemModelDto[], - spaceProductModel: SpaceProductModelEntity, - spaceModel: SpaceModelEntity, - queryRunner: QueryRunner, - ) { - await this.validateTags(itemModelDtos, queryRunner, spaceModel); - try { - const productItems = itemModelDtos.map((dto) => - queryRunner.manager.create(this.spaceProductItemRepository.target, { - tag: dto.tag, - spaceProductModel, - }), - ); - - await queryRunner.manager.save(productItems); - } catch (error) { - if (error instanceof HttpException) { - throw error; - } - - throw new HttpException( - error.message || - 'An unexpected error occurred while creating product items.', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } -} diff --git a/src/space-model/services/space-product-model.service.ts b/src/space-model/services/space-product-model.service.ts deleted file mode 100644 index ba14317..0000000 --- a/src/space-model/services/space-product-model.service.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { QueryRunner } from 'typeorm'; -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { CreateProductModelDto } from '../dtos'; -import { SpaceProductItemModelService } from './space-product-item-model.service'; -import { BaseProductModelService } from '../common'; -import { ProductService } from 'src/product/services'; -import { - SpaceModelEntity, - SpaceProductModelRepository, -} from '@app/common/modules/space-model'; - -@Injectable() -export class SpaceProductModelService extends BaseProductModelService { - constructor( - private readonly spaceProductModelRepository: SpaceProductModelRepository, - private readonly spaceProductItemModelService: SpaceProductItemModelService, - productService: ProductService, - ) { - super(productService); - } - - async createSpaceProductModels( - spaceProductModelDtos: CreateProductModelDto[], - spaceModel: SpaceModelEntity, - queryRunner: QueryRunner, - ) { - try { - const productModels = await Promise.all( - spaceProductModelDtos.map(async (dto) => { - const product = await this.getProduct(dto.productUuid); - return queryRunner.manager.create( - this.spaceProductModelRepository.target, - { - product, - spaceModel, - }, - ); - }), - ); - - const savedProductModels = await queryRunner.manager.save(productModels); - - await Promise.all( - spaceProductModelDtos.map((dto, index) => { - const savedModel = savedProductModels[index]; - return this.spaceProductItemModelService.createProdutItemModel( - dto.items, - savedModel, - spaceModel, - queryRunner, - ); - }), - ); - } catch (error) { - if (error instanceof HttpException) { - throw error; - } - throw new HttpException( - error.message || - 'An unexpected error occurred while creating product models.', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } -} diff --git a/src/space-model/services/subspace/index.ts b/src/space-model/services/subspace/index.ts index 78d7cd3..3965e6d 100644 --- a/src/space-model/services/subspace/index.ts +++ b/src/space-model/services/subspace/index.ts @@ -1,3 +1 @@ export * from './subspace-model.service'; -export * from './subspace-product-item-model.service'; -export * from './subspace-product-model.service'; diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index caefce2..fad3ee2 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -9,7 +9,6 @@ import { ModifySubspacesModelDto, } from '../../dtos'; import { QueryRunner } from 'typeorm'; -import { SubspaceProductModelService } from './subspace-product-model.service'; import { IDeletedSubsaceModelInterface, IModifySubspaceModelInterface, @@ -21,7 +20,6 @@ import { DeleteSubspaceModelDto } from 'src/space-model/dtos/subspaces-model-dto export class SubSpaceModelService { constructor( private readonly subspaceModelRepository: SubspaceModelRepository, - private readonly subSpaceProducetModelService: SubspaceProductModelService, ) {} async createSubSpaceModels( @@ -44,16 +42,9 @@ export class SubSpaceModelService { const addedSubspaces = await Promise.all( subSpaceModelDtos.map(async (dto, index) => { const subspaceModel = newSubspaces[index]; - const productModels = - await this.subSpaceProducetModelService.createSubspaceProductModels( - dto.spaceProductModels, - spaceModel, - subspaceModel, - queryRunner, - ); + return { subspaceModel, - productModels, }; }), ); @@ -103,15 +94,6 @@ export class SubSpaceModelService { updateResult.subspaceName = dto.subspaceName; } - if (dto.updatedProductModels) { - await this.subSpaceProducetModelService.modifySubspaceProductModels( - dto.updatedProductModels, - spaceModel, - subspaceModel, - queryRunner, - ); - } - updateResults.push(updateResult); } catch (error) { throw new HttpException( @@ -168,13 +150,6 @@ export class SubSpaceModelService { { disabled: true }, ); - if (subspaceModel.productModels.length > 0) { - await this.subSpaceProducetModelService.disableProductModels( - subspaceModel.productModels, - queryRunner, - ); - } - deleteResults.push({ uuid: dto.subspaceUuid }); } catch (error) { throw new HttpException( diff --git a/src/space-model/services/subspace/subspace-product-item-model.service.ts b/src/space-model/services/subspace/subspace-product-item-model.service.ts deleted file mode 100644 index 592e45c..0000000 --- a/src/space-model/services/subspace/subspace-product-item-model.service.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { QueryRunner } from 'typeorm'; -import { - SpaceModelEntity, - SubspaceProductItemModelEntity, - SubspaceProductItemModelRepository, - SubspaceProductModelEntity, -} from '@app/common/modules/space-model'; -import { BaseProductItemService } from '../../common'; -import { - CreateProductItemModelDto, - DeleteProductItemModelDto, - ProductItemModelModificationDto, - UpdateProductItemModelDto, -} from '../../dtos'; -import { IModifiedProductItemsModelsInterface } from 'src/space-model/interfaces'; - -@Injectable() -export class SubspaceProductItemModelService extends BaseProductItemService { - constructor( - private readonly subspaceProductItemRepository: SubspaceProductItemModelRepository, - ) { - super(); - } - - async createProductItemModel( - itemModelDtos: CreateProductItemModelDto[], - subspaceProductModel: SubspaceProductModelEntity, - spaceModel: SpaceModelEntity, - queryRunner: QueryRunner, - ): Promise { - if (!subspaceProductModel) { - throw new HttpException( - 'The spaceProductModel parameter is required but was not provided.', - HttpStatus.BAD_REQUEST, - ); - } - - await this.validateTags(itemModelDtos, queryRunner, spaceModel); - - try { - const productItems = itemModelDtos.map((dto) => - queryRunner.manager.create(this.subspaceProductItemRepository.target, { - tag: dto.tag, - subspaceProductModel, - }), - ); - return await this.saveProductItems( - productItems, - this.subspaceProductItemRepository, - queryRunner, - ); - } catch (error) { - this.handleException( - error, - 'An unexpected error occurred while creating product items.', - ); - } - } - - async updateProductItemModel( - dtos: UpdateProductItemModelDto[], - spaceModel: SpaceModelEntity, - queryRunner: QueryRunner, - ): Promise { - try { - await this.validateTags(dtos, queryRunner, spaceModel); - - const productItemModels = await Promise.all( - dtos.map(async (dto) => { - const productItemModel = await this.findOne(dto.productModelUuid); - productItemModel.tag = dto.tag; - return productItemModel; - }), - ); - - return (await this.saveProductItems( - productItemModels, - this.subspaceProductItemRepository, - queryRunner, - )) as SubspaceProductItemModelEntity[]; - } catch (error) { - this.handleException( - error, - 'Failed to save Subspace Product Item Model.', - ); - } - } - - async deleteProductModel( - dtos: DeleteProductItemModelDto[], - queryRunner: QueryRunner, - ): Promise { - try { - if (dtos.length === 0) return; - const productItemModels = await Promise.all( - dtos.map(async (dto) => { - const productItemModel = await this.findOne(dto.productModelUuid); - productItemModel.disabled = true; - return productItemModel; - }), - ); - - const disabledItemModels = await this.disableProductItemModels( - productItemModels, - queryRunner, - ); - - return disabledItemModels.map((item) => item.uuid); - } catch (error) { - this.handleException(error, 'Failed to delete SpaceModels.'); - } - } - - async disableProductItemModels( - productItemModels: SubspaceProductItemModelEntity[], - queryRunner: QueryRunner, - ): Promise { - productItemModels.forEach((model) => (model.disabled = true)); - return (await this.saveProductItems( - productItemModels, - this.subspaceProductItemRepository, - queryRunner, - )) as SubspaceProductItemModelEntity[]; - } - - async findOne(uuid: string): Promise { - const productItemModel = await this.subspaceProductItemRepository.findOne({ - where: { uuid }, - }); - - if (!productItemModel) { - throw new HttpException( - `Product item model not found for ${uuid}`, - HttpStatus.NOT_FOUND, - ); - } - - return productItemModel; - } - - async modifyProductItemModel( - dto: ProductItemModelModificationDto, - productModel: SubspaceProductModelEntity, - spaceModel: SpaceModelEntity, - queryRunner: QueryRunner, - ): Promise { - const productItemModels: IModifiedProductItemsModelsInterface = {}; - - try { - if (dto.add) { - productItemModels.new = await this.createProductItemModel( - dto.add, - productModel, - spaceModel, - queryRunner, - ); - } - if (dto.update) { - productItemModels.update = await this.updateProductItemModel( - dto.update, - spaceModel, - queryRunner, - ); - } - if (dto.delete) { - productItemModels.delete = await this.deleteProductModel( - dto.delete, - queryRunner, - ); - } - return productItemModels; - } catch (error) { - this.handleException(error, 'Failed to modify Product Item Models.'); - } - } - - private handleException(error: unknown, defaultMessage: string): never { - if (error instanceof HttpException) { - throw error; - } - throw new HttpException( - (error as Error).message || defaultMessage, - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } -} diff --git a/src/space-model/services/subspace/subspace-product-model.service.ts b/src/space-model/services/subspace/subspace-product-model.service.ts deleted file mode 100644 index 724234d..0000000 --- a/src/space-model/services/subspace/subspace-product-model.service.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { - SpaceModelEntity, - SubspaceModelEntity, - SubspaceProductModelEntity, - SubspaceProductModelRepository, -} from '@app/common/modules/space-model'; -import { In, QueryRunner } from 'typeorm'; -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; - -import { SubspaceProductItemModelService } from './subspace-product-item-model.service'; -import { - CreateProductModelDto, - DeleteProductModelDto, - ProductModelModificationDto, - UpdateProductModelDto, -} from '../../dtos'; -import { BaseProductModelService } from '../../common'; -import { ProductService } from 'src/product/services'; -import { - IModifiedProductModelsInterface, - IUpdatedProductModelInterface, - ProductModelInterface, -} from '../../interfaces'; - -@Injectable() -export class SubspaceProductModelService extends BaseProductModelService { - constructor( - private readonly subpaceProductModelRepository: SubspaceProductModelRepository, - productService: ProductService, - private readonly subspaceProductItemModelService: SubspaceProductItemModelService, - ) { - super(productService); - } - - async createSubspaceProductModels( - spaceProductModelDtos: CreateProductModelDto[], - spaceModel: SpaceModelEntity, - subspaceModel: SubspaceModelEntity, - queryRunner: QueryRunner, - ): Promise { - try { - if (!spaceProductModelDtos?.length) return; - - const productModels = await Promise.all( - spaceProductModelDtos.map(async (dto) => { - const product = await this.getProduct(dto.productUuid); - return queryRunner.manager.create( - this.subpaceProductModelRepository.target, - { - product, - subspaceModel, - }, - ); - }), - ); - - const savedProductModels = await queryRunner.manager.save(productModels); - - const newProductModels = await Promise.all( - spaceProductModelDtos.map(async (dto, index) => { - const savedModel = savedProductModels[index]; - const productItemModels = - await this.subspaceProductItemModelService.createProductItemModel( - dto.items, - savedModel, - spaceModel, - queryRunner, - ); - return { - productModel: savedModel, - productItemModels, - } as ProductModelInterface; - }), - ); - return newProductModels; - } catch (error) { - if (error instanceof HttpException) { - throw error; - } - throw new HttpException( - error.message || - 'An unexpected error occurred while creating product models.', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - async updateSubspaceProductModels( - dtos: UpdateProductModelDto[], - spaceModel: SpaceModelEntity, - queryRunner: QueryRunner, - ): Promise { - try { - const updatedProductModels = await Promise.all( - dtos.map(async (dto) => { - const productModel = await this.findOne(dto.productModelUuid); - const productModifiedItemModel = - await this.subspaceProductItemModelService.modifyProductItemModel( - dto.items, - productModel, - spaceModel, - queryRunner, - ); - return { - productModelUuid: productModel.uuid, - productModifiedItemModel, - }; - }), - ); - - return updatedProductModels; - } catch (error) { - if (error instanceof HttpException) { - throw error; - } - throw new HttpException( - 'Failed to update Subspace product model', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - async removeSubspaceProductModels( - deleteDtos: DeleteProductModelDto[], - transactionRunner: QueryRunner, - ): Promise { - try { - const productModels = await Promise.all( - deleteDtos.map((dto) => this.findOne(dto.productModelUuid)), - ); - - return await this.disableProductModels(productModels, transactionRunner); - } catch (error) { - throw new HttpException( - 'Failed to remove subspace product models', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - async disableProductModels( - productModels: SubspaceProductModelEntity[], - transactionRunner: QueryRunner, - ): Promise { - try { - const productModelUuids = productModels.map((model) => model.uuid); - - await transactionRunner.manager.update( - this.subpaceProductModelRepository.target, - { uuid: In(productModelUuids) }, - { disabled: true }, - ); - - const itemModelDisables = productModels.map((model) => - model.itemModels.length > 0 - ? this.subspaceProductItemModelService.disableProductItemModels( - model.itemModels, - transactionRunner, - ) - : Promise.resolve(), - ); - - await Promise.all(itemModelDisables); - - return productModelUuids; - } catch (error) { - throw new HttpException( - 'Failed to disable product models', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - async modifySubspaceProductModels( - modificationDto: ProductModelModificationDto, - spaceEntity: SpaceModelEntity, - subspaceEntity: SubspaceModelEntity, - transactionRunner: QueryRunner, - ): Promise { - const modifiedProductModels: IModifiedProductModelsInterface = {}; - - try { - await Promise.all([ - modificationDto.add - ? this.createSubspaceProductModels( - modificationDto.add, - spaceEntity, - subspaceEntity, - transactionRunner, - ).then((addedModels) => { - modifiedProductModels.add = addedModels; - }) - : Promise.resolve(), - modificationDto.update - ? this.updateSubspaceProductModels( - modificationDto.update, - spaceEntity, - transactionRunner, - ).then((updatedModels) => { - modifiedProductModels.update = updatedModels; - }) - : Promise.resolve(), - modificationDto.delete - ? this.removeSubspaceProductModels( - modificationDto.delete, - transactionRunner, - ).then((deletedModels) => { - modifiedProductModels.delete = deletedModels; - }) - : Promise.resolve(), - ]); - - return modifiedProductModels; - } catch (error) { - if (error instanceof HttpException) { - throw error; - } - throw new HttpException( - 'Failed to modify subspace product models', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - async findOne(uuid: string): Promise { - const productModel = await this.subpaceProductModelRepository.findOne({ - where: { - uuid, - }, - relations: ['itemModels'], - }); - if (!productModel) - throw new HttpException( - `Subspace Product model with uuid ${uuid} not found`, - HttpStatus.NOT_FOUND, - ); - return productModel; - } -} diff --git a/src/space-model/space-model.module.ts b/src/space-model/space-model.module.ts index e1e4b56..2140d9c 100644 --- a/src/space-model/space-model.module.ts +++ b/src/space-model/space-model.module.ts @@ -2,24 +2,13 @@ import { SpaceRepositoryModule } from '@app/common/modules/space/space.repositor import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { SpaceModelController } from './controllers'; -import { - SpaceModelService, - SpaceProductItemModelService, - SpaceProductModelService, - SubSpaceModelService, - SubspaceProductItemModelService, -} from './services'; +import { SpaceModelService, SubSpaceModelService } from './services'; import { SpaceModelRepository, - SpaceProductItemModelRepository, - SpaceProductModelRepository, SubspaceModelRepository, - SubspaceProductItemModelRepository, - SubspaceProductModelRepository, } from '@app/common/modules/space-model'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProductRepository } from '@app/common/modules/product/repositories'; -import { SubspaceProductModelService } from './services/subspace/subspace-product-model.service'; import { PropogateSubspaceHandler } from './handlers'; import { CqrsModule } from '@nestjs/cqrs'; import { SpaceRepository } from '@app/common/modules/space'; @@ -41,16 +30,8 @@ const CommandHandlers = [PropogateSubspaceHandler]; SpaceRepository, ProjectRepository, SubSpaceModelService, - SpaceProductModelService, SubspaceModelRepository, - SpaceProductModelRepository, ProductRepository, - SpaceProductItemModelService, - SpaceProductItemModelRepository, - SubspaceProductItemModelService, - SubspaceProductItemModelRepository, - SubspaceProductModelService, - SubspaceProductModelRepository, SubspaceRepository, SubspaceProductRepository, SubspaceProductItemRepository, diff --git a/src/space/services/space-product-items/space-product-items.service.ts b/src/space/services/space-product-items/space-product-items.service.ts index d4883b9..d9c141b 100644 --- a/src/space/services/space-product-items/space-product-items.service.ts +++ b/src/space/services/space-product-items/space-product-items.service.ts @@ -6,7 +6,6 @@ import { import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { CreateSpaceProductItemDto } from '../../dtos'; import { QueryRunner } from 'typeorm'; -import { SpaceProductModelEntity } from '@app/common/modules/space-model'; import { BaseProductItemService } from '../../common'; @Injectable() @@ -54,34 +53,4 @@ export class SpaceProductItemService extends BaseProductItemService { ); } } - - async createSpaceProductItemFromModel( - spaceProduct: SpaceProductEntity, - spaceProductModel: SpaceProductModelEntity, - queryRunner: QueryRunner, - ) { - const spaceProductItemModels = spaceProductModel.items; - if (!spaceProductItemModels?.length) return; - - try { - const productItems = spaceProductItemModels.map((model) => - queryRunner.manager.create(this.spaceProductItemRepository.target, { - tag: model.tag, - spaceProduct, - }), - ); - - await queryRunner.manager.save(productItems); - } catch (error) { - if (error instanceof HttpException) { - throw error; - } - - throw new HttpException( - error.message || - 'An unexpected error occurred while creating product items.', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } } diff --git a/src/space/services/space-products/space-products.service.ts b/src/space/services/space-products/space-products.service.ts index 6c5f1bd..4071428 100644 --- a/src/space/services/space-products/space-products.service.ts +++ b/src/space/services/space-products/space-products.service.ts @@ -5,7 +5,6 @@ import { SpaceProductEntity } from '@app/common/modules/space/entities/space-pro import { In, QueryRunner } from 'typeorm'; import { ProductAssignmentDto } from '../../dtos'; import { SpaceProductItemService } from '../space-product-items'; -import { SpaceModelEntity } from '@app/common/modules/space-model'; import { ProductEntity } from '@app/common/modules/product/entities'; import { ProductService } from 'src/product/services'; @@ -17,39 +16,6 @@ export class SpaceProductService { private readonly productService: ProductService, ) {} - async createFromModel( - spaceModel: SpaceModelEntity, - space: SpaceEntity, - queryRunner: QueryRunner, - ) { - const spaceProductModels = spaceModel.spaceProductModels; - if (!spaceProductModels?.length) return; - const newSpaceProducts = []; - - spaceProductModels.map((spaceProductModel) => { - newSpaceProducts.push( - queryRunner.manager.create(SpaceProductEntity, { - space: space, - product: spaceProductModel.product, - spaceProductModel: spaceProductModel, - }), - ); - }); - if (newSpaceProducts.length > 0) { - await queryRunner.manager.save(SpaceProductEntity, newSpaceProducts); - await Promise.all( - newSpaceProducts.map((spaceProduct, index) => { - const spaceProductModel = spaceProductModels[index]; - return this.spaceProductItemService.createSpaceProductItemFromModel( - spaceProduct, - spaceProductModel, - queryRunner, - ); - }), - ); - } - } - async assignProductsToSpace( space: SpaceEntity, products: ProductAssignmentDto[], diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index df0b0a4..2cdf92a 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -108,12 +108,6 @@ export class SpaceService { products, queryRunner, ); - } else if (spaceModel && spaceModel.spaceProductModels.length) { - await this.spaceProductService.createFromModel( - spaceModel, - newSpace, - queryRunner, - ); } await queryRunner.commitTransaction(); diff --git a/src/space/services/subspace/subspace-product-item.service.ts b/src/space/services/subspace/subspace-product-item.service.ts index 647a597..3eefb2f 100644 --- a/src/space/services/subspace/subspace-product-item.service.ts +++ b/src/space/services/subspace/subspace-product-item.service.ts @@ -6,10 +6,6 @@ import { SubspaceProductEntity, SubspaceProductItemEntity, } from '@app/common/modules/space'; -import { - SubspaceProductItemModelEntity, - SubspaceProductModelEntity, -} from '@app/common/modules/space-model'; import { SubspaceProductItemRepository } from '@app/common/modules/space/repositories/subspace.repository'; import { CreateSpaceProductItemDto } from '../../dtos'; import { BaseProductItemService } from '../../common'; @@ -22,44 +18,6 @@ export class SubspaceProductItemService extends BaseProductItemService { super(); } - async createItemFromModel( - product: SubspaceProductEntity, - productModel: SubspaceProductModelEntity, - queryRunner: QueryRunner, - ): Promise { - const itemModels = productModel.itemModels; - - if (!itemModels?.length) return; - - try { - const productItems = itemModels.map((model) => - this.createProductItem(product, model, queryRunner), - ); - - await queryRunner.manager.save( - this.productItemRepository.target, - productItems, - ); - } catch (error) { - throw new HttpException( - error.message || 'An error occurred while creating product items.', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - private createProductItem( - product: SubspaceProductEntity, - model: SubspaceProductItemModelEntity, - queryRunner: QueryRunner, - ): Partial { - return queryRunner.manager.create(this.productItemRepository.target, { - tag: model.tag, - product, - model, - }); - } - async createItemFromDtos( product: SubspaceProductEntity, itemDto: CreateSpaceProductItemDto[], diff --git a/src/space/services/subspace/subspace-product.service.ts b/src/space/services/subspace/subspace-product.service.ts index 48852d7..93ef045 100644 --- a/src/space/services/subspace/subspace-product.service.ts +++ b/src/space/services/subspace/subspace-product.service.ts @@ -6,10 +6,6 @@ import { SubspaceEntity, SubspaceProductEntity, } from '@app/common/modules/space'; -import { - SubspaceModelEntity, - SubspaceProductModelEntity, -} from '@app/common/modules/space-model'; import { SubspaceProductItemService } from './subspace-product-item.service'; import { ProductAssignmentDto } from 'src/space/dtos'; import { ProductService } from 'src/product/services'; @@ -22,52 +18,6 @@ export class SubspaceProductService { private readonly productService: ProductService, ) {} - async createFromModel( - subspaceModel: SubspaceModelEntity, - subspace: SubspaceEntity, - queryRunner: QueryRunner, - ): Promise { - const productModels = subspaceModel.productModels; - if (!productModels?.length) return; - - try { - const newSpaceProducts = productModels.map((productModel) => - this.createSubspaceProductEntity(subspace, productModel), - ); - - const subspaceProducts = await queryRunner.manager.save( - SubspaceProductEntity, - newSpaceProducts, - ); - - await Promise.all( - subspaceProducts.map((subspaceProduct, index) => - this.subspaceProductItemService.createItemFromModel( - subspaceProduct, - productModels[index], - queryRunner, - ), - ), - ); - } catch (error) { - throw new HttpException( - `Transaction failed: Unable to create subspace products ${error}`, - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - private createSubspaceProductEntity( - subspace: SubspaceEntity, - productModel: SubspaceProductModelEntity, - ): Partial { - return { - subspace, - product: productModel.product, - model: productModel, - }; - } - async createFromDto( productDtos: ProductAssignmentDto[], subspace: SubspaceEntity, diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 6b9a1a3..08e12e2 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -66,17 +66,7 @@ export class SubSpaceService { subSpaceModel, })); - const subspaces = await this.createSubspaces(subspaceData, queryRunner); - - await Promise.all( - subSpaceModels.map((model, index) => { - this.productService.createFromModel( - model, - subspaces[index], - queryRunner, - ); - }), - ); + await this.createSubspaces(subspaceData, queryRunner); } async createSubspacesFromDto( From a623028d7b81c63136458ef29295b68dda7380bc Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 23 Dec 2024 08:47:57 +0400 Subject: [PATCH 106/247] dto clean up --- .../dtos/create-space-model.dto.ts | 10 ---------- .../create-subspace-model.dto.ts | 20 +------------------ .../services/space-model.service.ts | 13 +----------- 3 files changed, 2 insertions(+), 41 deletions(-) diff --git a/src/space-model/dtos/create-space-model.dto.ts b/src/space-model/dtos/create-space-model.dto.ts index 31ff764..6ffaf26 100644 --- a/src/space-model/dtos/create-space-model.dto.ts +++ b/src/space-model/dtos/create-space-model.dto.ts @@ -2,7 +2,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString, IsArray, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; import { CreateSubspaceModelDto } from './subspaces-model-dtos/create-subspace-model.dto'; -import { CreateProductModelDto } from './product-model-dtos'; export class CreateSpaceModelDto { @ApiProperty({ @@ -21,13 +20,4 @@ export class CreateSpaceModelDto { @ValidateNested({ each: true }) @Type(() => CreateSubspaceModelDto) subspaceModels?: CreateSubspaceModelDto[]; - - @ApiProperty({ - description: 'List of products included in the model', - type: [CreateProductModelDto], - }) - @IsArray() - @ValidateNested({ each: true }) - @Type(() => CreateProductModelDto) - spaceProductModels?: CreateProductModelDto[]; } diff --git a/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts b/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts index ce5d2d1..a27ad3b 100644 --- a/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts +++ b/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts @@ -1,13 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { - IsArray, - IsNotEmpty, - IsOptional, - IsString, - ValidateNested, -} from 'class-validator'; -import { CreateProductModelDto } from '../product-model-dtos'; -import { Type } from 'class-transformer'; +import { IsNotEmpty, IsString } from 'class-validator'; export class CreateSubspaceModelDto { @ApiProperty({ @@ -17,14 +9,4 @@ export class CreateSubspaceModelDto { @IsNotEmpty() @IsString() subspaceName: string; - - @ApiProperty({ - description: 'List of products included in the model', - type: [CreateProductModelDto], - }) - @IsArray() - @IsOptional() - @ValidateNested({ each: true }) - @Type(() => CreateProductModelDto) - spaceProductModels?: CreateProductModelDto[]; } diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index e6156d9..92301ad 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -17,9 +17,6 @@ import { SpaceModelDto } from '@app/common/modules/space-model/dtos'; import { SpaceModelParam } from '../dtos/space-model-param'; import { ProjectService } from 'src/project/services'; import { ProjectEntity } from '@app/common/modules/project/entities'; -import { IModifySubspaceModelInterface } from '../interfaces'; -import { CommandBus } from '@nestjs/cqrs'; -import { PropogateSubspaceCommand } from '../commands'; @Injectable() export class SpaceModelService { @@ -28,7 +25,6 @@ export class SpaceModelService { private readonly spaceModelRepository: SpaceModelRepository, private readonly projectService: ProjectService, private readonly subSpaceModelService: SubSpaceModelService, - private commandBus: CommandBus, ) {} async createSpaceModel( @@ -136,13 +132,12 @@ export class SpaceModelService { await queryRunner.startTransaction(); try { const { modelName } = dto; - let updatedSubspaces: IModifySubspaceModelInterface; if (modelName) spaceModel.modelName = modelName; await queryRunner.manager.save(spaceModel); if (dto.subspaceModels) { - updatedSubspaces = await this.subSpaceModelService.modifySubSpaceModels( + await this.subSpaceModelService.modifySubSpaceModels( dto.subspaceModels, spaceModel, queryRunner, @@ -151,12 +146,6 @@ export class SpaceModelService { await queryRunner.commitTransaction(); - if (updatedSubspaces) { - await this.commandBus.execute( - new PropogateSubspaceCommand(updatedSubspaces), - ); - } - return new SuccessResponseDto({ message: 'SpaceModel updated successfully', data: spaceModel, From 4545b2e84cc8b958957a12dbbd1f7aea7c2e4363 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 23 Dec 2024 08:49:07 +0400 Subject: [PATCH 107/247] dto clean up --- .../base-product-item-model.dto.ts | 12 ------- .../create-product-item-model.dto.ts | 12 ------- .../delete-product-item-model.dto.ts | 3 -- .../dtos/product-item-model-dtos/index.ts | 4 --- .../product-item-model-modification.ts | 34 ------------------- .../update-prodct-item-model.dto.ts | 13 ------- .../base-product-model.dto.ts | 12 ------- .../create-product-model.dto.ts | 30 ---------------- .../delete-product-model.dto.ts | 3 -- .../dtos/product-model-dtos/index.ts | 4 --- .../product-model-modification.dto.ts | 34 ------------------- .../update-product-model.dto.ts | 17 ---------- .../update-subspace-model.dto.ts | 19 +---------- 13 files changed, 1 insertion(+), 196 deletions(-) delete mode 100644 src/space-model/dtos/product-item-model-dtos/base-product-item-model.dto.ts delete mode 100644 src/space-model/dtos/product-item-model-dtos/create-product-item-model.dto.ts delete mode 100644 src/space-model/dtos/product-item-model-dtos/delete-product-item-model.dto.ts delete mode 100644 src/space-model/dtos/product-item-model-dtos/index.ts delete mode 100644 src/space-model/dtos/product-item-model-dtos/product-item-model-modification.ts delete mode 100644 src/space-model/dtos/product-item-model-dtos/update-prodct-item-model.dto.ts delete mode 100644 src/space-model/dtos/product-model-dtos/base-product-model.dto.ts delete mode 100644 src/space-model/dtos/product-model-dtos/create-product-model.dto.ts delete mode 100644 src/space-model/dtos/product-model-dtos/delete-product-model.dto.ts delete mode 100644 src/space-model/dtos/product-model-dtos/index.ts delete mode 100644 src/space-model/dtos/product-model-dtos/product-model-modification.dto.ts delete mode 100644 src/space-model/dtos/product-model-dtos/update-product-model.dto.ts diff --git a/src/space-model/dtos/product-item-model-dtos/base-product-item-model.dto.ts b/src/space-model/dtos/product-item-model-dtos/base-product-item-model.dto.ts deleted file mode 100644 index dc01713..0000000 --- a/src/space-model/dtos/product-item-model-dtos/base-product-item-model.dto.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class BaseProductItemModelDto { - @ApiProperty({ - description: 'ID of the product item model', - example: 'product-item-uuid', - }) - @IsNotEmpty() - @IsString() - productModelUuid: string; -} diff --git a/src/space-model/dtos/product-item-model-dtos/create-product-item-model.dto.ts b/src/space-model/dtos/product-item-model-dtos/create-product-item-model.dto.ts deleted file mode 100644 index e6a7e76..0000000 --- a/src/space-model/dtos/product-item-model-dtos/create-product-item-model.dto.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class CreateProductItemModelDto { - @ApiProperty({ - description: 'Specific name for the product item', - example: 'Light 1', - }) - @IsNotEmpty() - @IsString() - tag: string; -} diff --git a/src/space-model/dtos/product-item-model-dtos/delete-product-item-model.dto.ts b/src/space-model/dtos/product-item-model-dtos/delete-product-item-model.dto.ts deleted file mode 100644 index 971a0d0..0000000 --- a/src/space-model/dtos/product-item-model-dtos/delete-product-item-model.dto.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { BaseProductItemModelDto } from './base-product-item-model.dto'; - -export class DeleteProductItemModelDto extends BaseProductItemModelDto {} diff --git a/src/space-model/dtos/product-item-model-dtos/index.ts b/src/space-model/dtos/product-item-model-dtos/index.ts deleted file mode 100644 index 5225122..0000000 --- a/src/space-model/dtos/product-item-model-dtos/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './create-product-item-model.dto'; -export * from './update-prodct-item-model.dto'; -export * from './delete-product-item-model.dto'; -export * from './product-item-model-modification' \ No newline at end of file diff --git a/src/space-model/dtos/product-item-model-dtos/product-item-model-modification.ts b/src/space-model/dtos/product-item-model-dtos/product-item-model-modification.ts deleted file mode 100644 index 04ca2d6..0000000 --- a/src/space-model/dtos/product-item-model-dtos/product-item-model-modification.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, ValidateNested, IsOptional } from 'class-validator'; - -import { CreateProductItemModelDto } from './create-product-item-model.dto'; -import { UpdateProductItemModelDto } from './update-prodct-item-model.dto'; -import { DeleteProductItemModelDto } from './delete-product-item-model.dto'; - -export class ProductItemModelModificationDto { - @IsArray() - @ApiProperty({ - description: 'Create the product item model ', - type: [CreateProductItemModelDto], - }) - @ValidateNested({ each: true }) - @IsOptional() - add?: CreateProductItemModelDto[]; - - @IsArray() - @ApiProperty({ - description: 'Update the product item model ', - type: [UpdateProductItemModelDto], - }) - @ValidateNested({ each: true }) - @IsOptional() - update?: UpdateProductItemModelDto[]; - - @IsArray() - @ApiProperty({ - description: 'Delete the product item model ', - type: [DeleteProductItemModelDto], - }) - @IsOptional() - delete?: DeleteProductItemModelDto[]; -} diff --git a/src/space-model/dtos/product-item-model-dtos/update-prodct-item-model.dto.ts b/src/space-model/dtos/product-item-model-dtos/update-prodct-item-model.dto.ts deleted file mode 100644 index ef88432..0000000 --- a/src/space-model/dtos/product-item-model-dtos/update-prodct-item-model.dto.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; -import { BaseProductItemModelDto } from './base-product-item-model.dto'; - -export class UpdateProductItemModelDto extends BaseProductItemModelDto { - @ApiProperty({ - description: 'Specific name for the product item', - example: 'Light 1', - }) - @IsNotEmpty() - @IsString() - tag: string; -} diff --git a/src/space-model/dtos/product-model-dtos/base-product-model.dto.ts b/src/space-model/dtos/product-model-dtos/base-product-model.dto.ts deleted file mode 100644 index 4a27559..0000000 --- a/src/space-model/dtos/product-model-dtos/base-product-model.dto.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; - -export class BaseProductModelDto { - @ApiProperty({ - description: 'ID of the product model', - example: 'product-uuid', - }) - @IsNotEmpty() - @IsString() - productModelUuid: string; -} diff --git a/src/space-model/dtos/product-model-dtos/create-product-model.dto.ts b/src/space-model/dtos/product-model-dtos/create-product-model.dto.ts deleted file mode 100644 index b40e9d7..0000000 --- a/src/space-model/dtos/product-model-dtos/create-product-model.dto.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { - IsNotEmpty, - IsString, - IsArray, - ValidateNested, - ArrayNotEmpty, -} from 'class-validator'; -import { Type } from 'class-transformer'; -import { CreateProductItemModelDto } from '../product-item-model-dtos/create-product-item-model.dto'; - -export class CreateProductModelDto { - @ApiProperty({ - description: 'ID of the product associated with the model', - example: 'product-uuid', - }) - @IsNotEmpty() - @IsString() - productUuid: string; - - @ApiProperty({ - description: 'Specific names for each product item', - type: [CreateProductItemModelDto], - }) - @IsArray() - @ArrayNotEmpty() - @ValidateNested({ each: true }) - @Type(() => CreateProductItemModelDto) - items: CreateProductItemModelDto[]; -} diff --git a/src/space-model/dtos/product-model-dtos/delete-product-model.dto.ts b/src/space-model/dtos/product-model-dtos/delete-product-model.dto.ts deleted file mode 100644 index 5653c8d..0000000 --- a/src/space-model/dtos/product-model-dtos/delete-product-model.dto.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { BaseProductModelDto } from './base-product-model.dto'; - -export class DeleteProductModelDto extends BaseProductModelDto {} diff --git a/src/space-model/dtos/product-model-dtos/index.ts b/src/space-model/dtos/product-model-dtos/index.ts deleted file mode 100644 index b2dbd90..0000000 --- a/src/space-model/dtos/product-model-dtos/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './create-product-model.dto'; -export * from './delete-product-model.dto'; -export * from './update-product-model.dto'; -export * from './product-model-modification.dto'; diff --git a/src/space-model/dtos/product-model-dtos/product-model-modification.dto.ts b/src/space-model/dtos/product-model-dtos/product-model-modification.dto.ts deleted file mode 100644 index 8bf9133..0000000 --- a/src/space-model/dtos/product-model-dtos/product-model-modification.dto.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, ValidateNested, IsOptional } from 'class-validator'; - -import { CreateProductModelDto } from './create-product-model.dto'; -import { DeleteProductModelDto } from './delete-product-model.dto'; -import { UpdateProductModelDto } from './update-product-model.dto'; - -export class ProductModelModificationDto { - @IsArray() - @ApiProperty({ - description: 'Create the product model ', - type: [CreateProductModelDto], - }) - @ValidateNested({ each: true }) - @IsOptional() - add?: CreateProductModelDto[]; - - @IsArray() - @ApiProperty({ - description: 'Update the product model ', - type: [UpdateProductModelDto], - }) - @ValidateNested({ each: true }) - @IsOptional() - update?: UpdateProductModelDto[]; - - @IsArray() - @ApiProperty({ - description: 'Delete the product model ', - type: [DeleteProductModelDto], - }) - @IsOptional() - delete?: DeleteProductModelDto[]; -} diff --git a/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts b/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts deleted file mode 100644 index 9310b46..0000000 --- a/src/space-model/dtos/product-model-dtos/update-product-model.dto.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsArray, ArrayNotEmpty, ValidateNested } from 'class-validator'; -import { BaseProductModelDto } from './base-product-model.dto'; -import { ProductItemModelModificationDto } from '../product-item-model-dtos'; - -export class UpdateProductModelDto extends BaseProductModelDto { - @ApiProperty({ - description: 'Update product item', - type: [ProductItemModelModificationDto], - }) - @IsArray() - @ArrayNotEmpty() - @ValidateNested({ each: true }) - @Type(() => ProductItemModelModificationDto) - items: ProductItemModelModificationDto; -} diff --git a/src/space-model/dtos/subspaces-model-dtos/update-subspace-model.dto.ts b/src/space-model/dtos/subspaces-model-dtos/update-subspace-model.dto.ts index 93ca12b..a386f4e 100644 --- a/src/space-model/dtos/subspaces-model-dtos/update-subspace-model.dto.ts +++ b/src/space-model/dtos/subspaces-model-dtos/update-subspace-model.dto.ts @@ -1,13 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { - IsNotEmpty, - IsString, - IsArray, - IsOptional, - ValidateNested, -} from 'class-validator'; -import { ProductModelModificationDto } from '../product-model-dtos'; +import { IsNotEmpty, IsString } from 'class-validator'; export class UpdateSubspaceModelDto { @ApiProperty({ @@ -21,13 +13,4 @@ export class UpdateSubspaceModelDto { @IsNotEmpty() @IsString() subspaceUuid: string; - - @ApiProperty({ - description: 'Products models modified in the model', - type: ProductModelModificationDto, - }) - @IsOptional() - @ValidateNested({ each: true }) - @Type(() => ProductModelModificationDto) - updatedProductModels?: ProductModelModificationDto; } From 4b55c4e39cb755d2dc8a17fd8e084857b46aa407 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 23 Dec 2024 08:53:01 +0400 Subject: [PATCH 108/247] service clean up --- src/space-model/common/index.ts | 1 - .../base-product-item-model.service.ts | 79 ------------------- .../services/base-product-model.service.ts | 10 --- src/space-model/common/services/index.ts | 2 - .../dtos/create-space-model.dto.ts | 10 +++ src/space-model/dtos/index.ts | 2 - .../create-subspace-model.dto.ts | 13 ++- .../tag-model-dtos/create-tag-model.dto.ts | 20 +++++ src/space-model/dtos/tag-model-dtos/index.ts | 0 9 files changed, 42 insertions(+), 95 deletions(-) delete mode 100644 src/space-model/common/index.ts delete mode 100644 src/space-model/common/services/base-product-item-model.service.ts delete mode 100644 src/space-model/common/services/base-product-model.service.ts delete mode 100644 src/space-model/common/services/index.ts create mode 100644 src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts create mode 100644 src/space-model/dtos/tag-model-dtos/index.ts diff --git a/src/space-model/common/index.ts b/src/space-model/common/index.ts deleted file mode 100644 index e371345..0000000 --- a/src/space-model/common/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './services'; diff --git a/src/space-model/common/services/base-product-item-model.service.ts b/src/space-model/common/services/base-product-item-model.service.ts deleted file mode 100644 index 4d67e6d..0000000 --- a/src/space-model/common/services/base-product-item-model.service.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { HttpException, HttpStatus } from '@nestjs/common'; -import { QueryRunner } from 'typeorm'; -import { SpaceModelEntity } from '@app/common/modules/space-model'; -import { CreateProductItemModelDto } from 'src/space-model/dtos'; - -export abstract class BaseProductItemService { - async validateTags( - itemModelDtos: CreateProductItemModelDto[], - queryRunner: QueryRunner, - spaceModel: SpaceModelEntity, - ): Promise { - const incomingTags = new Set( - itemModelDtos.map((item) => item.tag).filter(Boolean), - ); - - const duplicateTags = itemModelDtos - .map((item) => item.tag) - .filter((tag, index, array) => array.indexOf(tag) !== index); - - if (duplicateTags.length > 0) { - throw new HttpException( - `Duplicate tags found in the request: ${[...new Set(duplicateTags)].join(', ')}`, - HttpStatus.BAD_REQUEST, - ); - } - - const existingTagsQuery = ` - SELECT DISTINCT tag - FROM ( - SELECT spi.tag - FROM "subspace-product-item-model" spi - INNER JOIN "subspace-product-model" spm ON spi.subspace_product_model_uuid = spm.uuid - INNER JOIN "subspace-model" sm ON spm.subspace_model_uuid = sm.uuid - WHERE sm.space_model_uuid = $1 - UNION - SELECT spi.tag - FROM "space-product-item-model" spi - INNER JOIN "space-product-model" spm ON spi.space_product_model_uuid = spm.uuid - WHERE spm.space_model_uuid = $1 - ) AS combined_tags; - `; - - const existingTags = await queryRunner.manager.query(existingTagsQuery, [ - spaceModel.uuid, - ]); - const existingTagsSet = new Set( - existingTags.map((row: { tag: string }) => row.tag), - ); - - const conflictingTags = [...incomingTags].filter((tag) => - existingTagsSet.has(tag), - ); - if (conflictingTags.length > 0) { - throw new HttpException( - `Tags already exist in the model: ${conflictingTags.join(', ')}`, - HttpStatus.CONFLICT, - ); - } - } - - protected async saveProductItems( - productItems: T[], - targetRepository: any, - queryRunner: QueryRunner, - ): Promise { - try { - const savedItem = await queryRunner.manager.save( - targetRepository, - productItems, - ); - return savedItem; - } catch (error) { - throw new HttpException( - error.message || 'An error occurred while creating product items.', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } -} diff --git a/src/space-model/common/services/base-product-model.service.ts b/src/space-model/common/services/base-product-model.service.ts deleted file mode 100644 index 6ff8d91..0000000 --- a/src/space-model/common/services/base-product-model.service.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ProductService } from '../../../product/services'; - -export abstract class BaseProductModelService { - constructor(private readonly productService: ProductService) {} - - protected async getProduct(productId: string) { - const product = await this.productService.findOne(productId); - return product.data; - } -} diff --git a/src/space-model/common/services/index.ts b/src/space-model/common/services/index.ts deleted file mode 100644 index d1cc61e..0000000 --- a/src/space-model/common/services/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './base-product-item-model.service'; -export * from './base-product-model.service'; diff --git a/src/space-model/dtos/create-space-model.dto.ts b/src/space-model/dtos/create-space-model.dto.ts index 6ffaf26..0c37779 100644 --- a/src/space-model/dtos/create-space-model.dto.ts +++ b/src/space-model/dtos/create-space-model.dto.ts @@ -2,6 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString, IsArray, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; import { CreateSubspaceModelDto } from './subspaces-model-dtos/create-subspace-model.dto'; +import { CreateTagModelDto } from './tag-model-dtos/create-tag-model.dto'; export class CreateSpaceModelDto { @ApiProperty({ @@ -20,4 +21,13 @@ export class CreateSpaceModelDto { @ValidateNested({ each: true }) @Type(() => CreateSubspaceModelDto) subspaceModels?: CreateSubspaceModelDto[]; + + @ApiProperty({ + description: 'List of tags associated with the space model', + type: [CreateTagModelDto], + }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CreateTagModelDto) + tags?: CreateTagModelDto[]; } diff --git a/src/space-model/dtos/index.ts b/src/space-model/dtos/index.ts index 045d4aa..11c49af 100644 --- a/src/space-model/dtos/index.ts +++ b/src/space-model/dtos/index.ts @@ -1,6 +1,4 @@ export * from './create-space-model.dto'; -export * from './product-item-model-dtos'; -export * from './product-model-dtos'; export * from './project-param.dto'; export * from './update-space-model.dto'; export * from './space-model-param'; diff --git a/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts b/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts index a27ad3b..24eacfb 100644 --- a/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts +++ b/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts @@ -1,5 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsArray, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; +import { CreateTagModelDto } from '../tag-model-dtos/create-tag-model.dto'; +import { Type } from 'class-transformer'; export class CreateSubspaceModelDto { @ApiProperty({ @@ -9,4 +11,13 @@ export class CreateSubspaceModelDto { @IsNotEmpty() @IsString() subspaceName: string; + + @ApiProperty({ + description: 'List of tags associated with the subspace', + type: [CreateTagModelDto], + }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CreateTagModelDto) + tags?: CreateTagModelDto[]; } diff --git a/src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts b/src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts new file mode 100644 index 0000000..65acf2a --- /dev/null +++ b/src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class CreateTagModelDto { + @ApiProperty({ + description: 'Tag associated with the space or subspace', + example: 'Temperature Control', + }) + @IsNotEmpty() + @IsString() + tag: string; + + @ApiProperty({ + description: 'ID of the product associated with the tag', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @IsNotEmpty() + @IsString() + productUuid: string; +} diff --git a/src/space-model/dtos/tag-model-dtos/index.ts b/src/space-model/dtos/tag-model-dtos/index.ts new file mode 100644 index 0000000..e69de29 From 5cae090b9aac599882eb6e999a1720d63fcff96c Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 23 Dec 2024 00:00:19 -0600 Subject: [PATCH 109/247] fixed null issue --- libs/common/src/modules/space/entities/space-product.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/modules/space/entities/space-product.entity.ts b/libs/common/src/modules/space/entities/space-product.entity.ts index 5f8a062..a1d27ef 100644 --- a/libs/common/src/modules/space/entities/space-product.entity.ts +++ b/libs/common/src/modules/space/entities/space-product.entity.ts @@ -22,7 +22,7 @@ export class SpaceProductEntity extends AbstractEntity { product: ProductEntity; @Column({ - nullable: false, + nullable: true, type: 'int', }) productCount: number; From 39fd6e9dd92f3eb97ddd379002ec0f4b69a1a5f6 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 23 Dec 2024 00:01:11 -0600 Subject: [PATCH 110/247] Add project relationship to InviteUserEntity and invitedUsers to ProjectEntity --- .../modules/Invite-user/entities/Invite-user.entity.ts | 8 ++++++++ .../common/src/modules/project/entities/project.entity.ts | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts index e2414f5..c93f94b 100644 --- a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts +++ b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts @@ -15,6 +15,7 @@ import { UserEntity } from '../../user/entities'; import { SpaceEntity } from '../../space/entities'; import { RoleType } from '@app/common/constants/role.type.enum'; import { InviteUserDto, InviteUserSpaceDto } from '../dtos'; +import { ProjectEntity } from '../../project/entities'; @Entity({ name: 'invite-user' }) @Unique(['email', 'invitationCode']) @@ -91,6 +92,13 @@ export class InviteUserEntity extends AbstractEntity { (inviteUserSpace) => inviteUserSpace.inviteUser, ) spaces: InviteUserSpaceEntity[]; + + @ManyToOne(() => ProjectEntity, (project) => project.invitedUsers, { + nullable: true, + }) + @JoinColumn({ name: 'project_uuid' }) + public project: ProjectEntity; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/project/entities/project.entity.ts b/libs/common/src/modules/project/entities/project.entity.ts index 01fba17..ee6a2c5 100644 --- a/libs/common/src/modules/project/entities/project.entity.ts +++ b/libs/common/src/modules/project/entities/project.entity.ts @@ -4,6 +4,7 @@ import { ProjectDto } from '../dtos'; import { CommunityEntity } from '../../community/entities'; import { SpaceModelEntity } from '../../space-model'; import { UserEntity } from '../../user/entities'; +import { InviteUserEntity } from '../../Invite-user/entities'; @Entity({ name: 'project' }) @Unique(['name']) @@ -32,6 +33,9 @@ export class ProjectEntity extends AbstractEntity { @OneToMany(() => UserEntity, (user) => user.project) public users: UserEntity[]; + @OneToMany(() => InviteUserEntity, (inviteUser) => inviteUser.project) + public invitedUsers: InviteUserEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); From 864884933e2568a5984f11a61b422ac4ec911ef8 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 23 Dec 2024 00:01:36 -0600 Subject: [PATCH 111/247] Add endpoint to check email and project existence --- libs/common/src/constants/controller-route.ts | 4 ++ .../controllers/invite-user.controller.ts | 17 +++++++ src/invite-user/dtos/add.invite-user.dto.ts | 8 ++++ src/invite-user/dtos/check-email.dto.ts | 16 +++++++ .../services/invite-user.service.ts | 47 +++++++++++++++++++ 5 files changed, 92 insertions(+) create mode 100644 src/invite-user/dtos/check-email.dto.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index f6cbfe1..7ef8808 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -733,6 +733,10 @@ export class ControllerRoute { public static readonly CREATE_USER_INVITATION_DESCRIPTION = 'This endpoint creates an invitation for a user to assign to role and spaces.'; + public static readonly CHECK_EMAIL_SUMMARY = 'Check email'; + + public static readonly CHECK_EMAIL_DESCRIPTION = + 'This endpoint checks if an email already exists and have a project in the system.'; }; }; static PERMISSION = class { diff --git a/src/invite-user/controllers/invite-user.controller.ts b/src/invite-user/controllers/invite-user.controller.ts index 756ab68..4351345 100644 --- a/src/invite-user/controllers/invite-user.controller.ts +++ b/src/invite-user/controllers/invite-user.controller.ts @@ -6,6 +6,8 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { PermissionsGuard } from 'src/guards/permissions.guard'; import { Permissions } from 'src/decorators/permissions.decorator'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { CheckEmailDto } from '../dtos/check-email.dto'; @ApiTags('Invite User Module') @Controller({ @@ -34,4 +36,19 @@ export class InviteUserController { user.role.type, ); } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('check-email') + @ApiOperation({ + summary: ControllerRoute.INVITE_USER.ACTIONS.CHECK_EMAIL_SUMMARY, + description: ControllerRoute.INVITE_USER.ACTIONS.CHECK_EMAIL_DESCRIPTION, + }) + async checkEmailAndProject( + @Body() addUserInvitationDto: CheckEmailDto, + ): Promise { + return await this.inviteUserService.checkEmailAndProject( + addUserInvitationDto, + ); + } } diff --git a/src/invite-user/dtos/add.invite-user.dto.ts b/src/invite-user/dtos/add.invite-user.dto.ts index e47758d..94f2e8a 100644 --- a/src/invite-user/dtos/add.invite-user.dto.ts +++ b/src/invite-user/dtos/add.invite-user.dto.ts @@ -61,6 +61,14 @@ export class AddUserInvitationDto { @IsString() @IsNotEmpty() public roleUuid: string; + @ApiProperty({ + description: 'The project uuid of the user', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + required: true, + }) + @IsString() + @IsNotEmpty() + public projectUuid: string; @ApiProperty({ description: 'The array of space UUIDs (at least one required)', example: ['b5f3c9d2-58b7-4377-b3f7-60acb711d5d9'], diff --git a/src/invite-user/dtos/check-email.dto.ts b/src/invite-user/dtos/check-email.dto.ts new file mode 100644 index 0000000..79bcd70 --- /dev/null +++ b/src/invite-user/dtos/check-email.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEmail, IsNotEmpty } from 'class-validator'; + +export class CheckEmailDto { + @ApiProperty({ + description: 'The email of the user', + example: 'OqM9A@example.com', + required: true, + }) + @IsEmail() + @IsNotEmpty() + email: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index b63b3da..2180d0b 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -12,11 +12,14 @@ import { InviteUserRepository, InviteUserSpaceRepository, } from '@app/common/modules/Invite-user/repositiories'; +import { CheckEmailDto } from '../dtos/check-email.dto'; +import { UserRepository } from '@app/common/modules/user/repositories'; @Injectable() export class InviteUserService { constructor( private readonly inviteUserRepository: InviteUserRepository, + private readonly userRepository: UserRepository, private readonly inviteUserSpaceRepository: InviteUserSpaceRepository, private readonly dataSource: DataSource, ) {} @@ -33,6 +36,7 @@ export class InviteUserService { phoneNumber, roleUuid, spaceUuids, + projectUuid, } = dto; const invitationCode = generateRandomString(6); @@ -67,6 +71,7 @@ export class InviteUserService { status: UserStatusEnum.INVITED, invitationCode, invitedBy: roleType, + project: { uuid: projectUuid }, }); const invitedUser = await queryRunner.manager.save(inviteUser); @@ -105,4 +110,46 @@ export class InviteUserService { await queryRunner.release(); } } + async checkEmailAndProject(dto: CheckEmailDto): Promise { + const { email } = dto; + + try { + const user = await this.userRepository.findOne({ + where: { email }, + relations: ['project'], + }); + + if (user?.project) { + throw new HttpException( + 'This email already has a project', + HttpStatus.BAD_REQUEST, + ); + } + + const invitedUser = await this.inviteUserRepository.findOne({ + where: { email }, + relations: ['project'], + }); + + if (invitedUser?.project) { + throw new HttpException( + 'This email already has a project', + HttpStatus.BAD_REQUEST, + ); + } + + return new SuccessResponseDto({ + statusCode: HttpStatus.OK, + success: true, + message: 'Valid email', + }); + } catch (error) { + console.error('Error checking email and project:', error); + throw new HttpException( + error.message || + 'An unexpected error occurred while checking the email', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } From 67742d018f00582d132e23da9fb15686115edbb5 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 23 Dec 2024 01:37:37 -0600 Subject: [PATCH 112/247] Add method to find role by type --- src/role/services/role.service.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/role/services/role.service.ts b/src/role/services/role.service.ts index 5f75571..307f8a5 100644 --- a/src/role/services/role.service.ts +++ b/src/role/services/role.service.ts @@ -13,4 +13,12 @@ export class RoleService { ); return roles; } + async findRoleByType(roleType: RoleType) { + const role = await this.roleTypeRepository.findOne({ + where: { + type: roleType, + }, + }); + return role; + } } From 53a474d5cf198266c7b9be553a675dd94ba213ce Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 23 Dec 2024 01:37:46 -0600 Subject: [PATCH 113/247] Add RoleService to AuthModule and UserAuthService --- src/auth/auth.module.ts | 2 ++ src/auth/services/user-auth.service.ts | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 4af4688..8aaef36 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -8,6 +8,7 @@ import { UserRepository } from '@app/common/modules/user/repositories'; import { UserSessionRepository } from '@app/common/modules/session/repositories/session.repository'; import { UserOtpRepository } from '@app/common/modules/user/repositories'; import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; +import { RoleService } from 'src/role/services'; @Module({ imports: [ConfigModule, UserRepositoryModule, CommonModule], @@ -18,6 +19,7 @@ import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; UserSessionRepository, UserOtpRepository, RoleTypeRepository, + RoleService, ], exports: [UserAuthService], }) diff --git a/src/auth/services/user-auth.service.ts b/src/auth/services/user-auth.service.ts index b9b3ed3..89eda76 100644 --- a/src/auth/services/user-auth.service.ts +++ b/src/auth/services/user-auth.service.ts @@ -18,6 +18,8 @@ import * as argon2 from 'argon2'; import { differenceInSeconds } from '@app/common/helper/differenceInSeconds'; import { LessThan, MoreThan } from 'typeorm'; import { ConfigService } from '@nestjs/config'; +import { RoleService } from 'src/role/services'; +import { RoleType } from '@app/common/constants/role.type.enum'; @Injectable() export class UserAuthService { @@ -28,6 +30,7 @@ export class UserAuthService { private readonly helperHashService: HelperHashService, private readonly authService: AuthService, private readonly emailService: EmailService, + private readonly roleService: RoleService, private readonly configService: ConfigService, ) {} @@ -44,9 +47,13 @@ export class UserAuthService { try { const { regionUuid, ...rest } = userSignUpDto; + const spaceMemberRole = await this.roleService.findRoleByType( + RoleType.SPACE_MEMBER, + ); const user = await this.userRepository.save({ ...rest, password: hashedPassword, + roleType: { uuid: spaceMemberRole.uuid }, region: regionUuid ? { uuid: regionUuid, From a4740e8bbdc791610a0b23d0076cbe39b72f7fcd Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 23 Dec 2024 12:25:24 +0400 Subject: [PATCH 114/247] updated create space model --- src/space-model/dtos/index.ts | 1 + src/space-model/dtos/tag-model-dtos/index.ts | 2 + .../tag-model-dtos/update-tag-model.dto.ts | 21 +++ src/space-model/services/index.ts | 1 + .../services/space-model.service.ts | 48 ++++--- .../subspace/subspace-model.service.ts | 45 +++--- src/space-model/services/tag-model.service.ts | 128 ++++++++++++++++++ src/space-model/space-model.module.ts | 9 +- 8 files changed, 216 insertions(+), 39 deletions(-) create mode 100644 src/space-model/dtos/tag-model-dtos/update-tag-model.dto.ts create mode 100644 src/space-model/services/tag-model.service.ts diff --git a/src/space-model/dtos/index.ts b/src/space-model/dtos/index.ts index 11c49af..3a04fe1 100644 --- a/src/space-model/dtos/index.ts +++ b/src/space-model/dtos/index.ts @@ -3,3 +3,4 @@ export * from './project-param.dto'; export * from './update-space-model.dto'; export * from './space-model-param'; export * from './subspaces-model-dtos'; +export * from './tag-model-dtos'; diff --git a/src/space-model/dtos/tag-model-dtos/index.ts b/src/space-model/dtos/tag-model-dtos/index.ts index e69de29..75c60f8 100644 --- a/src/space-model/dtos/tag-model-dtos/index.ts +++ b/src/space-model/dtos/tag-model-dtos/index.ts @@ -0,0 +1,2 @@ +export * from './create-tag-model.dto'; +export * from './update-tag-model.dto'; diff --git a/src/space-model/dtos/tag-model-dtos/update-tag-model.dto.ts b/src/space-model/dtos/tag-model-dtos/update-tag-model.dto.ts new file mode 100644 index 0000000..ca5612f --- /dev/null +++ b/src/space-model/dtos/tag-model-dtos/update-tag-model.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; + +export class UpdateTagModelDto { + @ApiProperty({ + description: 'UUID of the tag to be updated', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @IsNotEmpty() + @IsUUID() + uuid: string; + + @ApiProperty({ + description: 'Updated name of the tag', + example: 'Updated Tag Name', + required: false, + }) + @IsOptional() + @IsString() + tag?: string; +} diff --git a/src/space-model/services/index.ts b/src/space-model/services/index.ts index 9999493..20dca88 100644 --- a/src/space-model/services/index.ts +++ b/src/space-model/services/index.ts @@ -1,2 +1,3 @@ export * from './space-model.service'; export * from './subspace'; +export * from './tag-model.service'; diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 92301ad..3227f9b 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -17,6 +17,8 @@ import { SpaceModelDto } from '@app/common/modules/space-model/dtos'; import { SpaceModelParam } from '../dtos/space-model-param'; import { ProjectService } from 'src/project/services'; import { ProjectEntity } from '@app/common/modules/project/entities'; +import { TagModelService } from './tag-model.service'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; @Injectable() export class SpaceModelService { @@ -25,21 +27,21 @@ export class SpaceModelService { private readonly spaceModelRepository: SpaceModelRepository, private readonly projectService: ProjectService, private readonly subSpaceModelService: SubSpaceModelService, + private readonly tagModelService: TagModelService, ) {} async createSpaceModel( createSpaceModelDto: CreateSpaceModelDto, params: ProjectParam, - ) { - const { modelName, subspaceModels } = createSpaceModelDto; - const project = await this.validateProject(params.projectUuid); - + ): Promise { + const { modelName, subspaceModels, tags } = createSpaceModelDto; const queryRunner = this.dataSource.createQueryRunner(); - await queryRunner.connect(); await queryRunner.startTransaction(); try { + const project = await this.validateProject(params.projectUuid); + const isModelExist = await this.validateName( modelName, params.projectUuid, @@ -55,13 +57,25 @@ export class SpaceModelService { modelName, project, }); + const savedSpaceModel = await queryRunner.manager.save(spaceModel); - if (subspaceModels) { - await this.subSpaceModelService.createSubSpaceModels( - subspaceModels, - savedSpaceModel, + if (subspaceModels?.length) { + savedSpaceModel.subspaceModels = + await this.subSpaceModelService.createSubSpaceModels( + subspaceModels, + savedSpaceModel, + queryRunner, + tags, + ); + } + + if (tags?.length) { + savedSpaceModel.tags = await this.tagModelService.createTags( + tags, queryRunner, + savedSpaceModel, + null, ); } @@ -75,14 +89,16 @@ export class SpaceModelService { } catch (error) { await queryRunner.rollbackTransaction(); - if (error instanceof HttpException) { - throw error; - } + const errorMessage = + error instanceof HttpException + ? error.message + : 'An unexpected error occurred'; + const statusCode = + error instanceof HttpException + ? error.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; - throw new HttpException( - error.message || `An unexpected error occurred`, - HttpStatus.INTERNAL_SERVER_ERROR, - ); + throw new HttpException(errorMessage, statusCode); } finally { await queryRunner.release(); } diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index fad3ee2..6a719c0 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -1,5 +1,6 @@ import { SpaceModelEntity, + SubspaceModelEntity, SubspaceModelRepository, } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; @@ -7,6 +8,7 @@ import { CreateSubspaceModelDto, UpdateSubspaceModelDto, ModifySubspacesModelDto, + CreateTagModelDto, } from '../../dtos'; import { QueryRunner } from 'typeorm'; import { @@ -15,47 +17,53 @@ import { IUpdateSubspaceModelInterface, } from 'src/space-model/interfaces'; import { DeleteSubspaceModelDto } from 'src/space-model/dtos/subspaces-model-dtos'; +import { TagModelService } from '../tag-model.service'; @Injectable() export class SubSpaceModelService { constructor( private readonly subspaceModelRepository: SubspaceModelRepository, + private readonly tagModelService: TagModelService, ) {} - async createSubSpaceModels( subSpaceModelDtos: CreateSubspaceModelDto[], spaceModel: SpaceModelEntity, queryRunner: QueryRunner, - ) { + otherTags: CreateTagModelDto[], + ): Promise { this.validateInputDtos(subSpaceModelDtos); try { const subspaces = subSpaceModelDtos.map((subspaceDto) => queryRunner.manager.create(this.subspaceModelRepository.target, { subspaceName: subspaceDto.subspaceName, - spaceModel: spaceModel, + spaceModel, }), ); - const newSubspaces = await queryRunner.manager.save(subspaces); + const savedSubspaces = await queryRunner.manager.save(subspaces); - const addedSubspaces = await Promise.all( + await Promise.all( subSpaceModelDtos.map(async (dto, index) => { - const subspaceModel = newSubspaces[index]; + const subspace = savedSubspaces[index]; - return { - subspaceModel, - }; + if (dto.tags && dto.tags.length > 0) { + const tagModels = await this.tagModelService.createTags( + dto.tags, + queryRunner, + null, + subspace, + otherTags, + ); + subspace.tags = tagModels; + } }), ); - return addedSubspaces; - } catch (error) { - if (error instanceof HttpException) { - throw error; - } + return savedSubspaces; + } catch (error) { throw new HttpException( - error.message || `An unexpected error occurred`, + error.message || 'Failed to create subspaces.', HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -230,13 +238,6 @@ export class SubSpaceModelService { const actions = []; if (dto.add) { - actions.push( - this.createSubSpaceModels(dto.add, spaceModel, queryRunner).then( - (addedSubspaces) => { - subspaces.new = addedSubspaces; - }, - ), - ); } if (dto.update) { diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts new file mode 100644 index 0000000..6a057cf --- /dev/null +++ b/src/space-model/services/tag-model.service.ts @@ -0,0 +1,128 @@ +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { QueryRunner } from 'typeorm'; +import { + SpaceModelEntity, + TagModel, +} from '@app/common/modules/space-model/entities'; +import { SubspaceModelEntity } from '@app/common/modules/space-model/entities'; +import { TagModelRepository } from '@app/common/modules/space-model'; +import { CreateTagModelDto, UpdateTagModelDto } from '../dtos'; +import { ProductService } from 'src/product/services'; + +@Injectable() +export class TagModelService { + constructor( + private readonly tagModelRepository: TagModelRepository, + private readonly productService: ProductService, + ) {} + + async createTags( + tags: CreateTagModelDto[], + queryRunner: QueryRunner, + spaceModel?: SpaceModelEntity, + subspaceModel?: SubspaceModelEntity, + otherTags?: CreateTagModelDto[], + ): Promise { + let alltags: CreateTagModelDto[] = []; + if (!tags.length) { + throw new HttpException('Tags cannot be empty.', HttpStatus.BAD_REQUEST); + } + if (otherTags) { + alltags = [...tags, ...otherTags]; + } + const duplicates = this.checkForDuplicates(alltags); + if (duplicates.length > 0) { + throw new HttpException( + `Duplicate tags found for the same product: ${duplicates.join(', ')}`, + HttpStatus.BAD_REQUEST, + ); + } + const tagEntities = await Promise.all( + tags.map(async (tagDto) => { + const product = await this.productService.findOne(tagDto.productUuid); + if (!product) { + throw new HttpException( + `Product with UUID ${tagDto.productUuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + + return queryRunner.manager.create(TagModel, { + tag: tagDto.tag, + product: product.data, + spaceModel, + subspaceModel, + }); + }), + ); + + try { + return await queryRunner.manager.save(tagEntities); + } catch (error) { + throw new HttpException( + 'Failed to save tag models due to an unexpected error.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async updateTags(tags: UpdateTagModelDto[], queryRunner: QueryRunner) { + try { + const updatePromises = tags.map(async (tagDto) => { + const existingTag = await this.tagModelRepository.findOne({ + where: { uuid: tagDto.uuid }, + }); + + if (!existingTag) { + throw new HttpException( + `Tag with ID ${tagDto.uuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + existingTag.tag = tagDto.tag || existingTag.tag; + + return queryRunner.manager.save(existingTag); + }); + + return await Promise.all(updatePromises); + } catch (error) { + throw new HttpException( + error.message || 'Failed to update tags', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async deleteTags(tagUuids: string[], queryRunner: QueryRunner) { + try { + const deletePromises = tagUuids.map((id) => + queryRunner.manager.softDelete(this.tagModelRepository.target, id), + ); + + await Promise.all(deletePromises); + return { message: 'Tags deleted successfully', tagUuids }; + } catch (error) { + throw new HttpException( + error.message || 'Failed to delete tags', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private checkForDuplicates(tags: CreateTagModelDto[]): string[] { + const seen = new Map(); + const duplicates: string[] = []; + + tags.forEach((tagDto) => { + const key = `${tagDto.productUuid}-${tagDto.tag}`; + if (seen.has(key)) { + duplicates.push(`${tagDto.tag} for Product: ${tagDto.productUuid}`); + } else { + seen.set(key, true); + } + }); + + return duplicates; + } +} diff --git a/src/space-model/space-model.module.ts b/src/space-model/space-model.module.ts index 2140d9c..09cc8dd 100644 --- a/src/space-model/space-model.module.ts +++ b/src/space-model/space-model.module.ts @@ -2,10 +2,15 @@ import { SpaceRepositoryModule } from '@app/common/modules/space/space.repositor import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { SpaceModelController } from './controllers'; -import { SpaceModelService, SubSpaceModelService } from './services'; +import { + SpaceModelService, + SubSpaceModelService, + TagModelService, +} from './services'; import { SpaceModelRepository, SubspaceModelRepository, + TagModelRepository, } from '@app/common/modules/space-model'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProductRepository } from '@app/common/modules/product/repositories'; @@ -35,6 +40,8 @@ const CommandHandlers = [PropogateSubspaceHandler]; SubspaceRepository, SubspaceProductRepository, SubspaceProductItemRepository, + TagModelService, + TagModelRepository, ], exports: [CqrsModule], }) From b0eec7c38ee3ca7848bdc568d7c28abc1db84f65 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 23 Dec 2024 20:34:44 +0400 Subject: [PATCH 115/247] added tag action --- .../src/constants/modify-action.enum.ts | 5 + .../space-model/entities/tag-model.entity.ts | 6 + .../dtos/subspaces-model-dtos/index.ts | 1 + .../modify-subspace-model.dto.ts | 40 ++++ src/space-model/dtos/tag-model-dtos/index.ts | 1 + .../tag-model-dtos/modify-tag-model.dto.ts | 37 ++++ .../dtos/update-space-model.dto.ts | 24 ++- .../services/space-model.service.ts | 32 ++- .../subspace/subspace-model.service.ts | 84 ++++---- src/space-model/services/tag-model.service.ts | 201 ++++++++++++++---- 10 files changed, 332 insertions(+), 99 deletions(-) create mode 100644 libs/common/src/constants/modify-action.enum.ts create mode 100644 src/space-model/dtos/subspaces-model-dtos/modify-subspace-model.dto.ts create mode 100644 src/space-model/dtos/tag-model-dtos/modify-tag-model.dto.ts diff --git a/libs/common/src/constants/modify-action.enum.ts b/libs/common/src/constants/modify-action.enum.ts new file mode 100644 index 0000000..28d6f77 --- /dev/null +++ b/libs/common/src/constants/modify-action.enum.ts @@ -0,0 +1,5 @@ +export enum ModifyAction { + ADD = 'add', + UPDATE = 'update', + DELETE = 'delete', +} diff --git a/libs/common/src/modules/space-model/entities/tag-model.entity.ts b/libs/common/src/modules/space-model/entities/tag-model.entity.ts index 499ec58..2ff86d4 100644 --- a/libs/common/src/modules/space-model/entities/tag-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/tag-model.entity.ts @@ -26,4 +26,10 @@ export class TagModel extends AbstractEntity { }) @JoinColumn({ name: 'subspace_id' }) subspaceModel: SubspaceModelEntity; + + @Column({ + nullable: false, + default: false, + }) + public disabled: boolean; } diff --git a/src/space-model/dtos/subspaces-model-dtos/index.ts b/src/space-model/dtos/subspaces-model-dtos/index.ts index 698a520..28b01ef 100644 --- a/src/space-model/dtos/subspaces-model-dtos/index.ts +++ b/src/space-model/dtos/subspaces-model-dtos/index.ts @@ -1,3 +1,4 @@ export * from './delete-subspace-model.dto'; export * from './create-subspace-model.dto'; export * from './update-subspace-model.dto'; +export * from './modify-subspace-model.dto'; diff --git a/src/space-model/dtos/subspaces-model-dtos/modify-subspace-model.dto.ts b/src/space-model/dtos/subspaces-model-dtos/modify-subspace-model.dto.ts new file mode 100644 index 0000000..9b5a2a5 --- /dev/null +++ b/src/space-model/dtos/subspaces-model-dtos/modify-subspace-model.dto.ts @@ -0,0 +1,40 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsString, IsOptional, IsArray, ValidateNested } from 'class-validator'; +import { ModifyTagModelDto } from '../tag-model-dtos'; + +export class ModifySubspaceModelDto { + @ApiProperty({ + description: 'Action to perform: add, update, or delete', + example: 'add', + }) + @IsString() + action: 'add' | 'update' | 'delete'; + + @ApiPropertyOptional({ + description: 'UUID of the subspace (required for update/delete)', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @IsOptional() + @IsString() + uuid?: string; + + @ApiPropertyOptional({ + description: 'Name of the subspace (required for add/update)', + example: 'Living Room', + }) + @IsOptional() + @IsString() + subspaceName?: string; + + @ApiPropertyOptional({ + description: + 'List of tag modifications (add/update/delete) for the subspace', + type: [ModifyTagModelDto], + }) + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => ModifyTagModelDto) + tags?: ModifyTagModelDto[]; +} diff --git a/src/space-model/dtos/tag-model-dtos/index.ts b/src/space-model/dtos/tag-model-dtos/index.ts index 75c60f8..a0f136d 100644 --- a/src/space-model/dtos/tag-model-dtos/index.ts +++ b/src/space-model/dtos/tag-model-dtos/index.ts @@ -1,2 +1,3 @@ export * from './create-tag-model.dto'; export * from './update-tag-model.dto'; +export * from './modify-tag-model.dto'; diff --git a/src/space-model/dtos/tag-model-dtos/modify-tag-model.dto.ts b/src/space-model/dtos/tag-model-dtos/modify-tag-model.dto.ts new file mode 100644 index 0000000..e3d390d --- /dev/null +++ b/src/space-model/dtos/tag-model-dtos/modify-tag-model.dto.ts @@ -0,0 +1,37 @@ +import { ModifyAction } from '@app/common/constants/modify-action.enum'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsString, IsOptional, IsEnum } from 'class-validator'; + +export class ModifyTagModelDto { + @ApiProperty({ + description: 'Action to perform: add, update, or delete', + example: ModifyAction.ADD, + }) + @IsEnum(ModifyAction) + action: ModifyAction; + + @ApiPropertyOptional({ + description: 'UUID of the tag (required for update/delete)', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @IsOptional() + @IsString() + uuid?: string; + + @ApiPropertyOptional({ + description: 'Name of the tag (required for add/update)', + example: 'Temperature Sensor', + }) + @IsOptional() + @IsString() + tag?: string; + + @ApiPropertyOptional({ + description: + 'UUID of the product associated with the tag (required for add)', + example: 'c789a91e-549a-4753-9006-02f89e8170e0', + }) + @IsOptional() + @IsString() + productUuid?: string; +} diff --git a/src/space-model/dtos/update-space-model.dto.ts b/src/space-model/dtos/update-space-model.dto.ts index 7a7051b..d1110ea 100644 --- a/src/space-model/dtos/update-space-model.dto.ts +++ b/src/space-model/dtos/update-space-model.dto.ts @@ -1,11 +1,13 @@ -import { ApiProperty } from '@nestjs/swagger'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsArray, IsOptional, IsString, ValidateNested } from 'class-validator'; import { CreateSubspaceModelDto } from './subspaces-model-dtos/create-subspace-model.dto'; import { Type } from 'class-transformer'; import { DeleteSubspaceModelDto, + ModifySubspaceModelDto, UpdateSubspaceModelDto, } from './subspaces-model-dtos'; +import { ModifyTagModelDto } from './tag-model-dtos'; export class ModifySubspacesModelDto { @ApiProperty({ @@ -51,6 +53,24 @@ export class UpdateSpaceModelDto { @IsString() modelName?: string; + @ApiPropertyOptional({ + description: 'List of subspace modifications (add/update/delete)', + type: [ModifySubspaceModelDto], + }) @IsOptional() - subspaceModels?: ModifySubspacesModelDto; + @IsArray() + @ValidateNested({ each: true }) + @Type(() => ModifySubspaceModelDto) + subspaceModels?: ModifySubspaceModelDto[]; + + @ApiPropertyOptional({ + description: + 'List of tag modifications (add/update/delete) for the space model', + type: [ModifyTagModelDto], + }) + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => ModifyTagModelDto) + tags?: ModifyTagModelDto[]; } diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 3227f9b..8e08ae3 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -42,16 +42,7 @@ export class SpaceModelService { try { const project = await this.validateProject(params.projectUuid); - const isModelExist = await this.validateName( - modelName, - params.projectUuid, - ); - if (isModelExist) { - throw new HttpException( - `Model name "${modelName}" already exists in this project ${params.projectUuid}.`, - HttpStatus.CONFLICT, - ); - } + await this.validateName(modelName, params.projectUuid); const spaceModel = this.spaceModelRepository.create({ modelName, @@ -148,9 +139,11 @@ export class SpaceModelService { await queryRunner.startTransaction(); try { const { modelName } = dto; - if (modelName) spaceModel.modelName = modelName; - - await queryRunner.manager.save(spaceModel); + if (modelName) { + await this.validateName(modelName, param.projectUuid); + spaceModel.modelName = modelName; + await queryRunner.manager.save(spaceModel); + } if (dto.subspaceModels) { await this.subSpaceModelService.modifySubSpaceModels( @@ -159,7 +152,6 @@ export class SpaceModelService { queryRunner, ); } - await queryRunner.commitTransaction(); return new SuccessResponseDto({ @@ -177,11 +169,17 @@ export class SpaceModelService { } } - async validateName(modelName: string, projectUuid: string): Promise { - const isModelExist = await this.spaceModelRepository.exists({ + async validateName(modelName: string, projectUuid: string): Promise { + const isModelExist = await this.spaceModelRepository.findOne({ where: { modelName, project: { uuid: projectUuid } }, }); - return isModelExist; + + if (isModelExist) { + throw new HttpException( + `Model name ${modelName} already exists in the project with UUID ${projectUuid}.`, + HttpStatus.CONFLICT, + ); + } } async validateSpaceModel(uuid: string): Promise { diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index 6a719c0..4c57673 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -7,16 +7,17 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { CreateSubspaceModelDto, UpdateSubspaceModelDto, - ModifySubspacesModelDto, CreateTagModelDto, } from '../../dtos'; import { QueryRunner } from 'typeorm'; import { IDeletedSubsaceModelInterface, - IModifySubspaceModelInterface, IUpdateSubspaceModelInterface, } from 'src/space-model/interfaces'; -import { DeleteSubspaceModelDto } from 'src/space-model/dtos/subspaces-model-dtos'; +import { + DeleteSubspaceModelDto, + ModifySubspaceModelDto, +} from 'src/space-model/dtos/subspaces-model-dtos'; import { TagModelService } from '../tag-model.service'; @Injectable() @@ -25,11 +26,12 @@ export class SubSpaceModelService { private readonly subspaceModelRepository: SubspaceModelRepository, private readonly tagModelService: TagModelService, ) {} + async createSubSpaceModels( subSpaceModelDtos: CreateSubspaceModelDto[], spaceModel: SpaceModelEntity, queryRunner: QueryRunner, - otherTags: CreateTagModelDto[], + otherTags?: CreateTagModelDto[], ): Promise { this.validateInputDtos(subSpaceModelDtos); @@ -62,6 +64,9 @@ export class SubSpaceModelService { return savedSubspaces; } catch (error) { + if (error instanceof HttpException) { + throw error; + } throw new HttpException( error.message || 'Failed to create subspaces.', HttpStatus.INTERNAL_SERVER_ERROR, @@ -183,7 +188,6 @@ export class SubSpaceModelService { where: { uuid: subspaceUuid, }, - relations: ['productModels', 'productModels.itemModels'], }); if (!subspace) { throw new HttpException( @@ -226,43 +230,49 @@ export class SubSpaceModelService { } async modifySubSpaceModels( - dto: ModifySubspacesModelDto, + subspaceDtos: ModifySubspaceModelDto[], spaceModel: SpaceModelEntity, queryRunner: QueryRunner, - ): Promise { - const subspaces: IModifySubspaceModelInterface = { - spaceModelUuid: spaceModel.uuid, - }; - + ) { try { - const actions = []; + for (const subspace of subspaceDtos) { + if (subspace.action === 'add') { + const createTagDtos: CreateTagModelDto[] = + subspace.tags?.map((tag) => ({ + tag: tag.tag as string, + productUuid: tag.productUuid as string, + })) || []; + await this.createSubSpaceModels( + [{ subspaceName: subspace.subspaceName, tags: createTagDtos }], + spaceModel, + queryRunner, + ); + } else if (subspace.action === 'update') { + const existingSubspace = await this.findOne(subspace.uuid); - if (dto.add) { + if (!existingSubspace) { + throw new HttpException( + `Subspace with ID ${subspace.uuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + + existingSubspace.subspaceName = + subspace.subspaceName || existingSubspace.subspaceName; + + const updatedSubspace = + await queryRunner.manager.save(existingSubspace); + + if (subspace.tags) { + await this.tagModelService.modifyTags( + subspace.tags, + queryRunner, + null, + updatedSubspace, + ); + } + } } - - if (dto.update) { - actions.push( - this.updateSubspaceModels(dto.update, spaceModel, queryRunner).then( - (updatedSubspaces) => { - subspaces.update = updatedSubspaces; - }, - ), - ); - } - - if (dto.delete) { - actions.push( - this.deleteSubspaceModels(dto.delete, queryRunner).then( - (deletedSubspaces) => { - subspaces.delete = deletedSubspaces; - }, - ), - ); - } - - await Promise.all(actions); - - return subspaces; } catch (error) { throw new HttpException( error.message || 'Failed to modify SpaceModels', diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index 6a057cf..21be316 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -6,7 +6,7 @@ import { } from '@app/common/modules/space-model/entities'; import { SubspaceModelEntity } from '@app/common/modules/space-model/entities'; import { TagModelRepository } from '@app/common/modules/space-model'; -import { CreateTagModelDto, UpdateTagModelDto } from '../dtos'; +import { CreateTagModelDto, ModifyTagModelDto } from '../dtos'; import { ProductService } from 'src/product/services'; @Injectable() @@ -21,44 +21,34 @@ export class TagModelService { queryRunner: QueryRunner, spaceModel?: SpaceModelEntity, subspaceModel?: SubspaceModelEntity, - otherTags?: CreateTagModelDto[], + additionalTags?: CreateTagModelDto[], ): Promise { - let alltags: CreateTagModelDto[] = []; if (!tags.length) { throw new HttpException('Tags cannot be empty.', HttpStatus.BAD_REQUEST); } - if (otherTags) { - alltags = [...tags, ...otherTags]; - } - const duplicates = this.checkForDuplicates(alltags); - if (duplicates.length > 0) { + + const combinedTags = additionalTags ? [...tags, ...additionalTags] : tags; + const duplicateTags = this.findDuplicateTags(combinedTags); + + if (duplicateTags.length > 0) { throw new HttpException( - `Duplicate tags found for the same product: ${duplicates.join(', ')}`, + `Duplicate tags found for the same product: ${duplicateTags.join(', ')}`, HttpStatus.BAD_REQUEST, ); } + const tagEntities = await Promise.all( - tags.map(async (tagDto) => { - const product = await this.productService.findOne(tagDto.productUuid); - if (!product) { - throw new HttpException( - `Product with UUID ${tagDto.productUuid} not found.`, - HttpStatus.NOT_FOUND, - ); - } - - return queryRunner.manager.create(TagModel, { - tag: tagDto.tag, - product: product.data, - spaceModel, - subspaceModel, - }); - }), + tags.map(async (tagDto) => + this.prepareTagEntity(tagDto, queryRunner, spaceModel, subspaceModel), + ), ); - try { return await queryRunner.manager.save(tagEntities); } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( 'Failed to save tag models due to an unexpected error.', HttpStatus.INTERNAL_SERVER_ERROR, @@ -66,27 +56,34 @@ export class TagModelService { } } - async updateTags(tags: UpdateTagModelDto[], queryRunner: QueryRunner) { + async updateTag( + tag: ModifyTagModelDto, + queryRunner: QueryRunner, + spaceModel?: SpaceModelEntity, + subspaceModel?: SubspaceModelEntity, + ): Promise { try { - const updatePromises = tags.map(async (tagDto) => { - const existingTag = await this.tagModelRepository.findOne({ - where: { uuid: tagDto.uuid }, - }); + const existingTag = await this.getTagByUuid(tag.uuid); - if (!existingTag) { - throw new HttpException( - `Tag with ID ${tagDto.uuid} not found`, - HttpStatus.NOT_FOUND, - ); - } + if (spaceModel) { + await this.checkTagReuse(tag.tag, existingTag.product.uuid, spaceModel); + } else { + await this.checkTagReuse( + tag.tag, + existingTag.product.uuid, + subspaceModel.spaceModel, + ); + } - existingTag.tag = tagDto.tag || existingTag.tag; + if (tag.tag) { + existingTag.tag = tag.tag; + } - return queryRunner.manager.save(existingTag); - }); - - return await Promise.all(updatePromises); + return await queryRunner.manager.save(existingTag); } catch (error) { + if (error instanceof HttpException) { + throw error; + } throw new HttpException( error.message || 'Failed to update tags', HttpStatus.INTERNAL_SERVER_ERROR, @@ -103,6 +100,9 @@ export class TagModelService { await Promise.all(deletePromises); return { message: 'Tags deleted successfully', tagUuids }; } catch (error) { + if (error instanceof HttpException) { + throw error; + } throw new HttpException( error.message || 'Failed to delete tags', HttpStatus.INTERNAL_SERVER_ERROR, @@ -110,7 +110,7 @@ export class TagModelService { } } - private checkForDuplicates(tags: CreateTagModelDto[]): string[] { + private findDuplicateTags(tags: CreateTagModelDto[]): string[] { const seen = new Map(); const duplicates: string[] = []; @@ -125,4 +125,119 @@ export class TagModelService { return duplicates; } + + async modifyTags( + tags: ModifyTagModelDto[], + queryRunner: QueryRunner, + spaceModel?: SpaceModelEntity, + subspaceModel?: SubspaceModelEntity, + ): Promise { + try { + for (const tag of tags) { + if (tag.action === 'add') { + const createTagDto: CreateTagModelDto = { + tag: tag.tag as string, + productUuid: tag.productUuid as string, + }; + + await this.createTags( + [createTagDto], + queryRunner, + spaceModel, + subspaceModel, + ); + } else if (tag.action === 'update') { + await this.updateTag(tag, queryRunner, spaceModel, subspaceModel); + } else if (tag.action === 'delete') { + await queryRunner.manager.update( + this.tagModelRepository.target, + { uuid: tag.uuid }, + { disabled: true }, + ); + } else { + throw new HttpException( + `Invalid action "${tag.action}" provided.`, + HttpStatus.BAD_REQUEST, + ); + } + } + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + `An error occurred while modifying tags: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private async checkTagReuse( + tag: string, + productUuid: string, + spaceModel: SpaceModelEntity, + ): Promise { + const isTagInSpaceModel = await this.tagModelRepository.exists({ + where: { tag, spaceModel, product: { uuid: productUuid } }, + }); + const isTagInSubspaceModel = await this.tagModelRepository.exists({ + where: { + tag, + subspaceModel: { spaceModel }, + product: { uuid: productUuid }, + }, + }); + + if (isTagInSpaceModel || isTagInSubspaceModel) { + throw new HttpException(`Tag can't be reused`, HttpStatus.CONFLICT); + } + } + + private async prepareTagEntity( + tagDto: CreateTagModelDto, + queryRunner: QueryRunner, + spaceModel?: SpaceModelEntity, + subspaceModel?: SubspaceModelEntity, + ): Promise { + const product = await this.productService.findOne(tagDto.productUuid); + + if (!product) { + throw new HttpException( + `Product with UUID ${tagDto.productUuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + + if (spaceModel) { + await this.checkTagReuse(tagDto.tag, tagDto.productUuid, spaceModel); + } else { + await this.checkTagReuse( + tagDto.tag, + tagDto.productUuid, + subspaceModel.spaceModel, + ); + } + + return queryRunner.manager.create(TagModel, { + tag: tagDto.tag, + product: product.data, + spaceModel, + subspaceModel, + }); + } + + private async getTagByUuid(uuid: string): Promise { + const tag = await this.tagModelRepository.findOne({ + where: { uuid }, + relations: ['product'], + }); + if (!tag) { + throw new HttpException( + `Tag with ID ${uuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + return tag; + } } From 41c86b47eb9ef9e3795adcd520e4294c51652d0e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 23 Dec 2024 20:49:48 +0400 Subject: [PATCH 116/247] space model update --- .../modify-subspace-model.dto.ts | 15 +- .../services/space-model.service.ts | 3 +- .../subspace/subspace-model.service.ts | 410 +++++++++--------- src/space-model/services/tag-model.service.ts | 8 +- 4 files changed, 217 insertions(+), 219 deletions(-) diff --git a/src/space-model/dtos/subspaces-model-dtos/modify-subspace-model.dto.ts b/src/space-model/dtos/subspaces-model-dtos/modify-subspace-model.dto.ts index 9b5a2a5..cbf021c 100644 --- a/src/space-model/dtos/subspaces-model-dtos/modify-subspace-model.dto.ts +++ b/src/space-model/dtos/subspaces-model-dtos/modify-subspace-model.dto.ts @@ -1,15 +1,22 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Type } from 'class-transformer'; -import { IsString, IsOptional, IsArray, ValidateNested } from 'class-validator'; +import { + IsString, + IsOptional, + IsArray, + ValidateNested, + IsEnum, +} from 'class-validator'; import { ModifyTagModelDto } from '../tag-model-dtos'; +import { ModifyAction } from '@app/common/constants/modify-action.enum'; export class ModifySubspaceModelDto { @ApiProperty({ description: 'Action to perform: add, update, or delete', - example: 'add', + example: ModifyAction.ADD, }) - @IsString() - action: 'add' | 'update' | 'delete'; + @IsEnum(ModifyAction) + action: ModifyAction; @ApiPropertyOptional({ description: 'UUID of the subspace (required for update/delete)', diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 8e08ae3..23e5d51 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -171,7 +171,7 @@ export class SpaceModelService { async validateName(modelName: string, projectUuid: string): Promise { const isModelExist = await this.spaceModelRepository.findOne({ - where: { modelName, project: { uuid: projectUuid } }, + where: { modelName, project: { uuid: projectUuid }, disabled: false }, }); if (isModelExist) { @@ -186,6 +186,7 @@ export class SpaceModelService { const spaceModel = await this.spaceModelRepository.findOne({ where: { uuid, + disabled: true, }, }); if (!spaceModel) { diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index 4c57673..0f59a0d 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -19,6 +19,7 @@ import { ModifySubspaceModelDto, } from 'src/space-model/dtos/subspaces-model-dtos'; import { TagModelService } from '../tag-model.service'; +import { ModifyAction } from '@app/common/constants/modify-action.enum'; @Injectable() export class SubSpaceModelService { @@ -35,94 +36,223 @@ export class SubSpaceModelService { ): Promise { this.validateInputDtos(subSpaceModelDtos); - try { - const subspaces = subSpaceModelDtos.map((subspaceDto) => - queryRunner.manager.create(this.subspaceModelRepository.target, { - subspaceName: subspaceDto.subspaceName, - spaceModel, - }), - ); + const subspaces = subSpaceModelDtos.map((subspaceDto) => + queryRunner.manager.create(this.subspaceModelRepository.target, { + subspaceName: subspaceDto.subspaceName, + spaceModel, + }), + ); - const savedSubspaces = await queryRunner.manager.save(subspaces); + const savedSubspaces = await queryRunner.manager.save(subspaces); - await Promise.all( - subSpaceModelDtos.map(async (dto, index) => { - const subspace = savedSubspaces[index]; + await Promise.all( + subSpaceModelDtos.map(async (dto, index) => { + const subspace = savedSubspaces[index]; + if (dto.tags?.length) { + subspace.tags = await this.tagModelService.createTags( + dto.tags, + queryRunner, + null, + subspace, + otherTags, + ); + } + }), + ); - if (dto.tags && dto.tags.length > 0) { - const tagModels = await this.tagModelService.createTags( - dto.tags, - queryRunner, - null, - subspace, - otherTags, - ); - subspace.tags = tagModels; - } - }), - ); - - return savedSubspaces; - } catch (error) { - if (error instanceof HttpException) { - throw error; - } - throw new HttpException( - error.message || 'Failed to create subspaces.', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + return savedSubspaces; } async updateSubspaceModels( updateDtos: UpdateSubspaceModelDto[], - spaceModel: SpaceModelEntity, queryRunner: QueryRunner, ): Promise { - try { - const updateResults: IUpdateSubspaceModelInterface[] = []; + const updateResults: IUpdateSubspaceModelInterface[] = []; - const updatePromises = updateDtos.map(async (dto) => { - try { - const subspaceModel = await this.findOne(dto.subspaceUuid); + for (const dto of updateDtos) { + await this.findOne(dto.subspaceUuid); + const updateResult: IUpdateSubspaceModelInterface = { + uuid: dto.subspaceUuid, + }; - if (!subspaceModel) { - throw new HttpException( - `Subspace model with UUID ${dto.subspaceUuid} not found.`, - HttpStatus.NOT_FOUND, - ); - } + if (dto.subspaceName) { + await this.updateSubspaceName( + dto.subspaceUuid, + dto.subspaceName, + queryRunner, + ); + updateResult.subspaceName = dto.subspaceName; + } - const updateResult: IUpdateSubspaceModelInterface = { - uuid: dto.subspaceUuid, - }; + updateResults.push(updateResult); + } - if (dto.subspaceName) { - await this.updateSubspaceName( - dto.subspaceUuid, - dto.subspaceName, - queryRunner, - ); - subspaceModel.subspaceName = dto.subspaceName; - updateResult.subspaceName = dto.subspaceName; - } + return updateResults; + } - updateResults.push(updateResult); - } catch (error) { + async deleteSubspaceModels( + deleteDtos: DeleteSubspaceModelDto[], + queryRunner: QueryRunner, + ): Promise { + const deleteResults: IDeletedSubsaceModelInterface[] = []; + + for (const dto of deleteDtos) { + const subspaceModel = await this.findOne(dto.subspaceUuid); + + await queryRunner.manager.update( + this.subspaceModelRepository.target, + { uuid: dto.subspaceUuid }, + { disabled: true }, + ); + + if (subspaceModel.tags?.length) { + const modifyTagDtos = subspaceModel.tags.map((tag) => ({ + uuid: tag.uuid, + action: ModifyAction.DELETE, + })); + await this.tagModelService.modifyTags( + modifyTagDtos, + queryRunner, + null, + subspaceModel, + ); + } + + deleteResults.push({ uuid: dto.subspaceUuid }); + } + + return deleteResults; + } + + async modifySubSpaceModels( + subspaceDtos: ModifySubspaceModelDto[], + spaceModel: SpaceModelEntity, + queryRunner: QueryRunner, + ): Promise { + for (const subspace of subspaceDtos) { + switch (subspace.action) { + case ModifyAction.ADD: + await this.handleAddAction(subspace, spaceModel, queryRunner); + break; + case ModifyAction.UPDATE: + await this.handleUpdateAction(subspace, queryRunner); + break; + case ModifyAction.DELETE: + await this.handleDeleteAction(subspace, queryRunner); + break; + default: throw new HttpException( - `Failed to update subspace model with UUID ${dto.subspaceUuid}: ${error.message}`, - HttpStatus.INTERNAL_SERVER_ERROR, + `Invalid action "${subspace.action}".`, + HttpStatus.BAD_REQUEST, ); - } - }); + } + } + } - await Promise.all(updatePromises); + private async handleAddAction( + subspace: ModifySubspaceModelDto, + spaceModel: SpaceModelEntity, + queryRunner: QueryRunner, + ): Promise { + const createTagDtos: CreateTagModelDto[] = + subspace.tags?.map((tag) => ({ + tag: tag.tag as string, + productUuid: tag.productUuid as string, + })) || []; + await this.createSubSpaceModels( + [{ subspaceName: subspace.subspaceName, tags: createTagDtos }], + spaceModel, + queryRunner, + ); + } - return updateResults; - } catch (error) { + private async handleUpdateAction( + subspace: ModifySubspaceModelDto, + queryRunner: QueryRunner, + ): Promise { + const existingSubspace = await this.findOne(subspace.uuid); + + if (subspace.subspaceName) { + existingSubspace.subspaceName = subspace.subspaceName; + await queryRunner.manager.save(existingSubspace); + } + + if (subspace.tags?.length) { + await this.tagModelService.modifyTags( + subspace.tags, + queryRunner, + null, + existingSubspace, + ); + } + } + + private async handleDeleteAction( + subspace: ModifySubspaceModelDto, + queryRunner: QueryRunner, + ): Promise { + const subspaceModel = await this.findOne(subspace.uuid); + + await queryRunner.manager.update( + this.subspaceModelRepository.target, + { uuid: subspace.uuid }, + { disabled: true }, + ); + + if (subspaceModel.tags?.length) { + const modifyTagDtos = subspaceModel.tags.map((tag) => ({ + uuid: tag.uuid, + action: ModifyAction.DELETE, + })); + await this.tagModelService.modifyTags( + modifyTagDtos, + queryRunner, + null, + subspaceModel, + ); + } + } + + private async findOne(subspaceUuid: string): Promise { + const subspace = await this.subspaceModelRepository.findOne({ + where: { uuid: subspaceUuid }, + relations: ['tags'], + }); + if (!subspace) { throw new HttpException( - error.message || 'Failed to update subspace models.', - HttpStatus.INTERNAL_SERVER_ERROR, + `SubspaceModel with UUID ${subspaceUuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + return subspace; + } + + private validateInputDtos(subSpaceModelDtos: CreateSubspaceModelDto[]): void { + if (subSpaceModelDtos.length === 0) { + throw new HttpException( + 'Subspace models cannot be empty.', + HttpStatus.BAD_REQUEST, + ); + } + this.validateName(subSpaceModelDtos.map((dto) => dto.subspaceName)); + } + + private validateName(names: string[]): void { + const seenNames = new Set(); + const duplicateNames = new Set(); + + for (const name of names) { + if (seenNames.has(name)) { + duplicateNames.add(name); + } else { + seenNames.add(name); + } + } + + if (duplicateNames.size > 0) { + throw new HttpException( + `Duplicate subspace names found: ${[...duplicateNames].join(', ')}`, + HttpStatus.CONFLICT, ); } } @@ -138,146 +268,4 @@ export class SubSpaceModelService { { subspaceName }, ); } - - async deleteSubspaceModels( - deleteDtos: DeleteSubspaceModelDto[], - queryRunner: QueryRunner, - ): Promise { - try { - const deleteResults: IDeletedSubsaceModelInterface[] = []; - - const deletePromises = deleteDtos.map(async (dto) => { - try { - const subspaceModel = await this.findOne(dto.subspaceUuid); - - if (!subspaceModel) { - throw new HttpException( - `Subspace model with UUID ${dto.subspaceUuid} not found.`, - HttpStatus.NOT_FOUND, - ); - } - - await queryRunner.manager.update( - this.subspaceModelRepository.target, - { uuid: dto.subspaceUuid }, - { disabled: true }, - ); - - deleteResults.push({ uuid: dto.subspaceUuid }); - } catch (error) { - throw new HttpException( - `Failed to delete subspace model with UUID ${dto.subspaceUuid}: ${error.message}`, - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - }); - - await Promise.all(deletePromises); - - return deleteResults; - } catch (error) { - throw new HttpException( - 'Bulk delete operation failed. Please try again.', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - async findOne(subspaceUuid: string) { - const subspace = await this.subspaceModelRepository.findOne({ - where: { - uuid: subspaceUuid, - }, - }); - if (!subspace) { - throw new HttpException( - `SubspaceModel with ${subspaceUuid} not found`, - HttpStatus.NOT_FOUND, - ); - } - return subspace; - } - - private validateInputDtos(subSpaceModelDtos: CreateSubspaceModelDto[]) { - if (subSpaceModelDtos.length === 0) { - throw new HttpException( - 'Subspace models cannot be empty.', - HttpStatus.BAD_REQUEST, - ); - } - const incomingNames = subSpaceModelDtos.map((dto) => dto.subspaceName); - this.validateName(incomingNames); - } - - private validateName(names: string[]) { - const seenNames = new Set(); - const duplicateNames = new Set(); - - for (const name of names) { - if (seenNames.has(name)) { - duplicateNames.add(name); - } else { - seenNames.add(name); - } - } - - if (duplicateNames.size > 0) { - throw new HttpException( - `Duplicate subspace names found in request: ${[...duplicateNames].join(', ')}`, - HttpStatus.CONFLICT, - ); - } - } - - async modifySubSpaceModels( - subspaceDtos: ModifySubspaceModelDto[], - spaceModel: SpaceModelEntity, - queryRunner: QueryRunner, - ) { - try { - for (const subspace of subspaceDtos) { - if (subspace.action === 'add') { - const createTagDtos: CreateTagModelDto[] = - subspace.tags?.map((tag) => ({ - tag: tag.tag as string, - productUuid: tag.productUuid as string, - })) || []; - await this.createSubSpaceModels( - [{ subspaceName: subspace.subspaceName, tags: createTagDtos }], - spaceModel, - queryRunner, - ); - } else if (subspace.action === 'update') { - const existingSubspace = await this.findOne(subspace.uuid); - - if (!existingSubspace) { - throw new HttpException( - `Subspace with ID ${subspace.uuid} not found.`, - HttpStatus.NOT_FOUND, - ); - } - - existingSubspace.subspaceName = - subspace.subspaceName || existingSubspace.subspaceName; - - const updatedSubspace = - await queryRunner.manager.save(existingSubspace); - - if (subspace.tags) { - await this.tagModelService.modifyTags( - subspace.tags, - queryRunner, - null, - updatedSubspace, - ); - } - } - } - } catch (error) { - throw new HttpException( - error.message || 'Failed to modify SpaceModels', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } } diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index 21be316..500eed1 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -8,6 +8,7 @@ import { SubspaceModelEntity } from '@app/common/modules/space-model/entities'; import { TagModelRepository } from '@app/common/modules/space-model'; import { CreateTagModelDto, ModifyTagModelDto } from '../dtos'; import { ProductService } from 'src/product/services'; +import { ModifyAction } from '@app/common/constants/modify-action.enum'; @Injectable() export class TagModelService { @@ -133,8 +134,9 @@ export class TagModelService { subspaceModel?: SubspaceModelEntity, ): Promise { try { + console.log(tags); for (const tag of tags) { - if (tag.action === 'add') { + if (tag.action === ModifyAction.ADD) { const createTagDto: CreateTagModelDto = { tag: tag.tag as string, productUuid: tag.productUuid as string, @@ -146,9 +148,9 @@ export class TagModelService { spaceModel, subspaceModel, ); - } else if (tag.action === 'update') { + } else if (tag.action === ModifyAction.UPDATE) { await this.updateTag(tag, queryRunner, spaceModel, subspaceModel); - } else if (tag.action === 'delete') { + } else if (tag.action === ModifyAction.DELETE) { await queryRunner.manager.update( this.tagModelRepository.target, { uuid: tag.uuid }, From fef44b3c4fc95f17cfa874f66ed8bcdf3b541d46 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 23 Dec 2024 22:13:24 +0400 Subject: [PATCH 117/247] added space model delete --- libs/common/src/constants/controller-route.ts | 4 ++ .../src/constants/permissions-mapping.ts | 3 +- libs/common/src/constants/role-permissions.ts | 6 ++- .../controllers/space-model.controller.ts | 16 +++++- .../services/space-model.service.ts | 52 +++++++++++++++++++ 5 files changed, 77 insertions(+), 4 deletions(-) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index aace4d6..6a9b52d 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -281,6 +281,10 @@ export class ControllerRoute { public static readonly UPDATE_SPACE_MODEL_SUMMARY = 'Update Space Model'; public static readonly UPDATE_SPACE_MODEL_DESCRIPTION = 'This endpoint allows you to update a Space Model attributesas well as manage its associated Subspaces and Device'; + + public static readonly DELETE_SPACE_MODEL_SUMMARY = 'Delete Space Model'; + public static readonly DELETE_SPACE_MODEL_DESCRIPTION = + 'This endpoint allows you to delete a specified Space Model within a project. Deleting a Space Model disables the model and all its associated subspaces and tags, ensuring they are no longer active but remain in the system for auditing.'; }; }; diff --git a/libs/common/src/constants/permissions-mapping.ts b/libs/common/src/constants/permissions-mapping.ts index e940933..09a4914 100644 --- a/libs/common/src/constants/permissions-mapping.ts +++ b/libs/common/src/constants/permissions-mapping.ts @@ -12,7 +12,8 @@ export const PermissionMapping = { 'ADD', 'UPDATE', 'DELETE', - 'MODULE_ADD', + 'MODEL_ADD', + 'MODEL_DELETE', 'MODEL_VIEW', 'ASSIGN_USER_TO_SPACE', 'DELETE_USER_FROM_SPACE', diff --git a/libs/common/src/constants/role-permissions.ts b/libs/common/src/constants/role-permissions.ts index a7163c3..c457c3c 100644 --- a/libs/common/src/constants/role-permissions.ts +++ b/libs/common/src/constants/role-permissions.ts @@ -16,9 +16,10 @@ export const RolePermissions = { 'SPACE_ADD', 'SPACE_UPDATE', 'SPACE_DELETE', - 'SPACE_MODULE_ADD', + 'SPACE_MODEL_ADD', 'SPACE_MODEL_VIEW', 'SPACE_MODEL_UPDATE', + 'SPACE_MODEL_DELETE', 'ASSIGN_USER_TO_SPACE', 'DELETE_USER_FROM_SPACE', 'SUBSPACE_VIEW', @@ -61,9 +62,10 @@ export const RolePermissions = { 'SPACE_ADD', 'SPACE_UPDATE', 'SPACE_DELETE', - 'SPACE_MODULE_ADD', + 'SPACE_MODEL_ADD', 'SPACE_MODEL_VIEW', 'SPACE_MODEL_UPDATE', + 'SPACE_MODEL_DELETE', 'ASSIGN_USER_TO_SPACE', 'DELETE_USER_FROM_SPACE', 'SUBSPACE_VIEW', diff --git a/src/space-model/controllers/space-model.controller.ts b/src/space-model/controllers/space-model.controller.ts index ba47ae9..5708e3f 100644 --- a/src/space-model/controllers/space-model.controller.ts +++ b/src/space-model/controllers/space-model.controller.ts @@ -2,6 +2,7 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; import { Body, Controller, + Delete, Get, Param, Post, @@ -32,7 +33,7 @@ export class SpaceModelController { @ApiBearerAuth() @UseGuards(PermissionsGuard) - @Permissions('SPACE_MODULE_ADD') + @Permissions('SPACE_MODEL_ADD') @ApiOperation({ summary: ControllerRoute.SPACE_MODEL.ACTIONS.CREATE_SPACE_MODEL_SUMMARY, description: @@ -80,4 +81,17 @@ export class SpaceModelController { ): Promise { return await this.spaceModelService.update(dto, param); } + + @ApiBearerAuth() + @UseGuards(PermissionsGuard) + @Permissions('SPACE_MODEL_DELETE') + @ApiOperation({ + summary: ControllerRoute.SPACE_MODEL.ACTIONS.DELETE_SPACE_MODEL_SUMMARY, + description: + ControllerRoute.SPACE_MODEL.ACTIONS.DELETE_SPACE_MODEL_DESCRIPTION, + }) + @Delete(':spaceModelUuid') + async delete(@Param() param: SpaceModelParam): Promise { + return await this.spaceModelService.deleteSpaceModel(param); + } } diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 23e5d51..0f62a0f 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -169,6 +169,58 @@ export class SpaceModelService { } } + async deleteSpaceModel(param: SpaceModelParam): Promise { + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + await this.validateProject(param.projectUuid); + const spaceModel = await this.validateSpaceModel(param.spaceModelUuid); + + if (spaceModel.subspaceModels?.length) { + const deleteSubspaceDtos = spaceModel.subspaceModels.map( + (subspace) => ({ + subspaceUuid: subspace.uuid, + }), + ); + + await this.subSpaceModelService.deleteSubspaceModels( + deleteSubspaceDtos, + queryRunner, + ); + } + + await queryRunner.manager.update( + this.spaceModelRepository.target, + { uuid: param.spaceModelUuid }, + { disabled: true }, + ); + + await queryRunner.commitTransaction(); + + return new SuccessResponseDto({ + message: `SpaceModel with UUID ${param.spaceModelUuid} deleted successfully.`, + statusCode: HttpStatus.OK, + }); + } catch (error) { + await queryRunner.rollbackTransaction(); + + const errorMessage = + error instanceof HttpException + ? error.message + : 'An unexpected error occurred while deleting the SpaceModel'; + const statusCode = + error instanceof HttpException + ? error.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + + throw new HttpException(errorMessage, statusCode); + } finally { + await queryRunner.release(); + } + } + async validateName(modelName: string, projectUuid: string): Promise { const isModelExist = await this.spaceModelRepository.findOne({ where: { modelName, project: { uuid: projectUuid }, disabled: false }, From 97b53bf41755004a1cbee615b83c82b208a4c7de Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 24 Dec 2024 08:12:56 +0400 Subject: [PATCH 118/247] space cleanup --- libs/common/src/common.module.ts | 3 +- libs/common/src/database/database.module.ts | 9 - .../modules/device/entities/device.entity.ts | 12 +- .../product/entities/product.entity.ts | 4 - libs/common/src/modules/space/dtos/index.ts | 2 - .../space/dtos/space-product-item.dto.ts | 15 -- .../modules/space/dtos/space-product.dto.ts | 19 -- .../src/modules/space/entities/index.ts | 2 - .../entities/space-product-item.entity.ts | 29 --- .../space/entities/space-product.entity.ts | 38 ---- .../modules/space/entities/space.entity.ts | 4 - .../modules/space/entities/subspace/index.ts | 2 - .../subspace/subspace-product-item.entity.ts | 32 ---- .../subspace/subspace-product.entity.ts | 38 ---- .../entities/subspace/subspace.entity.ts | 10 - .../space/repositories/space.repository.ts | 20 +- .../space/repositories/subspace.repository.ts | 20 +- .../modules/space/space.repository.module.ts | 10 +- src/space-model/space-model.module.ts | 8 +- src/space/dtos/add.space.dto.ts | 40 ---- src/space/dtos/subspace/add.subspace.dto.ts | 21 +-- src/space/services/index.ts | 2 - .../services/space-product-items/index.ts | 1 - .../space-product-items.service.ts | 56 ------ src/space/services/space-products/index.ts | 1 - .../space-products/space-products.service.ts | 176 ------------------ src/space/services/space.service.ts | 16 +- src/space/services/subspace/index.ts | 2 - .../subspace/subspace-product-item.service.ts | 50 ----- .../subspace/subspace-product.service.ts | 65 ------- .../services/subspace/subspace.service.ts | 13 -- src/space/space.module.ts | 24 +-- 32 files changed, 12 insertions(+), 732 deletions(-) delete mode 100644 libs/common/src/modules/space/dtos/space-product-item.dto.ts delete mode 100644 libs/common/src/modules/space/dtos/space-product.dto.ts delete mode 100644 libs/common/src/modules/space/entities/space-product-item.entity.ts delete mode 100644 libs/common/src/modules/space/entities/space-product.entity.ts delete mode 100644 libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts delete mode 100644 libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts delete mode 100644 src/space/services/space-product-items/index.ts delete mode 100644 src/space/services/space-product-items/space-product-items.service.ts delete mode 100644 src/space/services/space-products/index.ts delete mode 100644 src/space/services/space-products/space-products.service.ts delete mode 100644 src/space/services/subspace/subspace-product-item.service.ts delete mode 100644 src/space/services/subspace/subspace-product.service.ts diff --git a/libs/common/src/common.module.ts b/libs/common/src/common.module.ts index d63ff67..91e5507 100644 --- a/libs/common/src/common.module.ts +++ b/libs/common/src/common.module.ts @@ -9,7 +9,7 @@ import { EmailService } from './util/email.service'; import { ErrorMessageService } from 'src/error-message/error-message.service'; import { TuyaService } from './integrations/tuya/services/tuya.service'; import { SceneDeviceRepository } from './modules/scene-device/repositories'; -import { SpaceProductItemRepository, SpaceRepository } from './modules/space'; +import { SpaceRepository } from './modules/space'; import { SpaceModelRepository, SubspaceModelRepository, @@ -26,7 +26,6 @@ import { SubspaceRepository } from './modules/space/repositories/subspace.reposi SubspaceRepository, SubspaceModelRepository, SpaceModelRepository, - SpaceProductItemRepository, ], exports: [ CommonService, diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 8fa7f07..7e8dbb6 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -11,10 +11,7 @@ import { PermissionTypeEntity } from '../modules/permission/entities'; import { SpaceEntity, SpaceLinkEntity, - SpaceProductItemEntity, SubspaceEntity, - SubspaceProductEntity, - SubspaceProductItemEntity, } from '../modules/space/entities'; import { UserSpaceEntity } from '../modules/user/entities'; import { DeviceUserPermissionEntity } from '../modules/device/entities'; @@ -28,7 +25,6 @@ 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'; import { ProjectEntity } from '../modules/project/entities'; import { SpaceModelEntity, @@ -65,7 +61,6 @@ import { SpaceEntity, SpaceLinkEntity, SubspaceEntity, - SpaceProductEntity, UserSpaceEntity, DeviceUserPermissionEntity, RoleTypeEntity, @@ -81,10 +76,6 @@ import { SpaceModelEntity, SubspaceModelEntity, TagModel, - SpaceProductEntity, - SpaceProductItemEntity, - SubspaceProductEntity, - SubspaceProductItemEntity, InviteUserEntity, InviteUserSpaceEntity, ], diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index ade7d99..9a75950 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -6,15 +6,10 @@ import { Unique, Index, JoinColumn, - OneToOne, } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceDto, DeviceUserPermissionDto } from '../dtos/device.dto'; -import { - SpaceEntity, - SpaceProductItemEntity, - SubspaceEntity, -} from '../../space/entities'; +import { SpaceEntity, SubspaceEntity } from '../../space/entities'; import { ProductEntity } from '../../product/entities'; import { UserEntity } from '../../user/entities'; import { DeviceNotificationDto } from '../dtos'; @@ -79,11 +74,6 @@ export class DeviceEntity extends AbstractEntity { @OneToMany(() => SceneDeviceEntity, (sceneDevice) => sceneDevice.device, {}) sceneDevices: SceneDeviceEntity[]; - @OneToOne(() => SpaceProductItemEntity, (tag) => tag.device, { - nullable: true, - }) - public tag?: SpaceProductItemEntity; - constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/product/entities/product.entity.ts b/libs/common/src/modules/product/entities/product.entity.ts index 6de8ced..79ca211 100644 --- a/libs/common/src/modules/product/entities/product.entity.ts +++ b/libs/common/src/modules/product/entities/product.entity.ts @@ -2,7 +2,6 @@ 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'; import { TagModel } from '../../space-model'; @Entity({ name: 'product' }) @@ -28,9 +27,6 @@ export class ProductEntity extends AbstractEntity { }) public prodType: string; - @OneToMany(() => SpaceProductEntity, (spaceProduct) => spaceProduct.product) - spaceProducts: SpaceProductEntity[]; - @OneToMany(() => TagModel, (tag) => tag.product) tagModels: TagModel[]; diff --git a/libs/common/src/modules/space/dtos/index.ts b/libs/common/src/modules/space/dtos/index.ts index b470336..fcc0fdd 100644 --- a/libs/common/src/modules/space/dtos/index.ts +++ b/libs/common/src/modules/space/dtos/index.ts @@ -1,4 +1,2 @@ export * from './space.dto'; export * from './subspace.dto'; -export * from './space-product-item.dto'; -export * from './space-product.dto'; diff --git a/libs/common/src/modules/space/dtos/space-product-item.dto.ts b/libs/common/src/modules/space/dtos/space-product-item.dto.ts deleted file mode 100644 index 8973c1a..0000000 --- a/libs/common/src/modules/space/dtos/space-product-item.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { IsString, IsNotEmpty } from 'class-validator'; - -export class SpaceProductItemDto { - @IsString() - @IsNotEmpty() - uuid: string; - - @IsString() - @IsNotEmpty() - tag: string; - - @IsString() - @IsNotEmpty() - spaceProductUuid: string; -} diff --git a/libs/common/src/modules/space/dtos/space-product.dto.ts b/libs/common/src/modules/space/dtos/space-product.dto.ts deleted file mode 100644 index 92f5f71..0000000 --- a/libs/common/src/modules/space/dtos/space-product.dto.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsString, IsNotEmpty } from 'class-validator'; -import { SpaceProductItemDto } from './space-product-item.dto'; - -export class SpaceProductModelDto { - @IsString() - @IsNotEmpty() - uuid: string; - - @IsString() - @IsNotEmpty() - productUuid: string; - - @ApiProperty({ - description: 'List of individual items with specific names for the product', - type: [SpaceProductItemDto], - }) - items: SpaceProductItemDto[]; -} diff --git a/libs/common/src/modules/space/entities/index.ts b/libs/common/src/modules/space/entities/index.ts index f07ec93..1d25b03 100644 --- a/libs/common/src/modules/space/entities/index.ts +++ b/libs/common/src/modules/space/entities/index.ts @@ -1,5 +1,3 @@ export * from './space.entity'; export * from './subspace'; -export * from './space-product.entity'; -export * from './space-product-item.entity'; export * from './space-link.entity'; diff --git a/libs/common/src/modules/space/entities/space-product-item.entity.ts b/libs/common/src/modules/space/entities/space-product-item.entity.ts deleted file mode 100644 index 8f93264..0000000 --- a/libs/common/src/modules/space/entities/space-product-item.entity.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Column, Entity, ManyToOne, OneToOne } from 'typeorm'; -import { SpaceProductEntity } from './space-product.entity'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { SpaceProductItemDto } from '../dtos'; -import { DeviceEntity } from '../../device/entities'; - -@Entity({ name: 'space-product-item' }) -export class SpaceProductItemEntity extends AbstractEntity { - @Column({ - nullable: false, - }) - public tag: string; - - @ManyToOne(() => SpaceProductEntity, (spaceProduct) => spaceProduct.items, { - nullable: false, - }) - public spaceProduct: SpaceProductEntity; - - @Column({ - nullable: false, - default: false, - }) - public disabled: boolean; - - @OneToOne(() => DeviceEntity, (device) => device.tag, { - nullable: true, - }) - public device?: DeviceEntity; -} diff --git a/libs/common/src/modules/space/entities/space-product.entity.ts b/libs/common/src/modules/space/entities/space-product.entity.ts deleted file mode 100644 index 4e333cd..0000000 --- a/libs/common/src/modules/space/entities/space-product.entity.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Column, Entity, ManyToOne, JoinColumn, OneToMany } from 'typeorm'; -import { SpaceEntity } from './space.entity'; -import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { ProductEntity } from '../../product/entities'; -import { SpaceProductItemEntity } from './space-product-item.entity'; - -@Entity({ name: 'space-product' }) -export class SpaceProductEntity extends AbstractEntity { - @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, - default: false, - }) - public disabled: boolean; - - @OneToMany(() => SpaceProductItemEntity, (item) => item.spaceProduct, { - cascade: true, - }) - public items: SpaceProductItemEntity[]; - - constructor(partial: Partial) { - super(); - Object.assign(this, partial); - } -} diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index d6133da..9061df1 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -13,7 +13,6 @@ import { DeviceEntity } from '../../device/entities'; import { CommunityEntity } from '../../community/entities'; import { SubspaceEntity } from './subspace'; import { SpaceLinkEntity } from './space-link.entity'; -import { SpaceProductEntity } from './space-product.entity'; import { SceneEntity } from '../../scene/entities'; import { SpaceModelEntity } from '../../space-model'; import { InviteUserSpaceEntity } from '../../Invite-user/entities'; @@ -100,9 +99,6 @@ export class SpaceEntity extends AbstractEntity { }) public icon: string; - @OneToMany(() => SpaceProductEntity, (spaceProduct) => spaceProduct.space) - spaceProducts: SpaceProductEntity[]; - @OneToMany(() => SceneEntity, (scene) => scene.space) scenes: SceneEntity[]; diff --git a/libs/common/src/modules/space/entities/subspace/index.ts b/libs/common/src/modules/space/entities/subspace/index.ts index 471b7b1..be13961 100644 --- a/libs/common/src/modules/space/entities/subspace/index.ts +++ b/libs/common/src/modules/space/entities/subspace/index.ts @@ -1,3 +1 @@ export * from './subspace.entity'; -export * from './subspace-product.entity'; -export * from './subspace-product-item.entity'; diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts deleted file mode 100644 index eae8a75..0000000 --- a/libs/common/src/modules/space/entities/subspace/subspace-product-item.entity.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; -import { SpaceProductItemDto } from '../../dtos'; -import { Column, Entity, ManyToOne } from 'typeorm'; -import { SubspaceProductEntity } from './subspace-product.entity'; - -@Entity({ name: 'subspace-product-item' }) -export class SubspaceProductItemEntity extends AbstractEntity { - @Column({ - nullable: false, - }) - public tag: string; - - @ManyToOne( - () => SubspaceProductEntity, - (subspaceProduct) => subspaceProduct.items, - { - nullable: false, - }, - ) - public subspaceProduct: SubspaceProductEntity; - - @Column({ - nullable: false, - default: false, - }) - public disabled: boolean; - - constructor(partial: Partial) { - super(); - Object.assign(this, partial); - } -} diff --git a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts deleted file mode 100644 index b5a16cc..0000000 --- a/libs/common/src/modules/space/entities/subspace/subspace-product.entity.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ProductEntity } from '@app/common/modules/product/entities'; -import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'; -import { SubspaceEntity } from './subspace.entity'; -import { AbstractEntity } from '@app/common/modules/abstract/entities/abstract.entity'; -import { SubspaceProductItemEntity } from './subspace-product-item.entity'; -import { SpaceProductModelDto } from '../../dtos'; - -@Entity({ name: 'subspace-product' }) -export class SubspaceProductEntity extends AbstractEntity { - @Column({ - type: 'uuid', - default: () => 'gen_random_uuid()', - nullable: false, - }) - public uuid: string; - - @Column({ - nullable: false, - default: false, - }) - public disabled: boolean; - - @ManyToOne(() => SubspaceEntity, (subspace) => subspace.subspaceProducts, { - nullable: false, - }) - public subspace: SubspaceEntity; - - @ManyToOne(() => ProductEntity, (product) => product.spaceProducts, { - nullable: false, - }) - public product: ProductEntity; - - @OneToMany(() => SubspaceProductItemEntity, (item) => item.subspaceProduct, { - nullable: true, - }) - public items: SubspaceProductItemEntity[]; - -} diff --git a/libs/common/src/modules/space/entities/subspace/subspace.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace.entity.ts index 6ad7751..036df46 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace.entity.ts @@ -4,7 +4,6 @@ import { SubspaceModelEntity } from '@app/common/modules/space-model'; import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; import { SubspaceDto } from '../../dtos'; import { SpaceEntity } from '../space.entity'; -import { SubspaceProductEntity } from './subspace-product.entity'; @Entity({ name: 'subspace' }) export class SubspaceEntity extends AbstractEntity { @@ -41,15 +40,6 @@ export class SubspaceEntity extends AbstractEntity { @JoinColumn({ name: 'subspace_model_uuid' }) subSpaceModel?: SubspaceModelEntity; - @OneToMany( - () => SubspaceProductEntity, - (subspaceProduct) => subspaceProduct.subspace, - { - nullable: true, - }, - ) - public subspaceProducts: SubspaceProductEntity[]; - constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/space/repositories/space.repository.ts b/libs/common/src/modules/space/repositories/space.repository.ts index 66f96a4..a769302 100644 --- a/libs/common/src/modules/space/repositories/space.repository.ts +++ b/libs/common/src/modules/space/repositories/space.repository.ts @@ -1,11 +1,6 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; -import { SpaceProductEntity } from '../entities/space-product.entity'; -import { - SpaceEntity, - SpaceLinkEntity, - SpaceProductItemEntity, -} from '../entities'; +import { SpaceEntity, SpaceLinkEntity } from '../entities'; @Injectable() export class SpaceRepository extends Repository { @@ -20,16 +15,3 @@ export class SpaceLinkRepository extends Repository { super(SpaceLinkEntity, dataSource.createEntityManager()); } } -@Injectable() -export class SpaceProductRepository extends Repository { - constructor(private dataSource: DataSource) { - super(SpaceProductEntity, dataSource.createEntityManager()); - } -} - -@Injectable() -export class SpaceProductItemRepository extends Repository { - constructor(private dataSource: DataSource) { - super(SpaceProductItemEntity, dataSource.createEntityManager()); - } -} diff --git a/libs/common/src/modules/space/repositories/subspace.repository.ts b/libs/common/src/modules/space/repositories/subspace.repository.ts index 3682c05..5897510 100644 --- a/libs/common/src/modules/space/repositories/subspace.repository.ts +++ b/libs/common/src/modules/space/repositories/subspace.repository.ts @@ -1,9 +1,5 @@ import { DataSource, Repository } from 'typeorm'; -import { - SubspaceEntity, - SubspaceProductEntity, - SubspaceProductItemEntity, -} from '../entities'; +import { SubspaceEntity } from '../entities'; import { Injectable } from '@nestjs/common'; @Injectable() @@ -12,17 +8,3 @@ export class SubspaceRepository extends Repository { super(SubspaceEntity, dataSource.createEntityManager()); } } - -@Injectable() -export class SubspaceProductRepository extends Repository { - constructor(private dataSource: DataSource) { - super(SubspaceProductEntity, dataSource.createEntityManager()); - } -} - -@Injectable() -export class SubspaceProductItemRepository extends Repository { - constructor(private dataSource: DataSource) { - super(SubspaceProductItemEntity, dataSource.createEntityManager()); - } -} diff --git a/libs/common/src/modules/space/space.repository.module.ts b/libs/common/src/modules/space/space.repository.module.ts index b39f98d..90916c2 100644 --- a/libs/common/src/modules/space/space.repository.module.ts +++ b/libs/common/src/modules/space/space.repository.module.ts @@ -1,17 +1,11 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { SpaceEntity, SubspaceEntity, SubspaceProductEntity } from './entities'; +import { SpaceEntity, SubspaceEntity } from './entities'; @Module({ providers: [], exports: [], controllers: [], - imports: [ - TypeOrmModule.forFeature([ - SpaceEntity, - SubspaceEntity, - SubspaceProductEntity, - ]), - ], + imports: [TypeOrmModule.forFeature([SpaceEntity, SubspaceEntity])], }) export class SpaceRepositoryModule {} diff --git a/src/space-model/space-model.module.ts b/src/space-model/space-model.module.ts index 09cc8dd..736245b 100644 --- a/src/space-model/space-model.module.ts +++ b/src/space-model/space-model.module.ts @@ -17,11 +17,7 @@ import { ProductRepository } from '@app/common/modules/product/repositories'; import { PropogateSubspaceHandler } from './handlers'; import { CqrsModule } from '@nestjs/cqrs'; import { SpaceRepository } from '@app/common/modules/space'; -import { - SubspaceProductItemRepository, - SubspaceProductRepository, - SubspaceRepository, -} from '@app/common/modules/space/repositories/subspace.repository'; +import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; const CommandHandlers = [PropogateSubspaceHandler]; @@ -38,8 +34,6 @@ const CommandHandlers = [PropogateSubspaceHandler]; SubspaceModelRepository, ProductRepository, SubspaceRepository, - SubspaceProductRepository, - SubspaceProductItemRepository, TagModelService, TagModelRepository, ], diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index 7c10c59..353acf2 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -12,35 +12,6 @@ import { } from 'class-validator'; import { AddSubspaceDto } from './subspace'; -export class CreateSpaceProductItemDto { - @ApiProperty({ - description: 'Specific name for the product item', - example: 'Light 1', - }) - @IsNotEmpty() - @IsString() - tag: string; -} - -export class ProductAssignmentDto { - @ApiProperty({ - description: 'UUID of the product to be assigned', - example: 'prod-uuid-1234', - }) - @IsNotEmpty() - productId: string; - - @ApiProperty({ - description: 'Specific names for each product item', - type: [CreateSpaceProductItemDto], - example: [{ tag: 'Light 1' }, { tag: 'Light 2' }, { tag: 'Light 3' }], - }) - @IsArray() - @ValidateNested({ each: true }) - @Type(() => CreateSpaceProductItemDto) - items: CreateSpaceProductItemDto[]; -} - export class AddSpaceDto { @ApiProperty({ description: 'Name of the space (e.g., Floor 1, Unit 101)', @@ -97,17 +68,6 @@ export class AddSpaceDto { @IsOptional() direction?: string; - @ApiProperty({ - description: 'List of products assigned to this space', - type: [ProductAssignmentDto], - required: false, - }) - @IsArray() - @ValidateNested({ each: true }) - @IsOptional() - @Type(() => ProductAssignmentDto) - products?: ProductAssignmentDto[]; - @ApiProperty({ description: 'List of subspaces included in the model', type: [AddSubspaceDto], diff --git a/src/space/dtos/subspace/add.subspace.dto.ts b/src/space/dtos/subspace/add.subspace.dto.ts index 6b5078b..a2b12e2 100644 --- a/src/space/dtos/subspace/add.subspace.dto.ts +++ b/src/space/dtos/subspace/add.subspace.dto.ts @@ -1,13 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { - IsArray, - IsNotEmpty, - IsOptional, - IsString, - ValidateNested, -} from 'class-validator'; -import { ProductAssignmentDto } from '../add.space.dto'; +import { IsNotEmpty, IsString } from 'class-validator'; export class AddSubspaceDto { @ApiProperty({ @@ -17,15 +9,4 @@ export class AddSubspaceDto { @IsNotEmpty() @IsString() subspaceName: string; - - @ApiProperty({ - description: 'List of products assigned to this space', - type: [ProductAssignmentDto], - required: false, - }) - @IsArray() - @ValidateNested({ each: true }) - @IsOptional() - @Type(() => ProductAssignmentDto) - products?: ProductAssignmentDto[]; } diff --git a/src/space/services/index.ts b/src/space/services/index.ts index c67ccae..5f86e3d 100644 --- a/src/space/services/index.ts +++ b/src/space/services/index.ts @@ -4,6 +4,4 @@ export * from './space-device.service'; export * from './subspace'; export * from './space-link'; export * from './space-scene.service'; -export * from './space-products'; -export * from './space-product-items'; export * from './space-validation.service'; diff --git a/src/space/services/space-product-items/index.ts b/src/space/services/space-product-items/index.ts deleted file mode 100644 index fff8634..0000000 --- a/src/space/services/space-product-items/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './space-product-items.service'; diff --git a/src/space/services/space-product-items/space-product-items.service.ts b/src/space/services/space-product-items/space-product-items.service.ts deleted file mode 100644 index d9c141b..0000000 --- a/src/space/services/space-product-items/space-product-items.service.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - SpaceEntity, - SpaceProductEntity, - SpaceProductItemRepository, -} from '@app/common/modules/space'; -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { CreateSpaceProductItemDto } from '../../dtos'; -import { QueryRunner } from 'typeorm'; -import { BaseProductItemService } from '../../common'; - -@Injectable() -export class SpaceProductItemService extends BaseProductItemService { - constructor( - private readonly spaceProductItemRepository: SpaceProductItemRepository, - ) { - super(); - } - - async createProductItem( - itemModelDtos: CreateSpaceProductItemDto[], - spaceProduct: SpaceProductEntity, - space: SpaceEntity, - queryRunner: QueryRunner, - ) { - if (!itemModelDtos?.length) return; - - const incomingTags = itemModelDtos.map((item) => item.tag); - - await this.validateTags(incomingTags, queryRunner, space.uuid); - - try { - const productItems = itemModelDtos.map((dto) => - queryRunner.manager.create(this.spaceProductItemRepository.target, { - tag: dto.tag, - spaceProduct, - }), - ); - - await this.saveProductItems( - productItems, - this.spaceProductItemRepository.target, - queryRunner, - ); - } catch (error) { - if (error instanceof HttpException) { - throw error; - } - - throw new HttpException( - error.message || - 'An unexpected error occurred while creating product items.', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } -} diff --git a/src/space/services/space-products/index.ts b/src/space/services/space-products/index.ts deleted file mode 100644 index d0b92d2..0000000 --- a/src/space/services/space-products/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './space-products.service'; diff --git a/src/space/services/space-products/space-products.service.ts b/src/space/services/space-products/space-products.service.ts deleted file mode 100644 index 4071428..0000000 --- a/src/space/services/space-products/space-products.service.ts +++ /dev/null @@ -1,176 +0,0 @@ -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 { In, QueryRunner } from 'typeorm'; -import { ProductAssignmentDto } from '../../dtos'; -import { SpaceProductItemService } from '../space-product-items'; -import { ProductEntity } from '@app/common/modules/product/entities'; -import { ProductService } from 'src/product/services'; - -@Injectable() -export class SpaceProductService { - constructor( - private readonly productRepository: ProductRepository, - private readonly spaceProductItemService: SpaceProductItemService, - private readonly productService: ProductService, - ) {} - - async assignProductsToSpace( - space: SpaceEntity, - products: ProductAssignmentDto[], - queryRunner: QueryRunner, - ): Promise { - let updatedProducts: SpaceProductEntity[] = []; - - try { - const uniqueProducts = this.validateUniqueProducts(products); - const productEntities = await this.getProductEntities(uniqueProducts); - const existingSpaceProducts = await this.getExistingSpaceProducts( - space, - queryRunner, - ); - - if (existingSpaceProducts) { - updatedProducts = await this.updateExistingProducts( - existingSpaceProducts, - uniqueProducts, - productEntities, - queryRunner, - ); - } - - const newProducts = await this.createNewProducts( - uniqueProducts, - productEntities, - space, - queryRunner, - ); - - return [...updatedProducts, ...newProducts]; - } catch (error) { - if (!(error instanceof HttpException)) { - throw new HttpException( - `An error occurred while assigning products to the space ${error}`, - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - throw error; - } - } - - private validateUniqueProducts( - products: ProductAssignmentDto[], - ): ProductAssignmentDto[] { - 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: ProductAssignmentDto[], - ): Promise> { - 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, - ); - } - } - - private async getExistingSpaceProducts( - space: SpaceEntity, - queryRunner: QueryRunner, - ): Promise { - return queryRunner.manager.find(SpaceProductEntity, { - where: { space: { uuid: space.uuid } }, - relations: ['product'], - }); - } - - private async updateExistingProducts( - existingSpaceProducts: SpaceProductEntity[], - uniqueProducts: ProductAssignmentDto[], - productEntities: Map, - queryRunner: QueryRunner, - ): Promise { - const updatedProducts = []; - - for (const { productId } of uniqueProducts) { - productEntities.get(productId); - const existingProduct = existingSpaceProducts.find( - (spaceProduct) => spaceProduct.product.uuid === productId, - ); - - updatedProducts.push(existingProduct); - } - - if (updatedProducts.length > 0) { - await queryRunner.manager.save(SpaceProductEntity, updatedProducts); - } - - return updatedProducts; - } - - private async createNewProducts( - uniqueSpaceProducts: ProductAssignmentDto[], - productEntities: Map, - space: SpaceEntity, - queryRunner: QueryRunner, - ): Promise { - const newProducts = []; - - for (const uniqueSpaceProduct of uniqueSpaceProducts) { - const product = productEntities.get(uniqueSpaceProduct.productId); - await this.getProduct(uniqueSpaceProduct.productId); - - newProducts.push( - queryRunner.manager.create(SpaceProductEntity, { - space, - product, - }), - ); - } - if (newProducts.length > 0) { - await queryRunner.manager.save(SpaceProductEntity, newProducts); - - await Promise.all( - uniqueSpaceProducts.map((dto, index) => { - const spaceProduct = newProducts[index]; - return this.spaceProductItemService.createProductItem( - dto.items, - spaceProduct, - space, - queryRunner, - ); - }), - ); - } - - return newProducts; - } - - async getProduct(productId: string): Promise { - const product = await this.productService.findOne(productId); - return product.data; - } -} diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 2cdf92a..2bd5308 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -17,7 +17,6 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; 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'; import { CreateSubspaceModelDto } from 'src/space-model/dtos'; import { SubSpaceService } from './subspace'; import { DataSource, Not } from 'typeorm'; @@ -30,7 +29,6 @@ export class SpaceService { private readonly dataSource: DataSource, private readonly spaceRepository: SpaceRepository, private readonly spaceLinkService: SpaceLinkService, - private readonly spaceProductService: SpaceProductService, private readonly subSpaceService: SubSpaceService, private readonly validationService: ValidationService, ) {} @@ -102,13 +100,6 @@ export class SpaceService { ); } - if (products && products.length > 0) { - await this.spaceProductService.assignProductsToSpace( - newSpace, - products, - queryRunner, - ); - } await queryRunner.commitTransaction(); return new SuccessResponseDto({ @@ -264,14 +255,9 @@ export class SpaceService { Object.assign(space, updateSpaceDto, { parent }); // Save the updated space - const updatedSpace = await queryRunner.manager.save(space); + await queryRunner.manager.save(space); if (products && products.length > 0) { - await this.spaceProductService.assignProductsToSpace( - updatedSpace, - products, - queryRunner, - ); } await queryRunner.commitTransaction(); diff --git a/src/space/services/subspace/index.ts b/src/space/services/subspace/index.ts index b51a84a..973d199 100644 --- a/src/space/services/subspace/index.ts +++ b/src/space/services/subspace/index.ts @@ -1,4 +1,2 @@ export * from './subspace.service'; export * from './subspace-device.service'; -export * from './subspace-product-item.service'; -export * from './subspace-product.service'; diff --git a/src/space/services/subspace/subspace-product-item.service.ts b/src/space/services/subspace/subspace-product-item.service.ts deleted file mode 100644 index 3eefb2f..0000000 --- a/src/space/services/subspace/subspace-product-item.service.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; -import { QueryRunner } from 'typeorm'; - -import { - SpaceEntity, - SubspaceProductEntity, - SubspaceProductItemEntity, -} from '@app/common/modules/space'; -import { SubspaceProductItemRepository } from '@app/common/modules/space/repositories/subspace.repository'; -import { CreateSpaceProductItemDto } from '../../dtos'; -import { BaseProductItemService } from '../../common'; - -@Injectable() -export class SubspaceProductItemService extends BaseProductItemService { - constructor( - private readonly productItemRepository: SubspaceProductItemRepository, - ) { - super(); - } - - async createItemFromDtos( - product: SubspaceProductEntity, - itemDto: CreateSpaceProductItemDto[], - queryRunner: QueryRunner, - space: SpaceEntity, - ) { - if (!itemDto?.length) return; - const incomingTags = itemDto.map((item) => item.tag); - await this.validateTags(incomingTags, queryRunner, space.uuid); - - try { - const productItems = itemDto.map((dto) => - queryRunner.manager.create(SubspaceProductItemEntity, { - tag: dto.tag, - subspaceProduct: product, - }), - ); - - await queryRunner.manager.save( - this.productItemRepository.target, - productItems, - ); - } catch (error) { - throw new HttpException( - error.message || 'An error occurred while creating product items.', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } -} diff --git a/src/space/services/subspace/subspace-product.service.ts b/src/space/services/subspace/subspace-product.service.ts deleted file mode 100644 index 93ef045..0000000 --- a/src/space/services/subspace/subspace-product.service.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { QueryRunner } from 'typeorm'; - -import { - SpaceEntity, - SubspaceEntity, - SubspaceProductEntity, -} from '@app/common/modules/space'; -import { SubspaceProductItemService } from './subspace-product-item.service'; -import { ProductAssignmentDto } from 'src/space/dtos'; -import { ProductService } from 'src/product/services'; -import { ProductEntity } from '@app/common/modules/product/entities'; - -@Injectable() -export class SubspaceProductService { - constructor( - private readonly subspaceProductItemService: SubspaceProductItemService, - private readonly productService: ProductService, - ) {} - - async createFromDto( - productDtos: ProductAssignmentDto[], - subspace: SubspaceEntity, - queryRunner: QueryRunner, - space: SpaceEntity, - ): Promise { - try { - const newSpaceProducts = await Promise.all( - productDtos.map(async (dto) => { - const product = await this.getProduct(dto.productId); - return queryRunner.manager.create(SubspaceProductEntity, { - subspace, - product, - }); - }), - ); - - const subspaceProducts = await queryRunner.manager.save( - SubspaceProductEntity, - newSpaceProducts, - ); - - await Promise.all( - productDtos.map((dto, index) => - this.subspaceProductItemService.createItemFromDtos( - subspaceProducts[index], - dto.items, - queryRunner, - space, - ), - ), - ); - } catch (error) { - throw new HttpException( - `Failed to create subspace products from DTOs. Error: ${error.message}`, - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - async getProduct(productId: string): Promise { - const product = await this.productService.findOne(productId); - return product.data; - } -} diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 08e12e2..9f553fd 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -19,14 +19,12 @@ import { } from '@app/common/modules/space-model'; import { ValidationService } from '../space-validation.service'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; -import { SubspaceProductService } from './subspace-product.service'; @Injectable() export class SubSpaceService { constructor( private readonly subspaceRepository: SubspaceRepository, private readonly validationService: ValidationService, - private readonly productService: SubspaceProductService, ) {} async createSubspaces( @@ -82,17 +80,6 @@ export class SubSpaceService { const subspaces = await this.createSubspaces(subspaceData, queryRunner); - await Promise.all( - addSubspaceDtos.map((dto, index) => - this.productService.createFromDto( - dto.products, - subspaces[index], - queryRunner, - space, - ), - ), - ); - return subspaces; } catch (error) { throw new Error( diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 81d99e2..5d16689 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -12,20 +12,15 @@ import { import { SpaceDeviceService, SpaceLinkService, - SpaceProductItemService, - SpaceProductService, SpaceSceneService, SpaceService, SpaceUserService, SubspaceDeviceService, - SubspaceProductItemService, SubSpaceService, } from './services'; import { - SpaceProductRepository, SpaceRepository, SpaceLinkRepository, - SpaceProductItemRepository, } from '@app/common/modules/space/repositories'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { @@ -48,12 +43,7 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { SpaceModelRepository } from '@app/common/modules/space-model'; import { CommunityModule } from 'src/community/community.module'; import { ValidationService } from './services'; -import { - SubspaceProductItemRepository, - SubspaceProductRepository, - SubspaceRepository, -} from '@app/common/modules/space/repositories/subspace.repository'; -import { SubspaceProductService } from './services'; +import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, CommunityModule], @@ -75,9 +65,9 @@ import { SubspaceProductService } from './services'; SpaceLinkService, SubspaceDeviceService, SpaceRepository, + SubspaceRepository, DeviceRepository, CommunityRepository, - SubspaceRepository, SpaceLinkRepository, UserSpaceRepository, UserRepository, @@ -88,19 +78,11 @@ import { SubspaceProductService } from './services'; SceneRepository, DeviceService, DeviceStatusFirebaseService, - SubspaceProductItemRepository, DeviceStatusLogRepository, SceneDeviceRepository, - SpaceProductService, - SpaceProductRepository, + ProjectRepository, SpaceModelRepository, - SubspaceRepository, - SpaceProductItemService, - SpaceProductItemRepository, - SubspaceProductService, - SubspaceProductItemService, - SubspaceProductRepository, ], exports: [SpaceService], }) From 1e47fffc0add6cd001059e1f2df7c28975359edf Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 24 Dec 2024 08:43:25 +0400 Subject: [PATCH 119/247] cleaned space entities --- libs/common/src/database/database.module.ts | 2 + .../product/entities/product.entity.ts | 5 ++- .../space-model/entities/tag-model.entity.ts | 17 ++++++-- libs/common/src/modules/space/dtos/index.ts | 1 + libs/common/src/modules/space/dtos/tag.dto.ts | 21 ++++++++++ .../src/modules/space/entities/index.ts | 1 + .../modules/space/entities/space.entity.ts | 5 +++ .../entities/subspace/subspace.entity.ts | 4 ++ .../src/modules/space/entities/tag.entity.ts | 39 +++++++++++++++++++ .../space/repositories/space.repository.ts | 9 ++++- .../modules/space/space.repository.module.ts | 4 +- src/space/services/space.service.ts | 18 +++------ 12 files changed, 106 insertions(+), 20 deletions(-) create mode 100644 libs/common/src/modules/space/dtos/tag.dto.ts create mode 100644 libs/common/src/modules/space/entities/tag.entity.ts diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 7e8dbb6..472772b 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -12,6 +12,7 @@ import { SpaceEntity, SpaceLinkEntity, SubspaceEntity, + TagEntity, } from '../modules/space/entities'; import { UserSpaceEntity } from '../modules/user/entities'; import { DeviceUserPermissionEntity } from '../modules/device/entities'; @@ -61,6 +62,7 @@ import { SpaceEntity, SpaceLinkEntity, SubspaceEntity, + TagEntity, UserSpaceEntity, DeviceUserPermissionEntity, RoleTypeEntity, diff --git a/libs/common/src/modules/product/entities/product.entity.ts b/libs/common/src/modules/product/entities/product.entity.ts index 79ca211..dd7a1e5 100644 --- a/libs/common/src/modules/product/entities/product.entity.ts +++ b/libs/common/src/modules/product/entities/product.entity.ts @@ -3,7 +3,7 @@ import { ProductDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceEntity } from '../../device/entities'; import { TagModel } from '../../space-model'; - +import { TagEntity } from '../../space/entities/tag.entity'; @Entity({ name: 'product' }) export class ProductEntity extends AbstractEntity { @Column({ @@ -30,6 +30,9 @@ export class ProductEntity extends AbstractEntity { @OneToMany(() => TagModel, (tag) => tag.product) tagModels: TagModel[]; + @OneToMany(() => TagEntity, (tag) => tag.product) + tags: TagEntity[]; + @OneToMany( () => DeviceEntity, (devicesProductEntity) => devicesProductEntity.productDevice, diff --git a/libs/common/src/modules/space-model/entities/tag-model.entity.ts b/libs/common/src/modules/space-model/entities/tag-model.entity.ts index 2ff86d4..3f7805c 100644 --- a/libs/common/src/modules/space-model/entities/tag-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/tag-model.entity.ts @@ -1,9 +1,17 @@ -import { Column, Entity, JoinColumn, ManyToOne, Unique } from 'typeorm'; +import { + Column, + Entity, + JoinColumn, + ManyToOne, + OneToMany, + Unique, +} from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { TagModelDto } from '../dtos/tag-model.dto'; import { SpaceModelEntity } from './space-model.entity'; import { SubspaceModelEntity } from './subspace-model'; import { ProductEntity } from '../../product/entities'; +import { TagEntity } from '../../space/entities/tag.entity'; @Entity({ name: 'tag_model' }) @Unique(['tag', 'product', 'spaceModel', 'subspaceModel']) @@ -18,13 +26,13 @@ export class TagModel extends AbstractEntity { product: ProductEntity; @ManyToOne(() => SpaceModelEntity, (space) => space.tags, { nullable: true }) - @JoinColumn({ name: 'space_id' }) + @JoinColumn({ name: 'space_model_id' }) spaceModel: SpaceModelEntity; @ManyToOne(() => SubspaceModelEntity, (subspace) => subspace.tags, { nullable: true, }) - @JoinColumn({ name: 'subspace_id' }) + @JoinColumn({ name: 'subspace_model_id' }) subspaceModel: SubspaceModelEntity; @Column({ @@ -32,4 +40,7 @@ export class TagModel extends AbstractEntity { default: false, }) public disabled: boolean; + + @OneToMany(() => TagEntity, (tag) => tag.model) + tags: TagEntity[]; } diff --git a/libs/common/src/modules/space/dtos/index.ts b/libs/common/src/modules/space/dtos/index.ts index fcc0fdd..c511f8c 100644 --- a/libs/common/src/modules/space/dtos/index.ts +++ b/libs/common/src/modules/space/dtos/index.ts @@ -1,2 +1,3 @@ export * from './space.dto'; export * from './subspace.dto'; +export * from './tag.dto'; diff --git a/libs/common/src/modules/space/dtos/tag.dto.ts b/libs/common/src/modules/space/dtos/tag.dto.ts new file mode 100644 index 0000000..d988c62 --- /dev/null +++ b/libs/common/src/modules/space/dtos/tag.dto.ts @@ -0,0 +1,21 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +export class TagDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public name: string; + + @IsString() + @IsNotEmpty() + public productUuid: string; + + @IsString() + spaceUuid: string; + + @IsString() + subspaceUuid: string; +} diff --git a/libs/common/src/modules/space/entities/index.ts b/libs/common/src/modules/space/entities/index.ts index 1d25b03..5a514e6 100644 --- a/libs/common/src/modules/space/entities/index.ts +++ b/libs/common/src/modules/space/entities/index.ts @@ -1,3 +1,4 @@ export * from './space.entity'; export * from './subspace'; export * from './space-link.entity'; +export * from './tag.entity'; diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 9061df1..889363f 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -16,6 +16,7 @@ import { SpaceLinkEntity } from './space-link.entity'; import { SceneEntity } from '../../scene/entities'; import { SpaceModelEntity } from '../../space-model'; import { InviteUserSpaceEntity } from '../../Invite-user/entities'; +import { TagEntity } from './tag.entity'; @Entity({ name: 'space' }) @Unique(['invitationCode']) @@ -111,6 +112,10 @@ export class SpaceEntity extends AbstractEntity { (inviteUserSpace) => inviteUserSpace.space, ) invitedUsers: InviteUserSpaceEntity[]; + + @OneToMany(() => TagEntity, (tag) => tag.space) + tags: TagEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/space/entities/subspace/subspace.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace.entity.ts index 036df46..d2f86d9 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace.entity.ts @@ -4,6 +4,7 @@ import { SubspaceModelEntity } from '@app/common/modules/space-model'; import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; import { SubspaceDto } from '../../dtos'; import { SpaceEntity } from '../space.entity'; +import { TagEntity } from '../tag.entity'; @Entity({ name: 'subspace' }) export class SubspaceEntity extends AbstractEntity { @@ -40,6 +41,9 @@ export class SubspaceEntity extends AbstractEntity { @JoinColumn({ name: 'subspace_model_uuid' }) subSpaceModel?: SubspaceModelEntity; + @OneToMany(() => TagEntity, (tag) => tag.subspace) + tags: TagEntity[]; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/space/entities/tag.entity.ts b/libs/common/src/modules/space/entities/tag.entity.ts new file mode 100644 index 0000000..77e79cc --- /dev/null +++ b/libs/common/src/modules/space/entities/tag.entity.ts @@ -0,0 +1,39 @@ +import { Entity, Column, ManyToOne, JoinColumn, Unique } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { ProductEntity } from '../../product/entities'; +import { TagDto } from '../dtos'; +import { TagModel } from '../../space-model/entities/tag-model.entity'; +import { SpaceEntity } from './space.entity'; +import { SubspaceEntity } from './subspace'; + +@Entity({ name: 'tag' }) +@Unique(['tag', 'product', 'space', 'subspace']) +export class TagEntity extends AbstractEntity { + @Column({ type: 'varchar', length: 255 }) + tag: string; + + @ManyToOne(() => TagModel, (model) => model.tags, { + nullable: true, + }) + model: TagModel; + + @ManyToOne(() => ProductEntity, (product) => product.tags, { + nullable: false, + }) + product: ProductEntity; + + @ManyToOne(() => SpaceEntity, (space) => space.tags, { nullable: true }) + space: SpaceEntity; + + @ManyToOne(() => SubspaceEntity, (subspace) => subspace.tags, { + nullable: true, + }) + @JoinColumn({ name: 'subspace_id' }) + subspace: SubspaceEntity; + + @Column({ + nullable: false, + default: false, + }) + public disabled: boolean; +} diff --git a/libs/common/src/modules/space/repositories/space.repository.ts b/libs/common/src/modules/space/repositories/space.repository.ts index a769302..b2aacc0 100644 --- a/libs/common/src/modules/space/repositories/space.repository.ts +++ b/libs/common/src/modules/space/repositories/space.repository.ts @@ -1,6 +1,6 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; -import { SpaceEntity, SpaceLinkEntity } from '../entities'; +import { SpaceEntity, SpaceLinkEntity, TagEntity } from '../entities'; @Injectable() export class SpaceRepository extends Repository { @@ -15,3 +15,10 @@ export class SpaceLinkRepository extends Repository { super(SpaceLinkEntity, dataSource.createEntityManager()); } } + +@Injectable() +export class TagRepository extends Repository { + constructor(private dataSource: DataSource) { + super(TagEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/space/space.repository.module.ts b/libs/common/src/modules/space/space.repository.module.ts index 90916c2..030c684 100644 --- a/libs/common/src/modules/space/space.repository.module.ts +++ b/libs/common/src/modules/space/space.repository.module.ts @@ -1,11 +1,11 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { SpaceEntity, SubspaceEntity } from './entities'; +import { SpaceEntity, SubspaceEntity, TagEntity } from './entities'; @Module({ providers: [], exports: [], controllers: [], - imports: [TypeOrmModule.forFeature([SpaceEntity, SubspaceEntity])], + imports: [TypeOrmModule.forFeature([SpaceEntity, SubspaceEntity, TagEntity])], }) export class SpaceRepositoryModule {} diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 2bd5308..43b8f4e 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -9,7 +9,6 @@ import { AddSpaceDto, CommunitySpaceParam, GetSpaceParam, - ProductAssignmentDto, UpdateSpaceDto, } from '../dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; @@ -37,8 +36,7 @@ export class SpaceService { addSpaceDto: AddSpaceDto, params: CommunitySpaceParam, ): Promise { - const { parentUuid, direction, products, spaceModelUuid, subspaces } = - addSpaceDto; + const { parentUuid, direction, spaceModelUuid, subspaces } = addSpaceDto; const { communityUuid, projectUuid } = params; if (addSpaceDto.spaceName === ORPHAN_SPACE_NAME) { @@ -58,7 +56,7 @@ export class SpaceService { projectUuid, ); - this.validateSpaceCreation(spaceModelUuid, products, subspaces); + this.validateSpaceCreation(spaceModelUuid); const parent = parentUuid ? await this.validationService.validateSpace(parentUuid) @@ -246,7 +244,7 @@ export class SpaceService { } // If a parentId is provided, check if the parent exists - const { parentUuid, products } = updateSpaceDto; + const { parentUuid } = updateSpaceDto; const parent = parentUuid ? await this.validationService.validateSpace(parentUuid) : null; @@ -257,8 +255,6 @@ export class SpaceService { // Save the updated space await queryRunner.manager.save(space); - if (products && products.length > 0) { - } await queryRunner.commitTransaction(); return new SuccessResponseDto({ @@ -370,12 +366,8 @@ export class SpaceService { return rootSpaces; } - private validateSpaceCreation( - spaceModelUuid?: string, - products?: ProductAssignmentDto[], - subSpaces?: CreateSubspaceModelDto[], - ) { - if (spaceModelUuid && (products?.length || subSpaces?.length)) { + private validateSpaceCreation(spaceModelUuid?: string) { + if (spaceModelUuid) { throw new HttpException( 'For space creation choose either space model or products and subspace', HttpStatus.CONFLICT, From cb2778dce5fb21206d0e893bc2cd12718d64dc91 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 24 Dec 2024 11:33:58 +0400 Subject: [PATCH 120/247] update in tag service --- .../create-subspace-model.dto.ts | 2 +- .../tag-model-dtos/create-tag-model.dto.ts | 2 +- .../tag-model-dtos/modify-tag-model.dto.ts | 4 +- .../services/space-model.service.ts | 8 + .../subspace/subspace-model.service.ts | 70 ++---- src/space-model/services/tag-model.service.ts | 4 +- src/space/dtos/add.space.dto.ts | 10 + src/space/dtos/index.ts | 1 + src/space/dtos/subspace/add.subspace.dto.ts | 13 +- .../dtos/subspace/delete.subspace.dto.ts | 12 + src/space/dtos/subspace/index.ts | 3 + .../dtos/subspace/modify.subspace.dto.ts | 47 ++++ .../dtos/subspace/update.subspace.dto.ts | 16 ++ src/space/dtos/tag/create-tag-dto.ts | 20 ++ src/space/dtos/tag/index.ts | 1 + src/space/dtos/tag/modify-tag.dto.ts | 37 +++ src/space/dtos/update.space.dto.ts | 61 ++++- src/space/services/space.service.ts | 50 ++-- .../services/subspace/subspace.service.ts | 213 +++++++++++++--- src/space/services/tag/index.ts | 1 + src/space/services/tag/tag.service.ts | 237 ++++++++++++++++++ src/space/space.module.ts | 10 +- 22 files changed, 705 insertions(+), 117 deletions(-) create mode 100644 src/space/dtos/subspace/delete.subspace.dto.ts create mode 100644 src/space/dtos/subspace/modify.subspace.dto.ts create mode 100644 src/space/dtos/subspace/update.subspace.dto.ts create mode 100644 src/space/dtos/tag/create-tag-dto.ts create mode 100644 src/space/dtos/tag/index.ts create mode 100644 src/space/dtos/tag/modify-tag.dto.ts create mode 100644 src/space/services/tag/index.ts create mode 100644 src/space/services/tag/tag.service.ts diff --git a/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts b/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts index 24eacfb..1397edc 100644 --- a/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts +++ b/src/space-model/dtos/subspaces-model-dtos/create-subspace-model.dto.ts @@ -13,7 +13,7 @@ export class CreateSubspaceModelDto { subspaceName: string; @ApiProperty({ - description: 'List of tags associated with the subspace', + description: 'List of tag models associated with the subspace', type: [CreateTagModelDto], }) @IsArray() diff --git a/src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts b/src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts index 65acf2a..5f4ec66 100644 --- a/src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts +++ b/src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts @@ -3,7 +3,7 @@ import { IsNotEmpty, IsString } from 'class-validator'; export class CreateTagModelDto { @ApiProperty({ - description: 'Tag associated with the space or subspace', + description: 'Tag models associated with the space or subspace models', example: 'Temperature Control', }) @IsNotEmpty() diff --git a/src/space-model/dtos/tag-model-dtos/modify-tag-model.dto.ts b/src/space-model/dtos/tag-model-dtos/modify-tag-model.dto.ts index e3d390d..2b64fe3 100644 --- a/src/space-model/dtos/tag-model-dtos/modify-tag-model.dto.ts +++ b/src/space-model/dtos/tag-model-dtos/modify-tag-model.dto.ts @@ -11,7 +11,7 @@ export class ModifyTagModelDto { action: ModifyAction; @ApiPropertyOptional({ - description: 'UUID of the tag (required for update/delete)', + description: 'UUID of the tag model (required for update/delete)', example: '123e4567-e89b-12d3-a456-426614174000', }) @IsOptional() @@ -19,7 +19,7 @@ export class ModifyTagModelDto { uuid?: string; @ApiPropertyOptional({ - description: 'Name of the tag (required for add/update)', + description: 'Name of the tag model (required for add/update)', example: 'Temperature Sensor', }) @IsOptional() diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 0f62a0f..40d9970 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -152,6 +152,14 @@ export class SpaceModelService { queryRunner, ); } + + if (dto.tags) { + await this.tagModelService.modifyTags( + dto.tags, + queryRunner, + spaceModel, + ); + } await queryRunner.commitTransaction(); return new SuccessResponseDto({ diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index 0f59a0d..507a16a 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -4,16 +4,9 @@ import { SubspaceModelRepository, } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { - CreateSubspaceModelDto, - UpdateSubspaceModelDto, - CreateTagModelDto, -} from '../../dtos'; +import { CreateSubspaceModelDto, CreateTagModelDto } from '../../dtos'; import { QueryRunner } from 'typeorm'; -import { - IDeletedSubsaceModelInterface, - IUpdateSubspaceModelInterface, -} from 'src/space-model/interfaces'; +import { IDeletedSubsaceModelInterface } from 'src/space-model/interfaces'; import { DeleteSubspaceModelDto, ModifySubspaceModelDto, @@ -63,33 +56,6 @@ export class SubSpaceModelService { return savedSubspaces; } - async updateSubspaceModels( - updateDtos: UpdateSubspaceModelDto[], - queryRunner: QueryRunner, - ): Promise { - const updateResults: IUpdateSubspaceModelInterface[] = []; - - for (const dto of updateDtos) { - await this.findOne(dto.subspaceUuid); - const updateResult: IUpdateSubspaceModelInterface = { - uuid: dto.subspaceUuid, - }; - - if (dto.subspaceName) { - await this.updateSubspaceName( - dto.subspaceUuid, - dto.subspaceName, - queryRunner, - ); - updateResult.subspaceName = dto.subspaceName; - } - - updateResults.push(updateResult); - } - - return updateResults; - } - async deleteSubspaceModels( deleteDtos: DeleteSubspaceModelDto[], queryRunner: QueryRunner, @@ -167,22 +133,23 @@ export class SubSpaceModelService { } private async handleUpdateAction( - subspace: ModifySubspaceModelDto, + modifyDto: ModifySubspaceModelDto, queryRunner: QueryRunner, ): Promise { - const existingSubspace = await this.findOne(subspace.uuid); + const subspace = await this.findOne(modifyDto.uuid); - if (subspace.subspaceName) { - existingSubspace.subspaceName = subspace.subspaceName; - await queryRunner.manager.save(existingSubspace); - } + await this.updateSubspaceName( + queryRunner, + subspace, + modifyDto.subspaceName, + ); - if (subspace.tags?.length) { + if (modifyDto.tags?.length) { await this.tagModelService.modifyTags( - subspace.tags, + modifyDto.tags, queryRunner, null, - existingSubspace, + subspace, ); } } @@ -258,14 +225,13 @@ export class SubSpaceModelService { } private async updateSubspaceName( - uuid: string, - subspaceName: string, queryRunner: QueryRunner, + subSpaceModel: SubspaceModelEntity, + subspaceName?: string, ): Promise { - await queryRunner.manager.update( - this.subspaceModelRepository.target, - { uuid }, - { subspaceName }, - ); + if (subspaceName) { + subSpaceModel.subspaceName = subspaceName; + await queryRunner.manager.save(subSpaceModel); + } } } diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index 500eed1..8207e0b 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -169,7 +169,7 @@ export class TagModelService { } throw new HttpException( - `An error occurred while modifying tags: ${error.message}`, + `An error occurred while modifying tag models: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -236,7 +236,7 @@ export class TagModelService { }); if (!tag) { throw new HttpException( - `Tag with ID ${uuid} not found.`, + `Tag model with ID ${uuid} not found.`, HttpStatus.NOT_FOUND, ); } diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index 353acf2..e682b11 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -11,6 +11,7 @@ import { ValidateNested, } from 'class-validator'; import { AddSubspaceDto } from './subspace'; +import { CreateTagDto } from './tag'; export class AddSpaceDto { @ApiProperty({ @@ -77,6 +78,15 @@ export class AddSpaceDto { @ValidateNested({ each: true }) @Type(() => AddSubspaceDto) subspaces?: AddSubspaceDto[]; + + @ApiProperty({ + description: 'List of tags associated with the space model', + type: [CreateTagDto], + }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CreateTagDto) + tags?: CreateTagDto[]; } export class AddUserSpaceDto { diff --git a/src/space/dtos/index.ts b/src/space/dtos/index.ts index 3c85266..506efa9 100644 --- a/src/space/dtos/index.ts +++ b/src/space/dtos/index.ts @@ -5,3 +5,4 @@ export * from './user-space.param'; export * from './subspace'; export * from './project.param.dto'; export * from './update.space.dto'; +export * from './tag'; diff --git a/src/space/dtos/subspace/add.subspace.dto.ts b/src/space/dtos/subspace/add.subspace.dto.ts index a2b12e2..98b381a 100644 --- a/src/space/dtos/subspace/add.subspace.dto.ts +++ b/src/space/dtos/subspace/add.subspace.dto.ts @@ -1,5 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsArray, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; +import { CreateTagDto } from '../tag'; +import { Type } from 'class-transformer'; export class AddSubspaceDto { @ApiProperty({ @@ -9,4 +11,13 @@ export class AddSubspaceDto { @IsNotEmpty() @IsString() subspaceName: string; + + @ApiProperty({ + description: 'List of tags associated with the subspace', + type: [CreateTagDto], + }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => CreateTagDto) + tags?: CreateTagDto[]; } diff --git a/src/space/dtos/subspace/delete.subspace.dto.ts b/src/space/dtos/subspace/delete.subspace.dto.ts new file mode 100644 index 0000000..3329a14 --- /dev/null +++ b/src/space/dtos/subspace/delete.subspace.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class DeleteSubspaceDto { + @ApiProperty({ + description: 'Uuid of the subspace model need to be deleted', + example: '982fc3a3-64dc-4afb-a5b5-65ee8fef0424', + }) + @IsNotEmpty() + @IsString() + subspaceUuid: string; +} diff --git a/src/space/dtos/subspace/index.ts b/src/space/dtos/subspace/index.ts index 0463a85..0071b44 100644 --- a/src/space/dtos/subspace/index.ts +++ b/src/space/dtos/subspace/index.ts @@ -1,3 +1,6 @@ export * from './add.subspace.dto'; export * from './get.subspace.param'; export * from './add.subspace-device.param'; +export * from './update.subspace.dto'; +export * from './delete.subspace.dto'; +export * from './modify.subspace.dto'; diff --git a/src/space/dtos/subspace/modify.subspace.dto.ts b/src/space/dtos/subspace/modify.subspace.dto.ts new file mode 100644 index 0000000..39a40e6 --- /dev/null +++ b/src/space/dtos/subspace/modify.subspace.dto.ts @@ -0,0 +1,47 @@ +import { ModifyAction } from '@app/common/constants/modify-action.enum'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsEnum, + IsOptional, + IsString, + IsArray, + ValidateNested, +} from 'class-validator'; +import { ModifyTagDto } from '../tag/modify-tag.dto'; + +export class ModifySubspaceDto { + @ApiProperty({ + description: 'Action to perform: add, update, or delete', + example: ModifyAction.ADD, + }) + @IsEnum(ModifyAction) + action: ModifyAction; + + @ApiPropertyOptional({ + description: 'UUID of the subspace (required for update/delete)', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @IsOptional() + @IsString() + uuid?: string; + + @ApiPropertyOptional({ + description: 'Name of the subspace (required for add/update)', + example: 'Living Room', + }) + @IsOptional() + @IsString() + subspaceName?: string; + + @ApiPropertyOptional({ + description: + 'List of tag modifications (add/update/delete) for the subspace', + type: [ModifyTagDto], + }) + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => ModifyTagDto) + tags?: ModifyTagDto[]; +} diff --git a/src/space/dtos/subspace/update.subspace.dto.ts b/src/space/dtos/subspace/update.subspace.dto.ts new file mode 100644 index 0000000..0931d9e --- /dev/null +++ b/src/space/dtos/subspace/update.subspace.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class UpdateSubspaceDto { + @ApiProperty({ + description: 'Name of the subspace', + example: 'Living Room', + }) + @IsNotEmpty() + @IsString() + subspaceName?: string; + + @IsNotEmpty() + @IsString() + subspaceUuid: string; +} diff --git a/src/space/dtos/tag/create-tag-dto.ts b/src/space/dtos/tag/create-tag-dto.ts new file mode 100644 index 0000000..8e89155 --- /dev/null +++ b/src/space/dtos/tag/create-tag-dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class CreateTagDto { + @ApiProperty({ + description: 'Tag associated with the space or subspace', + example: 'Temperature Control', + }) + @IsNotEmpty() + @IsString() + tag: string; + + @ApiProperty({ + description: 'ID of the product associated with the tag', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @IsNotEmpty() + @IsString() + productUuid: string; +} diff --git a/src/space/dtos/tag/index.ts b/src/space/dtos/tag/index.ts new file mode 100644 index 0000000..599ba4b --- /dev/null +++ b/src/space/dtos/tag/index.ts @@ -0,0 +1 @@ +export * from './create-tag-dto'; diff --git a/src/space/dtos/tag/modify-tag.dto.ts b/src/space/dtos/tag/modify-tag.dto.ts new file mode 100644 index 0000000..6088a2a --- /dev/null +++ b/src/space/dtos/tag/modify-tag.dto.ts @@ -0,0 +1,37 @@ +import { ModifyAction } from '@app/common/constants/modify-action.enum'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsOptional, IsString } from 'class-validator'; + +export class ModifyTagDto { + @ApiProperty({ + description: 'Action to perform: add, update, or delete', + example: ModifyAction.ADD, + }) + @IsEnum(ModifyAction) + action: ModifyAction; + + @ApiPropertyOptional({ + description: 'UUID of the tag (required for update/delete)', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @IsOptional() + @IsString() + uuid?: string; + + @ApiPropertyOptional({ + description: 'Name of the tag (required for add/update)', + example: 'Temperature Sensor', + }) + @IsOptional() + @IsString() + tag?: string; + + @ApiPropertyOptional({ + description: + 'UUID of the product associated with the tag (required for add)', + example: 'c789a91e-549a-4753-9006-02f89e8170e0', + }) + @IsOptional() + @IsString() + productUuid?: string; +} diff --git a/src/space/dtos/update.space.dto.ts b/src/space/dtos/update.space.dto.ts index d40476b..ae50dea 100644 --- a/src/space/dtos/update.space.dto.ts +++ b/src/space/dtos/update.space.dto.ts @@ -1,4 +1,59 @@ -import { PartialType } from '@nestjs/swagger'; -import { AddSpaceDto } from './add.space.dto'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsArray, + IsNumber, + IsOptional, + IsString, + ValidateNested, +} from 'class-validator'; +import { ModifySubspaceDto } from './subspace'; +import { Type } from 'class-transformer'; +import { ModifyTagDto } from './tag/modify-tag.dto'; -export class UpdateSpaceDto extends PartialType(AddSpaceDto) {} +export class UpdateSpaceDto { + @ApiProperty({ + description: 'Updated name of the space ', + example: 'New Space Name', + }) + @IsOptional() + @IsString() + spaceName?: string; + + @ApiProperty({ + description: 'Icon identifier for the space', + example: 'assets/location', + required: false, + }) + @IsString() + @IsOptional() + public icon?: string; + + @ApiProperty({ description: 'X position on canvas', example: 120 }) + @IsNumber() + x: number; + + @ApiProperty({ description: 'Y position on canvas', example: 200 }) + @IsNumber() + y: number; + + @ApiPropertyOptional({ + description: 'List of subspace modifications (add/update/delete)', + type: [ModifySubspaceDto], + }) + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => ModifySubspaceDto) + subspaceModels?: ModifySubspaceDto[]; + + @ApiPropertyOptional({ + description: + 'List of tag modifications (add/update/delete) for the space model', + type: [ModifyTagDto], + }) + @IsOptional() + @IsArray() + @ValidateNested({ each: true }) + @Type(() => ModifyTagDto) + tags?: ModifyTagDto[]; +} diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 43b8f4e..7498981 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -16,11 +16,11 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SpaceEntity } from '@app/common/modules/space/entities'; import { generateRandomString } from '@app/common/helper/randomString'; import { SpaceLinkService } from './space-link'; -import { CreateSubspaceModelDto } from 'src/space-model/dtos'; import { SubSpaceService } from './subspace'; import { DataSource, Not } from 'typeorm'; import { ValidationService } from './space-validation.service'; import { ORPHAN_SPACE_NAME } from '@app/common/constants/orphan-constant'; +import { TagService } from './tag'; @Injectable() export class SpaceService { @@ -30,13 +30,15 @@ export class SpaceService { private readonly spaceLinkService: SpaceLinkService, private readonly subSpaceService: SubSpaceService, private readonly validationService: ValidationService, + private readonly tagService: TagService, ) {} async createSpace( addSpaceDto: AddSpaceDto, params: CommunitySpaceParam, ): Promise { - const { parentUuid, direction, spaceModelUuid, subspaces } = addSpaceDto; + const { parentUuid, direction, spaceModelUuid, subspaces, tags } = + addSpaceDto; const { communityUuid, projectUuid } = params; if (addSpaceDto.spaceName === ORPHAN_SPACE_NAME) { @@ -67,14 +69,14 @@ export class SpaceService { : null; try { - const newSpace = queryRunner.manager.create(SpaceEntity, { + const space = queryRunner.manager.create(SpaceEntity, { ...addSpaceDto, spaceModel, parent: parentUuid ? parent : null, community, }); - await queryRunner.manager.save(newSpace); + const newSpace = await queryRunner.manager.save(space); if (direction && parent) { await this.spaceLinkService.saveSpaceLink( @@ -90,11 +92,14 @@ export class SpaceService { newSpace, queryRunner, ); - } else if (spaceModel && spaceModel.subspaceModels.length) { - await this.subSpaceService.createSubSpaceFromModel( - spaceModel, - newSpace, + } + + if (tags?.length) { + newSpace.tags = await this.tagService.createTags( + tags, queryRunner, + newSpace, + null, ); } @@ -242,19 +247,32 @@ export class SpaceService { HttpStatus.BAD_REQUEST, ); } + if (updateSpaceDto.spaceName) space.spaceName = updateSpaceDto.spaceName; - // If a parentId is provided, check if the parent exists - const { parentUuid } = updateSpaceDto; - const parent = parentUuid - ? await this.validationService.validateSpace(parentUuid) - : null; + if (updateSpaceDto.x) space.x = updateSpaceDto.x; - // Update other space properties from updateSpaceDto - Object.assign(space, updateSpaceDto, { parent }); + if (updateSpaceDto.y) space.y = updateSpaceDto.y; + if (updateSpaceDto.icon) space.icon = updateSpaceDto.icon; + if (updateSpaceDto.icon) space.icon = updateSpaceDto.icon; - // Save the updated space await queryRunner.manager.save(space); + if (updateSpaceDto.subspaceModels) { + await this.subSpaceService.modifySubSpace( + updateSpaceDto.subspaceModels, + space, + queryRunner, + ); + } + + if (updateSpaceDto.tags) { + await this.tagService.modifyTags( + updateSpaceDto.tags, + queryRunner, + space, + ); + } + await queryRunner.commitTransaction(); return new SuccessResponseDto({ diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 9f553fd..8795ff7 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -1,6 +1,13 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { AddSubspaceDto, GetSpaceParam, GetSubSpaceParam } from '../../dtos'; +import { + AddSubspaceDto, + CreateTagDto, + DeleteSubspaceDto, + GetSpaceParam, + GetSubSpaceParam, + ModifySubspaceDto, +} from '../../dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { TypeORMCustomModel, @@ -19,12 +26,15 @@ import { } from '@app/common/modules/space-model'; import { ValidationService } from '../space-validation.service'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; +import { TagService } from '../tag'; +import { ModifyAction } from '@app/common/constants/modify-action.enum'; @Injectable() export class SubSpaceService { constructor( private readonly subspaceRepository: SubspaceRepository, private readonly validationService: ValidationService, + private readonly tagService: TagService, ) {} async createSubspaces( @@ -71,6 +81,7 @@ export class SubSpaceService { addSubspaceDtos: AddSubspaceDto[], space: SpaceEntity, queryRunner: QueryRunner, + otherTags?: CreateTagDto[], ): Promise { try { const subspaceData = addSubspaceDtos.map((dto) => ({ @@ -80,6 +91,20 @@ export class SubSpaceService { const subspaces = await this.createSubspaces(subspaceData, queryRunner); + await Promise.all( + addSubspaceDtos.map(async (dto, index) => { + const subspace = subspaces[index]; + if (dto.tags?.length) { + subspace.tags = await this.tagService.createTags( + dto.tags, + queryRunner, + null, + subspace, + otherTags, + ); + } + }), + ); return subspaces; } catch (error) { throw new Error( @@ -144,43 +169,6 @@ export class SubSpaceService { } } - async findOne(params: GetSubSpaceParam): Promise { - const { communityUuid, subSpaceUuid, spaceUuid, projectUuid } = params; - await this.validationService.validateSpaceWithinCommunityAndProject( - communityUuid, - projectUuid, - spaceUuid, - ); - try { - const subSpace = await this.subspaceRepository.findOne({ - where: { - uuid: subSpaceUuid, - }, - }); - - // If space is not found, throw a NotFoundException - if (!subSpace) { - throw new HttpException( - `Sub Space with UUID ${subSpaceUuid} not found`, - HttpStatus.NOT_FOUND, - ); - } - return new SuccessResponseDto({ - message: `Subspace with ID ${subSpaceUuid} successfully fetched`, - data: subSpace, - }); - } catch (error) { - if (error instanceof HttpException) { - throw error; // If it's an HttpException, rethrow it - } else { - throw new HttpException( - 'An error occurred while deleting the subspace', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - } - async updateSubSpace( params: GetSubSpaceParam, updateSubSpaceDto: AddSubspaceDto, @@ -256,4 +244,153 @@ export class SubSpaceService { ); } } + + async deleteSubspaces( + deleteDtos: DeleteSubspaceDto[], + queryRunner: QueryRunner, + ) { + const deleteResults: { uuid: string }[] = []; + + for (const dto of deleteDtos) { + const subspaceModel = await this.findOne(dto.subspaceUuid); + + await queryRunner.manager.update( + this.subspaceRepository.target, + { uuid: dto.subspaceUuid }, + { disabled: true }, + ); + + if (subspaceModel.tags?.length) { + const modifyTagDtos = subspaceModel.tags.map((tag) => ({ + uuid: tag.uuid, + action: ModifyAction.DELETE, + })); + await this.tagService.modifyTags( + modifyTagDtos, + queryRunner, + null, + subspaceModel, + ); + } + + deleteResults.push({ uuid: dto.subspaceUuid }); + } + + return deleteResults; + } + + async modifySubSpace( + subspaceDtos: ModifySubspaceDto[], + space: SpaceEntity, + queryRunner: QueryRunner, + ): Promise { + for (const subspace of subspaceDtos) { + switch (subspace.action) { + case ModifyAction.ADD: + await this.handleAddAction(subspace, space, queryRunner); + break; + case ModifyAction.UPDATE: + await this.handleUpdateAction(subspace, queryRunner); + break; + case ModifyAction.DELETE: + await this.handleDeleteAction(subspace, queryRunner); + break; + default: + throw new HttpException( + `Invalid action "${subspace.action}".`, + HttpStatus.BAD_REQUEST, + ); + } + } + } + + private async handleAddAction( + subspace: ModifySubspaceDto, + space: SpaceEntity, + queryRunner: QueryRunner, + ): Promise { + const createTagDtos: CreateTagDto[] = + subspace.tags?.map((tag) => ({ + tag: tag.tag as string, + productUuid: tag.productUuid as string, + })) || []; + await this.createSubspacesFromDto( + [{ subspaceName: subspace.subspaceName, tags: createTagDtos }], + space, + queryRunner, + ); + } + + private async handleUpdateAction( + modifyDto: ModifySubspaceDto, + queryRunner: QueryRunner, + ): Promise { + const subspace = await this.findOne(modifyDto.uuid); + + await this.updateSubspaceName( + queryRunner, + subspace, + modifyDto.subspaceName, + ); + + if (modifyDto.tags?.length) { + await this.tagService.modifyTags( + modifyDto.tags, + queryRunner, + null, + subspace, + ); + } + } + + private async handleDeleteAction( + subspace: ModifySubspaceDto, + queryRunner: QueryRunner, + ): Promise { + const subspaceModel = await this.findOne(subspace.uuid); + + await queryRunner.manager.update( + this.subspaceRepository.target, + { uuid: subspace.uuid }, + { disabled: true }, + ); + + if (subspaceModel.tags?.length) { + const modifyTagDtos = subspaceModel.tags.map((tag) => ({ + uuid: tag.uuid, + action: ModifyAction.DELETE, + })); + await this.tagService.modifyTags( + modifyTagDtos, + queryRunner, + null, + subspaceModel, + ); + } + } + + private async findOne(subspaceUuid: string): Promise { + const subspace = await this.subspaceRepository.findOne({ + where: { uuid: subspaceUuid }, + relations: ['tags'], + }); + if (!subspace) { + throw new HttpException( + `SubspaceModel with UUID ${subspaceUuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + return subspace; + } + + private async updateSubspaceName( + queryRunner: QueryRunner, + subSpace: SubspaceEntity, + subspaceName?: string, + ): Promise { + if (subspaceName) { + subSpace.subspaceName = subspaceName; + await queryRunner.manager.save(subSpace); + } + } } diff --git a/src/space/services/tag/index.ts b/src/space/services/tag/index.ts new file mode 100644 index 0000000..0cbeec4 --- /dev/null +++ b/src/space/services/tag/index.ts @@ -0,0 +1 @@ +export * from './tag.service'; diff --git a/src/space/services/tag/tag.service.ts b/src/space/services/tag/tag.service.ts new file mode 100644 index 0000000..9514cc4 --- /dev/null +++ b/src/space/services/tag/tag.service.ts @@ -0,0 +1,237 @@ +import { ModifyAction } from '@app/common/constants/modify-action.enum'; +import { + SpaceEntity, + SubspaceEntity, + TagEntity, + TagRepository, +} from '@app/common/modules/space'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { ProductService } from 'src/product/services'; +import { CreateTagDto } from 'src/space/dtos'; +import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto'; +import { QueryRunner } from 'typeorm'; + +@Injectable() +export class TagService { + constructor( + private readonly tagRepository: TagRepository, + private readonly productService: ProductService, + ) {} + + async createTags( + tags: CreateTagDto[], + queryRunner: QueryRunner, + space?: SpaceEntity, + subspace?: SubspaceEntity, + additionalTags?: CreateTagDto[], + ): Promise { + if (!tags.length) { + throw new HttpException('Tags cannot be empty.', HttpStatus.BAD_REQUEST); + } + + const combinedTags = additionalTags ? [...tags, ...additionalTags] : tags; + const duplicateTags = this.findDuplicateTags(combinedTags); + + if (duplicateTags.length > 0) { + throw new HttpException( + `Duplicate tags found for the same product: ${duplicateTags.join(', ')}`, + HttpStatus.BAD_REQUEST, + ); + } + + const tagEntities = await Promise.all( + tags.map(async (tagDto) => + this.prepareTagEntity(tagDto, queryRunner, space, subspace), + ), + ); + try { + return await queryRunner.manager.save(tagEntities); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + 'Failed to save tag models due to an unexpected error.', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async updateTag( + tag: ModifyTagDto, + queryRunner: QueryRunner, + space?: SpaceEntity, + subspace?: SubspaceEntity, + ): Promise { + try { + const existingTag = await this.getTagByUuid(tag.uuid); + + if (space) { + await this.checkTagReuse(tag.tag, existingTag.product.uuid, space); + } else { + await this.checkTagReuse( + tag.tag, + existingTag.product.uuid, + subspace.space, + ); + } + + if (tag.tag) { + existingTag.tag = tag.tag; + } + + return await queryRunner.manager.save(existingTag); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( + error.message || 'Failed to update tags', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async deleteTags(tagUuids: string[], queryRunner: QueryRunner) { + try { + const deletePromises = tagUuids.map((id) => + queryRunner.manager.softDelete(this.tagRepository.target, id), + ); + + await Promise.all(deletePromises); + return { message: 'Tags deleted successfully', tagUuids }; + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( + error.message || 'Failed to delete tags', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private findDuplicateTags(tags: CreateTagDto[]): string[] { + const seen = new Map(); + const duplicates: string[] = []; + + tags.forEach((tagDto) => { + const key = `${tagDto.productUuid}-${tagDto.tag}`; + if (seen.has(key)) { + duplicates.push(`${tagDto.tag} for Product: ${tagDto.productUuid}`); + } else { + seen.set(key, true); + } + }); + + return duplicates; + } + + async modifyTags( + tags: ModifyTagDto[], + queryRunner: QueryRunner, + space?: SpaceEntity, + subspace?: SubspaceEntity, + ): Promise { + try { + console.log(tags); + for (const tag of tags) { + if (tag.action === ModifyAction.ADD) { + const createTagDto: CreateTagDto = { + tag: tag.tag as string, + productUuid: tag.productUuid as string, + }; + + await this.createTags([createTagDto], queryRunner, space, subspace); + } else if (tag.action === ModifyAction.UPDATE) { + await this.updateTag(tag, queryRunner, space, subspace); + } else if (tag.action === ModifyAction.DELETE) { + await queryRunner.manager.update( + this.tagRepository.target, + { uuid: tag.uuid }, + { disabled: true }, + ); + } else { + throw new HttpException( + `Invalid action "${tag.action}" provided.`, + HttpStatus.BAD_REQUEST, + ); + } + } + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + `An error occurred while modifying tags: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private async checkTagReuse( + tag: string, + productUuid: string, + space: SpaceEntity, + ): Promise { + const isTagInSpace = await this.tagRepository.exists({ + where: { tag, space, product: { uuid: productUuid } }, + }); + const isTagInSubspace = await this.tagRepository.exists({ + where: { + tag, + subspace: { space }, + product: { uuid: productUuid }, + }, + }); + + if (isTagInSpace || isTagInSubspace) { + throw new HttpException(`Tag can't be reused`, HttpStatus.CONFLICT); + } + } + + private async prepareTagEntity( + tagDto: CreateTagDto, + queryRunner: QueryRunner, + space?: SpaceEntity, + subspace?: SubspaceEntity, + ): Promise { + const product = await this.productService.findOne(tagDto.productUuid); + + if (!product) { + throw new HttpException( + `Product with UUID ${tagDto.productUuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + + if (space) { + await this.checkTagReuse(tagDto.tag, tagDto.productUuid, space); + } else { + await this.checkTagReuse(tagDto.tag, tagDto.productUuid, subspace.space); + } + + return queryRunner.manager.create(TagEntity, { + tag: tagDto.tag, + product: product.data, + space, + subspace, + }); + } + + private async getTagByUuid(uuid: string): Promise { + const tag = await this.tagRepository.findOne({ + where: { uuid }, + relations: ['product'], + }); + if (!tag) { + throw new HttpException( + `Tag with ID ${uuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + return tag; + } +} diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 5d16689..a17d95e 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -21,6 +21,7 @@ import { import { SpaceRepository, SpaceLinkRepository, + TagRepository, } from '@app/common/modules/space/repositories'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { @@ -40,10 +41,14 @@ import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories'; import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; -import { SpaceModelRepository } from '@app/common/modules/space-model'; +import { + SpaceModelRepository, + TagModelRepository, +} from '@app/common/modules/space-model'; import { CommunityModule } from 'src/community/community.module'; import { ValidationService } from './services'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; +import { TagService } from './services/tag'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, CommunityModule], @@ -57,8 +62,11 @@ import { SubspaceRepository } from '@app/common/modules/space/repositories/subsp ], providers: [ ValidationService, + TagModelRepository, + TagRepository, SpaceService, TuyaService, + TagService, ProductRepository, SubSpaceService, SpaceDeviceService, From 2e46176fd51d4bf254601e465f551aa0069bb729 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 24 Dec 2024 14:51:06 +0400 Subject: [PATCH 121/247] added space delete --- .../services/space-model.service.ts | 7 ++ .../subspace/subspace-model.service.ts | 52 ++++++++++--- src/space-model/services/tag-model.service.ts | 26 ++++--- .../subspace/subspace.controller.ts | 2 +- src/space/dtos/update.space.dto.ts | 6 +- .../services/space-validation.service.ts | 1 + src/space/services/space.service.ts | 44 +++++++++-- .../services/subspace/subspace.service.ts | 77 +++++++++++++++++-- src/space/services/tag/tag.service.ts | 29 ++++--- 9 files changed, 200 insertions(+), 44 deletions(-) diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 40d9970..ad89d37 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -199,6 +199,12 @@ export class SpaceModelService { ); } + if (spaceModel.tags?.length) { + const deleteSpaceTagsDtos = spaceModel.tags.map((tag) => tag.uuid); + + await this.tagModelService.deleteTags(deleteSpaceTagsDtos, queryRunner); + } + await queryRunner.manager.update( this.spaceModelRepository.target, { uuid: param.spaceModelUuid }, @@ -248,6 +254,7 @@ export class SpaceModelService { uuid, disabled: true, }, + relations: ['subspaceModels', 'tags'], }); if (!spaceModel) { throw new HttpException('space model not found', HttpStatus.NOT_FOUND); diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index 507a16a..6b8cb9f 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -5,7 +5,7 @@ import { } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { CreateSubspaceModelDto, CreateTagModelDto } from '../../dtos'; -import { QueryRunner } from 'typeorm'; +import { In, QueryRunner } from 'typeorm'; import { IDeletedSubsaceModelInterface } from 'src/space-model/interfaces'; import { DeleteSubspaceModelDto, @@ -27,7 +27,7 @@ export class SubSpaceModelService { queryRunner: QueryRunner, otherTags?: CreateTagModelDto[], ): Promise { - this.validateInputDtos(subSpaceModelDtos); + this.validateInputDtos(subSpaceModelDtos, spaceModel); const subspaces = subSpaceModelDtos.map((subspaceDto) => queryRunner.manager.create(this.subspaceModelRepository.target, { @@ -41,13 +41,18 @@ export class SubSpaceModelService { await Promise.all( subSpaceModelDtos.map(async (dto, index) => { const subspace = savedSubspaces[index]; + + const otherDtoTags = subSpaceModelDtos + .filter((_, i) => i !== index) + .flatMap((otherDto) => otherDto.tags || []); + if (dto.tags?.length) { subspace.tags = await this.tagModelService.createTags( dto.tags, queryRunner, null, subspace, - otherTags, + [...(otherTags || []), ...otherDtoTags], ); } }), @@ -194,34 +199,61 @@ export class SubSpaceModelService { return subspace; } - private validateInputDtos(subSpaceModelDtos: CreateSubspaceModelDto[]): void { + private validateInputDtos( + subSpaceModelDtos: CreateSubspaceModelDto[], + spaceModel: SpaceModelEntity, + ): void { if (subSpaceModelDtos.length === 0) { throw new HttpException( 'Subspace models cannot be empty.', HttpStatus.BAD_REQUEST, ); } - this.validateName(subSpaceModelDtos.map((dto) => dto.subspaceName)); + this.validateName( + subSpaceModelDtos.map((dto) => dto.subspaceName), + spaceModel, + ); } - private validateName(names: string[]): void { + private async validateName( + names: string[], + spaceModel: SpaceModelEntity, + ): Promise { const seenNames = new Set(); const duplicateNames = new Set(); for (const name of names) { - if (seenNames.has(name)) { + if (!seenNames.add(name)) { duplicateNames.add(name); - } else { - seenNames.add(name); } } if (duplicateNames.size > 0) { throw new HttpException( - `Duplicate subspace names found: ${[...duplicateNames].join(', ')}`, + `Duplicate subspace model names found: ${[...duplicateNames].join(', ')}`, HttpStatus.CONFLICT, ); } + + const existingNames = await this.subspaceModelRepository.find({ + select: ['subspaceName'], + where: { + subspaceName: In([...seenNames]), + spaceModel: { + uuid: spaceModel.uuid, + }, + }, + }); + + if (existingNames.length > 0) { + const existingNamesList = existingNames + .map((e) => e.subspaceName) + .join(', '); + throw new HttpException( + `Subspace model names already exist in the space: ${existingNamesList}`, + HttpStatus.BAD_REQUEST, + ); + } } private async updateSubspaceName( diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index 8207e0b..5682bc2 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -180,18 +180,24 @@ export class TagModelService { productUuid: string, spaceModel: SpaceModelEntity, ): Promise { - const isTagInSpaceModel = await this.tagModelRepository.exists({ - where: { tag, spaceModel, product: { uuid: productUuid } }, - }); - const isTagInSubspaceModel = await this.tagModelRepository.exists({ - where: { - tag, - subspaceModel: { spaceModel }, - product: { uuid: productUuid }, - }, + const tagExists = await this.tagModelRepository.exists({ + where: [ + { + tag, + spaceModel: { uuid: spaceModel.uuid }, + product: { uuid: productUuid }, + disabled: false, + }, + { + tag, + subspaceModel: { spaceModel: { uuid: spaceModel.uuid } }, + product: { uuid: productUuid }, + disabled: false, + }, + ], }); - if (isTagInSpaceModel || isTagInSubspaceModel) { + if (tagExists) { throw new HttpException(`Tag can't be reused`, HttpStatus.CONFLICT); } } diff --git a/src/space/controllers/subspace/subspace.controller.ts b/src/space/controllers/subspace/subspace.controller.ts index 37e264b..3ed36af 100644 --- a/src/space/controllers/subspace/subspace.controller.ts +++ b/src/space/controllers/subspace/subspace.controller.ts @@ -65,7 +65,7 @@ export class SubSpaceController { }) @Get(':subSpaceUuid') async findOne(@Param() params: GetSubSpaceParam): Promise { - return this.subSpaceService.findOne(params); + return this.subSpaceService.getOne(params); } @ApiBearerAuth() diff --git a/src/space/dtos/update.space.dto.ts b/src/space/dtos/update.space.dto.ts index ae50dea..9bc7950 100644 --- a/src/space/dtos/update.space.dto.ts +++ b/src/space/dtos/update.space.dto.ts @@ -30,11 +30,13 @@ export class UpdateSpaceDto { @ApiProperty({ description: 'X position on canvas', example: 120 }) @IsNumber() - x: number; + @IsOptional() + x?: number; @ApiProperty({ description: 'Y position on canvas', example: 200 }) @IsNumber() - y: number; + @IsOptional() + y?: number; @ApiPropertyOptional({ description: 'List of subspace modifications (add/update/delete)', diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts index fa4a5f7..77ff011 100644 --- a/src/space/services/space-validation.service.ts +++ b/src/space/services/space-validation.service.ts @@ -44,6 +44,7 @@ export class ValidationService { async validateSpace(spaceUuid: string): Promise { const space = await this.spaceRepository.findOne({ where: { uuid: spaceUuid }, + relations: ['subspaces', 'tags'], }); if (!space) { diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 7498981..0bdbff9 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -58,7 +58,7 @@ export class SpaceService { projectUuid, ); - this.validateSpaceCreation(spaceModelUuid); + this.validateSpaceCreation(addSpaceDto, spaceModelUuid); const parent = parentUuid ? await this.validationService.validateSpace(parentUuid) @@ -91,6 +91,7 @@ export class SpaceService { subspaces, newSpace, queryRunner, + tags, ); } @@ -189,6 +190,10 @@ export class SpaceService { } async delete(params: GetSpaceParam): Promise { + const queryRunner = this.dataSource.createQueryRunner(); + + await queryRunner.connect(); + await queryRunner.startTransaction(); try { const { communityUuid, spaceUuid, projectUuid } = params; @@ -205,14 +210,38 @@ export class SpaceService { HttpStatus.BAD_REQUEST, ); } - // Delete the space - await this.spaceRepository.remove(space); + + if (space.tags?.length) { + const deleteSpaceTagsDtos = space.tags.map((tag) => tag.uuid); + await this.tagService.deleteTags(deleteSpaceTagsDtos, queryRunner); + } + + if (space.subspaces?.length) { + const deleteSubspaceDtos = space.subspaces.map((subspace) => ({ + subspaceUuid: subspace.uuid, + })); + + await this.subSpaceService.deleteSubspaces( + deleteSubspaceDtos, + queryRunner, + ); + } + + await queryRunner.manager.update( + this.spaceRepository.target, + { uuid: params.spaceUuid }, + { disabled: true }, + ); + + await queryRunner.commitTransaction(); return new SuccessResponseDto({ message: `Space with ID ${spaceUuid} successfully deleted`, statusCode: HttpStatus.OK, }); } catch (error) { + await queryRunner.rollbackTransaction(); + if (error instanceof HttpException) { throw error; } @@ -220,6 +249,8 @@ export class SpaceService { 'An error occurred while deleting the space', HttpStatus.INTERNAL_SERVER_ERROR, ); + } finally { + await queryRunner.release(); } } @@ -384,8 +415,11 @@ export class SpaceService { return rootSpaces; } - private validateSpaceCreation(spaceModelUuid?: string) { - if (spaceModelUuid) { + private validateSpaceCreation( + addSpaceDto: AddSpaceDto, + spaceModelUuid?: string, + ) { + if (spaceModelUuid && (addSpaceDto.tags || addSpaceDto.subspaces)) { throw new HttpException( 'For space creation choose either space model or products and subspace', HttpStatus.CONFLICT, diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 8795ff7..519608d 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -15,7 +15,7 @@ import { } from '@app/common/models/typeOrmCustom.model'; import { PageResponse } from '@app/common/dto/pagination.response.dto'; import { SubspaceDto } from '@app/common/modules/space/dtos'; -import { QueryRunner } from 'typeorm'; +import { In, QueryRunner } from 'typeorm'; import { SpaceEntity, SubspaceEntity, @@ -84,6 +84,11 @@ export class SubSpaceService { otherTags?: CreateTagDto[], ): Promise { try { + await this.validateName( + addSubspaceDtos.map((dto) => dto.subspaceName), + space, + ); + const subspaceData = addSubspaceDtos.map((dto) => ({ subspaceName: dto.subspaceName, space, @@ -93,6 +98,9 @@ export class SubSpaceService { await Promise.all( addSubspaceDtos.map(async (dto, index) => { + const otherDtoTags = addSubspaceDtos + .filter((_, i) => i !== index) + .flatMap((otherDto) => otherDto.tags || []); const subspace = subspaces[index]; if (dto.tags?.length) { subspace.tags = await this.tagService.createTags( @@ -100,15 +108,20 @@ export class SubSpaceService { queryRunner, null, subspace, - otherTags, + [...(otherTags || []), ...otherDtoTags], ); } }), ); return subspaces; } catch (error) { - throw new Error( - `Transaction failed: Unable to create subspaces and products. ${error.message}`, + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + 'Failed to save subspaces due to an unexpected error.', + HttpStatus.INTERNAL_SERVER_ERROR, ); } } @@ -304,6 +317,19 @@ export class SubSpaceService { } } + async getOne(params: GetSubSpaceParam): Promise { + await this.validationService.validateSpaceWithinCommunityAndProject( + params.communityUuid, + params.projectUuid, + params.spaceUuid, + ); + const subspace = await this.findOne(params.subSpaceUuid); + return new SuccessResponseDto({ + message: `Successfully retrieved subspace`, + data: subspace, + }); + } + private async handleAddAction( subspace: ModifySubspaceDto, space: SpaceEntity, @@ -372,7 +398,7 @@ export class SubSpaceService { private async findOne(subspaceUuid: string): Promise { const subspace = await this.subspaceRepository.findOne({ where: { uuid: subspaceUuid }, - relations: ['tags'], + relations: ['tags', 'space'], }); if (!subspace) { throw new HttpException( @@ -393,4 +419,45 @@ export class SubSpaceService { await queryRunner.manager.save(subSpace); } } + + private async validateName( + names: string[], + space: SpaceEntity, + ): Promise { + const seenNames = new Set(); + const duplicateNames = new Set(); + + for (const name of names) { + if (!seenNames.add(name)) { + duplicateNames.add(name); + } + } + + if (duplicateNames.size > 0) { + throw new HttpException( + `Duplicate subspace names found: ${[...duplicateNames].join(', ')}`, + HttpStatus.CONFLICT, + ); + } + + const existingNames = await this.subspaceRepository.find({ + select: ['subspaceName'], + where: { + subspaceName: In([...seenNames]), + space: { + uuid: space.uuid, + }, + }, + }); + + if (existingNames.length > 0) { + const existingNamesList = existingNames + .map((e) => e.subspaceName) + .join(', '); + throw new HttpException( + `Subspace names already exist in the space: ${existingNamesList}`, + HttpStatus.BAD_REQUEST, + ); + } + } } diff --git a/src/space/services/tag/tag.service.ts b/src/space/services/tag/tag.service.ts index 9514cc4..173d91d 100644 --- a/src/space/services/tag/tag.service.ts +++ b/src/space/services/tag/tag.service.ts @@ -135,7 +135,6 @@ export class TagService { subspace?: SubspaceEntity, ): Promise { try { - console.log(tags); for (const tag of tags) { if (tag.action === ModifyAction.ADD) { const createTagDto: CreateTagDto = { @@ -176,18 +175,26 @@ export class TagService { productUuid: string, space: SpaceEntity, ): Promise { - const isTagInSpace = await this.tagRepository.exists({ - where: { tag, space, product: { uuid: productUuid } }, - }); - const isTagInSubspace = await this.tagRepository.exists({ - where: { - tag, - subspace: { space }, - product: { uuid: productUuid }, - }, + const { uuid: spaceUuid } = space; + + const tagExists = await this.tagRepository.exists({ + where: [ + { + tag, + space: { uuid: spaceUuid }, + product: { uuid: productUuid }, + disabled: false, + }, + { + tag, + subspace: { space: { uuid: spaceUuid } }, + product: { uuid: productUuid }, + disabled: false, + }, + ], }); - if (isTagInSpace || isTagInSubspace) { + if (tagExists) { throw new HttpException(`Tag can't be reused`, HttpStatus.CONFLICT); } } From a74a0db26e3a3878448bbddadc816443828c029c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 25 Dec 2024 10:52:48 +0400 Subject: [PATCH 122/247] updated space update --- .../entities/space-model.entity.ts | 5 + .../subspace-model/subspace-model.entity.ts | 4 +- .../modules/space/entities/space.entity.ts | 5 +- .../entities/subspace/subspace.entity.ts | 5 +- .../services/space-model.service.ts | 13 +- src/space-model/space-model.module.ts | 2 +- src/space/dtos/add.space.dto.ts | 2 - src/space/dtos/update.space.dto.ts | 2 +- .../services/space-validation.service.ts | 11 +- src/space/services/space.service.ts | 143 ++++++++--- .../services/subspace/subspace.service.ts | 53 +++- src/space/services/tag/tag.service.ts | 243 +++++++++++------- src/space/space.module.ts | 11 +- 13 files changed, 331 insertions(+), 168 deletions(-) diff --git a/libs/common/src/modules/space-model/entities/space-model.entity.ts b/libs/common/src/modules/space-model/entities/space-model.entity.ts index b108729..18d0688 100644 --- a/libs/common/src/modules/space-model/entities/space-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-model.entity.ts @@ -58,4 +58,9 @@ export class SpaceModelEntity extends AbstractEntity { @OneToMany(() => TagModel, (tag) => tag.spaceModel) tags: TagModel[]; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } } diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts index becb2f8..e1d0be0 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts @@ -30,10 +30,10 @@ export class SubspaceModelEntity extends AbstractEntity { ) public spaceModel: SpaceModelEntity; - @OneToMany(() => SubspaceEntity, (space) => space.subSpaceModel, { + @OneToMany(() => SubspaceEntity, (subspace) => subspace.subSpaceModel, { cascade: true, }) - public spaces: SubspaceEntity[]; + public subspaceModel: SubspaceEntity[]; @Column({ nullable: false, diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 889363f..8ecbc70 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -103,8 +103,9 @@ export class SpaceEntity extends AbstractEntity { @OneToMany(() => SceneEntity, (scene) => scene.space) scenes: SceneEntity[]; - @ManyToOne(() => SpaceModelEntity, { nullable: true }) - @JoinColumn({ name: 'space_model_uuid' }) + @ManyToOne(() => SpaceModelEntity, (spaceModel) => spaceModel.spaces, { + nullable: true, + }) spaceModel?: SpaceModelEntity; @OneToMany( diff --git a/libs/common/src/modules/space/entities/subspace/subspace.entity.ts b/libs/common/src/modules/space/entities/subspace/subspace.entity.ts index d2f86d9..c7247cc 100644 --- a/libs/common/src/modules/space/entities/subspace/subspace.entity.ts +++ b/libs/common/src/modules/space/entities/subspace/subspace.entity.ts @@ -37,8 +37,9 @@ export class SubspaceEntity extends AbstractEntity { }) devices: DeviceEntity[]; - @ManyToOne(() => SubspaceModelEntity, { nullable: true }) - @JoinColumn({ name: 'subspace_model_uuid' }) + @ManyToOne(() => SubspaceModelEntity, (subspace) => subspace.subspaceModel, { + nullable: true, + }) subSpaceModel?: SubspaceModelEntity; @OneToMany(() => TagEntity, (tag) => tag.subspace) diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index ad89d37..7704fd1 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -106,8 +106,7 @@ export class SpaceModelService { pageable.where = { project: { uuid: param.projectUuid }, }; - pageable.include = - 'subspaceModels,spaceProductModels,subspaceModels.productModels,subspaceModels.productModels.itemModels,spaceProductModels.items'; + pageable.include = 'subspaceModels,tags,subspaceModels.tags'; const customModel = TypeORMCustomModel(this.spaceModelRepository); @@ -252,9 +251,15 @@ export class SpaceModelService { const spaceModel = await this.spaceModelRepository.findOne({ where: { uuid, - disabled: true, + disabled: false, }, - relations: ['subspaceModels', 'tags'], + relations: [ + 'subspaceModels', + 'tags', + 'tags.product', + 'subspaceModels.tags', + 'subspaceModels.tags.product', + ], }); if (!spaceModel) { throw new HttpException('space model not found', HttpStatus.NOT_FOUND); diff --git a/src/space-model/space-model.module.ts b/src/space-model/space-model.module.ts index 736245b..bc3398d 100644 --- a/src/space-model/space-model.module.ts +++ b/src/space-model/space-model.module.ts @@ -37,6 +37,6 @@ const CommandHandlers = [PropogateSubspaceHandler]; TagModelService, TagModelRepository, ], - exports: [CqrsModule], + exports: [CqrsModule, SpaceModelService], }) export class SpaceModelModule {} diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index e682b11..a3c9a3b 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -74,7 +74,6 @@ export class AddSpaceDto { type: [AddSubspaceDto], }) @IsOptional() - @IsArray() @ValidateNested({ each: true }) @Type(() => AddSubspaceDto) subspaces?: AddSubspaceDto[]; @@ -83,7 +82,6 @@ export class AddSpaceDto { description: 'List of tags associated with the space model', type: [CreateTagDto], }) - @IsArray() @ValidateNested({ each: true }) @Type(() => CreateTagDto) tags?: CreateTagDto[]; diff --git a/src/space/dtos/update.space.dto.ts b/src/space/dtos/update.space.dto.ts index 9bc7950..02efb86 100644 --- a/src/space/dtos/update.space.dto.ts +++ b/src/space/dtos/update.space.dto.ts @@ -46,7 +46,7 @@ export class UpdateSpaceDto { @IsArray() @ValidateNested({ each: true }) @Type(() => ModifySubspaceDto) - subspaceModels?: ModifySubspaceDto[]; + subspace?: ModifySubspaceDto[]; @ApiPropertyOptional({ description: diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts index 77ff011..e8bfd32 100644 --- a/src/space/services/space-validation.service.ts +++ b/src/space/services/space-validation.service.ts @@ -43,7 +43,7 @@ export class ValidationService { async validateSpace(spaceUuid: string): Promise { const space = await this.spaceRepository.findOne({ - where: { uuid: spaceUuid }, + where: { uuid: spaceUuid, disabled: false }, relations: ['subspaces', 'tags'], }); @@ -62,11 +62,10 @@ export class ValidationService { where: { uuid: spaceModelUuid }, relations: [ 'subspaceModels', - 'subspaceModels.productModels.product', - 'subspaceModels.productModels', - 'spaceProductModels', - 'spaceProductModels.product', - 'spaceProductModels.items', + 'subspaceModels.tags', + 'tags', + 'subspaceModels.tags.product', + 'tags.product', ], }); diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 0bdbff9..d7ff346 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -7,7 +7,9 @@ import { } from '@nestjs/common'; import { AddSpaceDto, + AddSubspaceDto, CommunitySpaceParam, + CreateTagDto, GetSpaceParam, UpdateSpaceDto, } from '../dtos'; @@ -17,10 +19,11 @@ import { SpaceEntity } from '@app/common/modules/space/entities'; import { generateRandomString } from '@app/common/helper/randomString'; import { SpaceLinkService } from './space-link'; import { SubSpaceService } from './subspace'; -import { DataSource, Not } from 'typeorm'; +import { DataSource, Not, QueryRunner } from 'typeorm'; import { ValidationService } from './space-validation.service'; import { ORPHAN_SPACE_NAME } from '@app/common/constants/orphan-constant'; import { TagService } from './tag'; +import { SpaceModelService } from 'src/space-model/services'; @Injectable() export class SpaceService { @@ -31,6 +34,7 @@ export class SpaceService { private readonly subSpaceService: SubSpaceService, private readonly validationService: ValidationService, private readonly tagService: TagService, + private readonly spaceModelService: SpaceModelService, ) {} async createSpace( @@ -78,31 +82,23 @@ export class SpaceService { const newSpace = await queryRunner.manager.save(space); - if (direction && parent) { - await this.spaceLinkService.saveSpaceLink( - parent.uuid, - newSpace.uuid, - direction, - ); - } - - if (subspaces?.length) { - await this.subSpaceService.createSubspacesFromDto( - subspaces, - newSpace, - queryRunner, - tags, - ); - } - - if (tags?.length) { - newSpace.tags = await this.tagService.createTags( - tags, - queryRunner, - newSpace, - null, - ); - } + await Promise.all([ + spaceModelUuid && + this.createFromModel(spaceModelUuid, queryRunner, newSpace), + direction && parent + ? this.spaceLinkService.saveSpaceLink( + parent.uuid, + newSpace.uuid, + direction, + ) + : Promise.resolve(), + subspaces?.length + ? this.createSubspaces(subspaces, newSpace, queryRunner, tags) + : Promise.resolve(), + tags?.length + ? this.createTags(tags, queryRunner, newSpace) + : Promise.resolve(), + ]); await queryRunner.commitTransaction(); @@ -123,6 +119,41 @@ export class SpaceService { } } + async createFromModel( + spaceModelUuid: string, + queryRunner: QueryRunner, + space: SpaceEntity, + ) { + try { + const spaceModel = + await this.spaceModelService.validateSpaceModel(spaceModelUuid); + + space.spaceModel = spaceModel; + await queryRunner.manager.save(SpaceEntity, space); + + await this.subSpaceService.createSubSpaceFromModel( + spaceModel.subspaceModels, + space, + queryRunner, + ); + + await this.tagService.createTagsFromModel( + queryRunner, + spaceModel.tags, + space, + null, + ); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( + 'An error occurred while creating the space from space model', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getSpacesHierarchyForCommunity( params: CommunitySpaceParam, ): Promise { @@ -274,29 +305,32 @@ export class SpaceService { if (space.spaceName === ORPHAN_SPACE_NAME) { throw new HttpException( - `space ${ORPHAN_SPACE_NAME} cannot be updated`, + `Space "${ORPHAN_SPACE_NAME}" cannot be updated`, HttpStatus.BAD_REQUEST, ); } - if (updateSpaceDto.spaceName) space.spaceName = updateSpaceDto.spaceName; - if (updateSpaceDto.x) space.x = updateSpaceDto.x; - - if (updateSpaceDto.y) space.y = updateSpaceDto.y; - if (updateSpaceDto.icon) space.icon = updateSpaceDto.icon; - if (updateSpaceDto.icon) space.icon = updateSpaceDto.icon; + this.updateSpaceProperties(space, updateSpaceDto); await queryRunner.manager.save(space); - if (updateSpaceDto.subspaceModels) { + const hasSubspace = updateSpaceDto.subspace?.length > 0; + const hasTags = updateSpaceDto.tags?.length > 0; + + if (hasSubspace || hasTags) { + await this.tagService.unlinkModels(space.tags, queryRunner); + await this.subSpaceService.unlinkModels(space.subspaces, queryRunner); + } + + if (hasSubspace) { await this.subSpaceService.modifySubSpace( - updateSpaceDto.subspaceModels, + updateSpaceDto.subspace, space, queryRunner, ); } - if (updateSpaceDto.tags) { + if (hasTags) { await this.tagService.modifyTags( updateSpaceDto.tags, queryRunner, @@ -317,6 +351,7 @@ export class SpaceService { if (error instanceof HttpException) { throw error; } + throw new HttpException( 'An error occurred while updating the space', HttpStatus.INTERNAL_SERVER_ERROR, @@ -326,6 +361,18 @@ export class SpaceService { } } + private updateSpaceProperties( + space: SpaceEntity, + updateSpaceDto: UpdateSpaceDto, + ): void { + const { spaceName, x, y, icon } = updateSpaceDto; + + if (spaceName) space.spaceName = spaceName; + if (x) space.x = x; + if (y) space.y = y; + if (icon) space.icon = icon; + } + async getSpacesHierarchyForSpace( params: GetSpaceParam, ): Promise { @@ -339,7 +386,7 @@ export class SpaceService { try { // Get all spaces that are children of the provided space, including the parent-child relations const spaces = await this.spaceRepository.find({ - where: { parent: { uuid: spaceUuid } }, + where: { parent: { uuid: spaceUuid }, disabled: false }, relations: ['parent', 'children'], // Include parent and children relations }); @@ -426,4 +473,26 @@ export class SpaceService { ); } } + + private async createSubspaces( + subspaces: AddSubspaceDto[], + space: SpaceEntity, + queryRunner: QueryRunner, + tags: CreateTagDto[], + ): Promise { + space.subspaces = await this.subSpaceService.createSubspacesFromDto( + subspaces, + space, + queryRunner, + tags, + ); + } + + private async createTags( + tags: CreateTagDto[], + queryRunner: QueryRunner, + space: SpaceEntity, + ): Promise { + space.tags = await this.tagService.createTags(tags, queryRunner, space); + } } diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 519608d..01c3bd5 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -19,11 +19,9 @@ import { In, QueryRunner } from 'typeorm'; import { SpaceEntity, SubspaceEntity, + TagEntity, } from '@app/common/modules/space/entities'; -import { - SpaceModelEntity, - SubspaceModelEntity, -} from '@app/common/modules/space-model'; +import { SubspaceModelEntity } from '@app/common/modules/space-model'; import { ValidationService } from '../space-validation.service'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; import { TagService } from '../tag'; @@ -49,7 +47,6 @@ export class SubSpaceService { const subspaces = subspaceData.map((data) => queryRunner.manager.create(this.subspaceRepository.target, data), ); - return await queryRunner.manager.save(subspaces); } catch (error) { throw new HttpException( @@ -60,21 +57,30 @@ export class SubSpaceService { } async createSubSpaceFromModel( - spaceModel: SpaceModelEntity, + subspaceModels: SubspaceModelEntity[], space: SpaceEntity, queryRunner: QueryRunner, ): Promise { - const subSpaceModels = spaceModel.subspaceModels; + if (!subspaceModels?.length) return; - if (!subSpaceModels?.length) return; - - const subspaceData = subSpaceModels.map((subSpaceModel) => ({ + const subspaceData = subspaceModels.map((subSpaceModel) => ({ subspaceName: subSpaceModel.subspaceName, space, subSpaceModel, })); - await this.createSubspaces(subspaceData, queryRunner); + const subspaces = await this.createSubspaces(subspaceData, queryRunner); + + await Promise.all( + subspaceModels.map((model, index) => + this.tagService.createTagsFromModel( + queryRunner, + model.tags || [], + null, + subspaces[index], + ), + ), + ); } async createSubspacesFromDto( @@ -317,6 +323,31 @@ export class SubSpaceService { } } + async unlinkModels( + subspaces: SubspaceEntity[], + queryRunner: QueryRunner, + ): Promise { + if (!subspaces || subspaces.length === 0) { + return; + } + try { + const allTags = subspaces.flatMap((subSpace) => { + subSpace.subSpaceModel = null; + return subSpace.tags || []; + }); + + await this.tagService.unlinkModels(allTags, queryRunner); + + await queryRunner.manager.save(subspaces); + } catch (error) { + if (error instanceof HttpException) throw error; + throw new HttpException( + `Failed to unlink subspace models: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getOne(params: GetSubSpaceParam): Promise { await this.validationService.validateSpaceWithinCommunityAndProject( params.communityUuid, diff --git a/src/space/services/tag/tag.service.ts b/src/space/services/tag/tag.service.ts index 173d91d..c9ccc1c 100644 --- a/src/space/services/tag/tag.service.ts +++ b/src/space/services/tag/tag.service.ts @@ -5,6 +5,7 @@ import { TagEntity, TagRepository, } from '@app/common/modules/space'; +import { TagModel } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { ProductService } from 'src/product/services'; import { CreateTagDto } from 'src/space/dtos'; @@ -25,39 +26,43 @@ export class TagService { subspace?: SubspaceEntity, additionalTags?: CreateTagDto[], ): Promise { - if (!tags.length) { - throw new HttpException('Tags cannot be empty.', HttpStatus.BAD_REQUEST); - } + this.validateTagsInput(tags); - const combinedTags = additionalTags ? [...tags, ...additionalTags] : tags; - const duplicateTags = this.findDuplicateTags(combinedTags); + const combinedTags = this.combineTags(tags, additionalTags); + this.ensureNoDuplicateTags(combinedTags); - if (duplicateTags.length > 0) { - throw new HttpException( - `Duplicate tags found for the same product: ${duplicateTags.join(', ')}`, - HttpStatus.BAD_REQUEST, - ); - } - - const tagEntities = await Promise.all( - tags.map(async (tagDto) => - this.prepareTagEntity(tagDto, queryRunner, space, subspace), - ), - ); try { + const tagEntities = await Promise.all( + tags.map(async (tagDto) => + this.prepareTagEntity(tagDto, queryRunner, space, subspace), + ), + ); + return await queryRunner.manager.save(tagEntities); } catch (error) { - if (error instanceof HttpException) { - throw error; - } - - throw new HttpException( - 'Failed to save tag models due to an unexpected error.', - HttpStatus.INTERNAL_SERVER_ERROR, - ); + throw this.handleUnexpectedError('Failed to save tags', error); } } + async createTagsFromModel( + queryRunner: QueryRunner, + tagModels: TagModel[], + space?: SpaceEntity, + subspace?: SubspaceEntity, + ): Promise { + if (!tagModels?.length) return; + + const tags = tagModels.map((model) => + queryRunner.manager.create(this.tagRepository.target, { + tag: model.tag, + space: space || undefined, + subspace: subspace || undefined, + product: model.product, + }), + ); + + await queryRunner.manager.save(tags); + } async updateTag( tag: ModifyTagDto, queryRunner: QueryRunner, @@ -67,48 +72,94 @@ export class TagService { try { const existingTag = await this.getTagByUuid(tag.uuid); - if (space) { - await this.checkTagReuse(tag.tag, existingTag.product.uuid, space); - } else { + const contextSpace = space ?? subspace?.space; + + if (contextSpace) { await this.checkTagReuse( tag.tag, existingTag.product.uuid, - subspace.space, + contextSpace, ); } - if (tag.tag) { - existingTag.tag = tag.tag; - } - - return await queryRunner.manager.save(existingTag); - } catch (error) { - if (error instanceof HttpException) { - throw error; - } - throw new HttpException( - error.message || 'Failed to update tags', - HttpStatus.INTERNAL_SERVER_ERROR, + return await queryRunner.manager.save( + Object.assign(existingTag, { tag: tag.tag }), ); + } catch (error) { + throw this.handleUnexpectedError('Failed to update tags', error); } } async deleteTags(tagUuids: string[], queryRunner: QueryRunner) { - try { - const deletePromises = tagUuids.map((id) => - queryRunner.manager.softDelete(this.tagRepository.target, id), - ); + if (!tagUuids?.length) return; - await Promise.all(deletePromises); + try { + await Promise.all( + tagUuids.map((id) => + queryRunner.manager.update( + this.tagRepository.target, + { uuid: id }, + { disabled: true }, + ), + ), + ); return { message: 'Tags deleted successfully', tagUuids }; } catch (error) { - if (error instanceof HttpException) { - throw error; - } - throw new HttpException( - error.message || 'Failed to delete tags', - HttpStatus.INTERNAL_SERVER_ERROR, + throw this.handleUnexpectedError('Failed to update tags', error); + } + } + + async modifyTags( + tags: ModifyTagDto[], + queryRunner: QueryRunner, + space?: SpaceEntity, + subspace?: SubspaceEntity, + ): Promise { + if (!tags?.length) return; + + try { + await Promise.all( + tags.map(async (tag) => { + switch (tag.action) { + case ModifyAction.ADD: + await this.createTags( + [{ tag: tag.tag, productUuid: tag.productUuid }], + queryRunner, + space, + subspace, + ); + break; + case ModifyAction.UPDATE: + await this.updateTag(tag, queryRunner, space, subspace); + break; + case ModifyAction.DELETE: + await this.deleteTags([tag.uuid], queryRunner); + + break; + default: + throw new HttpException( + `Invalid action "${tag.action}" provided.`, + HttpStatus.BAD_REQUEST, + ); + } + }), ); + } catch (error) { + throw this.handleUnexpectedError('Failed to modify tags', error); + } + } + + async unlinkModels(tags: TagEntity[], queryRunner: QueryRunner) { + if (!tags?.length) return; + + try { + tags.forEach((tag) => { + tag.model = null; + }); + + await queryRunner.manager.save(tags); + } catch (error) { + throw this.handleUnexpectedError('Failed to unlink tag models', error); } } @@ -128,48 +179,6 @@ export class TagService { return duplicates; } - async modifyTags( - tags: ModifyTagDto[], - queryRunner: QueryRunner, - space?: SpaceEntity, - subspace?: SubspaceEntity, - ): Promise { - try { - for (const tag of tags) { - if (tag.action === ModifyAction.ADD) { - const createTagDto: CreateTagDto = { - tag: tag.tag as string, - productUuid: tag.productUuid as string, - }; - - await this.createTags([createTagDto], queryRunner, space, subspace); - } else if (tag.action === ModifyAction.UPDATE) { - await this.updateTag(tag, queryRunner, space, subspace); - } else if (tag.action === ModifyAction.DELETE) { - await queryRunner.manager.update( - this.tagRepository.target, - { uuid: tag.uuid }, - { disabled: true }, - ); - } else { - throw new HttpException( - `Invalid action "${tag.action}" provided.`, - HttpStatus.BAD_REQUEST, - ); - } - } - } catch (error) { - if (error instanceof HttpException) { - throw error; - } - - throw new HttpException( - `An error occurred while modifying tags: ${error.message}`, - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - private async checkTagReuse( tag: string, productUuid: string, @@ -214,11 +223,11 @@ export class TagService { ); } - if (space) { - await this.checkTagReuse(tagDto.tag, tagDto.productUuid, space); - } else { - await this.checkTagReuse(tagDto.tag, tagDto.productUuid, subspace.space); - } + await this.checkTagReuse( + tagDto.tag, + tagDto.productUuid, + space ?? subspace.space, + ); return queryRunner.manager.create(TagEntity, { tag: tagDto.tag, @@ -233,6 +242,7 @@ export class TagService { where: { uuid }, relations: ['product'], }); + if (!tag) { throw new HttpException( `Tag with ID ${uuid} not found.`, @@ -241,4 +251,39 @@ export class TagService { } return tag; } + + private handleUnexpectedError( + message: string, + error: unknown, + ): HttpException { + if (error instanceof HttpException) throw error; + return new HttpException( + `${message}: ${(error as Error)?.message || 'Unknown error'}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + private combineTags( + primaryTags: CreateTagDto[], + additionalTags?: CreateTagDto[], + ): CreateTagDto[] { + return additionalTags ? [...primaryTags, ...additionalTags] : primaryTags; + } + + private ensureNoDuplicateTags(tags: CreateTagDto[]): void { + const duplicates = this.findDuplicateTags(tags); + + if (duplicates.length > 0) { + throw new HttpException( + `Duplicate tags found: ${duplicates.join(', ')}`, + HttpStatus.BAD_REQUEST, + ); + } + } + + private validateTagsInput(tags: CreateTagDto[]): void { + if (!tags?.length) { + return; + } + } } diff --git a/src/space/space.module.ts b/src/space/space.module.ts index a17d95e..7f707e8 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -43,12 +43,18 @@ import { SceneDeviceRepository } from '@app/common/modules/scene-device/reposito import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { SpaceModelRepository, + SubspaceModelRepository, TagModelRepository, } from '@app/common/modules/space-model'; import { CommunityModule } from 'src/community/community.module'; import { ValidationService } from './services'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; import { TagService } from './services/tag'; +import { + SpaceModelService, + SubSpaceModelService, + TagModelService, +} from 'src/space-model/services'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, CommunityModule], @@ -88,9 +94,12 @@ import { TagService } from './services/tag'; DeviceStatusFirebaseService, DeviceStatusLogRepository, SceneDeviceRepository, - + SpaceModelService, + SubSpaceModelService, + TagModelService, ProjectRepository, SpaceModelRepository, + SubspaceModelRepository, ], exports: [SpaceService], }) From ba00a8591012533dac0c0d0172a7bdbf8f95ef2b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 25 Dec 2024 11:18:52 +0400 Subject: [PATCH 123/247] filter space links --- src/space/services/space-link/space-link.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/space/services/space-link/space-link.service.ts b/src/space/services/space-link/space-link.service.ts index 0acece0..a20f76f 100644 --- a/src/space/services/space-link/space-link.service.ts +++ b/src/space/services/space-link/space-link.service.ts @@ -22,6 +22,7 @@ export class SpaceLinkService { where: { startSpace: { uuid: startSpaceId }, endSpace: { uuid: endSpaceId }, + disabled: false, }, }); From f9fe2090767319e5fc578a3025f1130c2e8155ca Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 25 Dec 2024 11:46:24 +0400 Subject: [PATCH 124/247] use query runner for space link --- .../services/space-link/space-link.service.ts | 22 +++++++++----- src/space/services/space.service.ts | 29 +++++++++++++++++++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/space/services/space-link/space-link.service.ts b/src/space/services/space-link/space-link.service.ts index a20f76f..55c40ab 100644 --- a/src/space/services/space-link/space-link.service.ts +++ b/src/space/services/space-link/space-link.service.ts @@ -1,8 +1,10 @@ +import { SpaceEntity, SpaceLinkEntity } from '@app/common/modules/space'; import { SpaceLinkRepository, SpaceRepository, } from '@app/common/modules/space/repositories'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { QueryRunner } from 'typeorm'; @Injectable() export class SpaceLinkService { @@ -15,10 +17,11 @@ export class SpaceLinkService { startSpaceId: string, endSpaceId: string, direction: string, + queryRunner: QueryRunner, ): Promise { try { // Check if a link between the startSpace and endSpace already exists - const existingLink = await this.spaceLinkRepository.findOne({ + const existingLink = await queryRunner.manager.findOne(SpaceLinkEntity, { where: { startSpace: { uuid: startSpaceId }, endSpace: { uuid: endSpaceId }, @@ -29,13 +32,16 @@ export class SpaceLinkService { if (existingLink) { // Update the direction if the link exists existingLink.direction = direction; - await this.spaceLinkRepository.save(existingLink); + await queryRunner.manager.save(SpaceLinkEntity, existingLink); return; } - const existingEndSpaceLink = await this.spaceLinkRepository.findOne({ - where: { endSpace: { uuid: endSpaceId } }, - }); + const existingEndSpaceLink = await queryRunner.manager.findOne( + SpaceLinkEntity, + { + where: { endSpace: { uuid: endSpaceId } }, + }, + ); if ( existingEndSpaceLink && @@ -47,7 +53,7 @@ export class SpaceLinkService { } // Find start space - const startSpace = await this.spaceRepository.findOne({ + const startSpace = await queryRunner.manager.findOne(SpaceEntity, { where: { uuid: startSpaceId }, }); @@ -59,7 +65,7 @@ export class SpaceLinkService { } // Find end space - const endSpace = await this.spaceRepository.findOne({ + const endSpace = await queryRunner.manager.findOne(SpaceEntity, { where: { uuid: endSpaceId }, }); @@ -77,7 +83,7 @@ export class SpaceLinkService { direction, }); - await this.spaceLinkRepository.save(spaceLink); + await queryRunner.manager.save(SpaceLinkEntity, spaceLink); } catch (error) { throw new HttpException( error.message || diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index d7ff346..db41d6b 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -90,6 +90,7 @@ export class SpaceService { parent.uuid, newSpace.uuid, direction, + queryRunner, ) : Promise.resolve(), subspaces?.length @@ -361,6 +362,34 @@ export class SpaceService { } } + async deleteSpace(params: GetSpaceParam) { + try { + const { communityUuid, spaceUuid, projectUuid } = params; + const space = + await this.validationService.validateSpaceWithinCommunityAndProject( + communityUuid, + projectUuid, + spaceUuid, + ); + const spaces = await this.spaceRepository.find({ + where: { parent: { uuid: spaceUuid }, disabled: false }, + relations: ['parent', 'children'], // Include parent and children relations + }); + console.log(spaces); + // space.disabled = true; + await this.spaceRepository.update({ uuid: space.uuid }, space); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + 'An error occurred while deleting the space', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + private updateSpaceProperties( space: SpaceEntity, updateSpaceDto: UpdateSpaceDto, From f7b75a630a8dd06f2cfca5e423838bc14b9aa52a Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 25 Dec 2024 16:23:09 +0400 Subject: [PATCH 125/247] disabling space --- .../entities/scene-device.entity.ts | 6 ++ .../modules/scene/entities/scene.entity.ts | 6 ++ src/device/services/device.service.ts | 27 +++++++- src/scene/services/scene.service.ts | 16 +++-- src/space/controllers/space.controller.ts | 4 +- .../services/space-link/space-link.service.ts | 31 +++++++++ src/space/services/space-scene.service.ts | 31 +++++++++ .../services/space-validation.service.ts | 8 +-- src/space/services/space.service.ts | 67 +++++++++++++++---- .../services/subspace/subspace.service.ts | 1 - src/space/space.module.ts | 12 +++- src/users/services/user-space.service.ts | 16 +++++ 12 files changed, 198 insertions(+), 27 deletions(-) diff --git a/libs/common/src/modules/scene-device/entities/scene-device.entity.ts b/libs/common/src/modules/scene-device/entities/scene-device.entity.ts index 9814fdf..a5f1fb5 100644 --- a/libs/common/src/modules/scene-device/entities/scene-device.entity.ts +++ b/libs/common/src/modules/scene-device/entities/scene-device.entity.ts @@ -44,6 +44,12 @@ export class SceneDeviceEntity extends AbstractEntity { @JoinColumn({ name: 'scene_uuid' }) scene: SceneEntity; + @Column({ + nullable: false, + default: false, + }) + public disabled: boolean; + constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/scene/entities/scene.entity.ts b/libs/common/src/modules/scene/entities/scene.entity.ts index 5daa690..86b1beb 100644 --- a/libs/common/src/modules/scene/entities/scene.entity.ts +++ b/libs/common/src/modules/scene/entities/scene.entity.ts @@ -59,6 +59,12 @@ export class SceneEntity extends AbstractEntity { @JoinColumn({ name: 'space_uuid' }) space: SpaceEntity; + @Column({ + nullable: false, + default: false, + }) + public disabled: boolean; + @ManyToOne(() => SceneIconEntity, (icon) => icon.scenesIconEntity, { nullable: false, }) diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index add62c9..4846cd3 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -41,7 +41,7 @@ import { import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { PermissionType } from '@app/common/constants/permission-type.enum'; -import { In } from 'typeorm'; +import { In, QueryRunner } from 'typeorm'; import { ProductType } from '@app/common/constants/product-type.enum'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { DeviceStatusFirebaseService } from '@app/common/firebase/devices-status/services/devices-status.service'; @@ -59,6 +59,7 @@ import { DeviceSceneParamDto } from '../dtos/device.param.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { DeleteSceneFromSceneDeviceDto } from '../dtos/delete.device.dto'; +import { DeviceEntity } from '@app/common/modules/device/entities'; @Injectable() export class DeviceService { @@ -83,6 +84,7 @@ export class DeviceService { secretKey, }); } + async getDeviceByDeviceUuid( deviceUuid: string, withProductDevice: boolean = true, @@ -98,6 +100,29 @@ export class DeviceService { relations, }); } + async deleteDevice( + devices: DeviceEntity[], + orphanSpace: SpaceEntity, + queryRunner: QueryRunner, + ): Promise { + try { + const deviceIds = devices.map((device) => device.uuid); + + if (deviceIds.length > 0) { + await queryRunner.manager + .createQueryBuilder() + .update(DeviceEntity) + .set({ spaceDevice: orphanSpace }) + .whereInIds(deviceIds) + .execute(); + } + } catch (error) { + throw new HttpException( + `Failed to update devices to orphan space: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } async getDeviceByDeviceTuyaUuid(deviceTuyaUuid: string) { return await this.deviceRepository.findOne({ diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 691fbe1..498d157 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -495,12 +495,16 @@ export class SceneService { const space = await this.getSpaceByUuid(scene.space.uuid); await this.delete(scene.sceneTuyaUuid, space.spaceTuyaUuid); - await this.sceneDeviceRepository.delete({ - scene: { uuid: sceneUuid }, - }); - await this.sceneRepository.delete({ - uuid: sceneUuid, - }); + await this.sceneDeviceRepository.update( + { uuid: sceneUuid }, + { disabled: true }, + ); + await this.sceneRepository.update( + { + uuid: sceneUuid, + }, + { disabled: true }, + ); return new SuccessResponseDto({ message: `Scene with ID ${sceneUuid} deleted successfully`, }); diff --git a/src/space/controllers/space.controller.ts b/src/space/controllers/space.controller.ts index 18d556d..9ada727 100644 --- a/src/space/controllers/space.controller.ts +++ b/src/space/controllers/space.controller.ts @@ -67,8 +67,8 @@ export class SpaceController { description: ControllerRoute.SPACE.ACTIONS.DELETE_SPACE_DESCRIPTION, }) @Delete('/:spaceUuid') - async deleteSpace(@Param() params: GetSpaceParam): Promise { - return this.spaceService.delete(params); + async deleteSpace(@Param() params: GetSpaceParam){ + return await this.spaceService.deleteSpace(params); } @ApiBearerAuth() diff --git a/src/space/services/space-link/space-link.service.ts b/src/space/services/space-link/space-link.service.ts index 55c40ab..8ad797e 100644 --- a/src/space/services/space-link/space-link.service.ts +++ b/src/space/services/space-link/space-link.service.ts @@ -92,4 +92,35 @@ export class SpaceLinkService { ); } } + async deleteSpaceLink( + space: SpaceEntity, + queryRunner: QueryRunner, + ): Promise { + try { + const spaceLinks = await queryRunner.manager.find(SpaceLinkEntity, { + where: [ + { startSpace: space, disabled: false }, + { endSpace: space, disabled: false }, + ], + }); + + if (spaceLinks.length === 0) { + return; + } + + const linkIds = spaceLinks.map((link) => link.uuid); + + await queryRunner.manager + .createQueryBuilder() + .update(SpaceLinkEntity) + .set({ disabled: true }) + .whereInIds(linkIds) + .execute(); + } catch (error) { + throw new HttpException( + `Failed to disable space links for the given space: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/space/services/space-scene.service.ts b/src/space/services/space-scene.service.ts index 4e77158..6c6b528 100644 --- a/src/space/services/space-scene.service.ts +++ b/src/space/services/space-scene.service.ts @@ -5,6 +5,9 @@ import { SceneService } from '../../scene/services'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { GetSceneDto } from '../../scene/dtos'; import { ValidationService } from './space-validation.service'; +import { SpaceEntity } from '@app/common/modules/space'; +import { QueryRunner } from 'typeorm'; +import { SceneEntity } from '@app/common/modules/scene/entities'; @Injectable() export class SpaceSceneService { @@ -48,4 +51,32 @@ export class SpaceSceneService { } } } + + async deleteScenes( + space: SpaceEntity, + queryRunner: QueryRunner, + ): Promise { + try { + const scenes = await queryRunner.manager.find(SceneEntity, { + where: { space }, + }); + + if (scenes.length === 0) { + return; + } + + const sceneUuids = scenes.map((scene) => scene.uuid); + + await Promise.all( + sceneUuids.map((uuid) => + this.sceneSevice.deleteScene({ sceneUuid: uuid }), + ), + ); + } catch (error) { + throw new HttpException( + `Failed to delete scenes: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts index e8bfd32..0fc7673 100644 --- a/src/space/services/space-validation.service.ts +++ b/src/space/services/space-validation.service.ts @@ -1,4 +1,3 @@ -import { CommunityEntity } from '@app/common/modules/community/entities'; import { SpaceEntity } from '@app/common/modules/space/entities'; import { SpaceRepository } from '@app/common/modules/space/repositories'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; @@ -21,16 +20,17 @@ export class ValidationService { async validateCommunityAndProject( communityUuid: string, projectUuid: string, - ): Promise { - await this.projectService.findOne(projectUuid); + ) { + const project = await this.projectService.findOne(projectUuid); const community = await this.communityService.getCommunityById({ communityUuid, projectUuid, }); - return community.data; + return { community: community.data, project: project }; } + async validateSpaceWithinCommunityAndProject( communityUuid: string, projectUuid: string, diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index db41d6b..685ae99 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -24,6 +24,9 @@ import { ValidationService } from './space-validation.service'; import { ORPHAN_SPACE_NAME } from '@app/common/constants/orphan-constant'; import { TagService } from './tag'; import { SpaceModelService } from 'src/space-model/services'; +import { UserSpaceService } from 'src/users/services'; +import { DeviceService } from 'src/device/services'; +import { SpaceSceneService } from './space-scene.service'; @Injectable() export class SpaceService { @@ -35,6 +38,9 @@ export class SpaceService { private readonly validationService: ValidationService, private readonly tagService: TagService, private readonly spaceModelService: SpaceModelService, + private readonly userService: UserSpaceService, + private readonly deviceService: DeviceService, + private readonly sceneService: SpaceSceneService, ) {} async createSpace( @@ -363,33 +369,70 @@ export class SpaceService { } async deleteSpace(params: GetSpaceParam) { + const queryRunner = this.dataSource.createQueryRunner(); + const { spaceUuid } = params; + try { - const { communityUuid, spaceUuid, projectUuid } = params; - const space = - await this.validationService.validateSpaceWithinCommunityAndProject( - communityUuid, - projectUuid, - spaceUuid, - ); + await queryRunner.connect(); + await queryRunner.startTransaction(); + const spaces = await this.spaceRepository.find({ where: { parent: { uuid: spaceUuid }, disabled: false }, - relations: ['parent', 'children'], // Include parent and children relations + relations: [ + 'parent', + 'children', + 'subspaces', + 'tags', + 'subspaces.tags', + 'devices', + ], // Include parent and children relations }); - console.log(spaces); - // space.disabled = true; - await this.spaceRepository.update({ uuid: space.uuid }, space); + //this.disableSpace(space, orphanSpace); + return spaces; } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException( - 'An error occurred while deleting the space', + `An error occurred while deleting the space ${error}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } } + async disableSpace(space: SpaceEntity, orphanSpace: SpaceEntity) { + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.connect(); + await queryRunner.startTransaction(); + try { + const deleteSubspaceDtos = space.subspaces?.map((subspace) => ({ + subspaceUuid: subspace.uuid, + })); + const deleteSpaceTagsDtos = space.tags?.map((tag) => tag.uuid); + + await this.userService.deleteUserSpace(space.uuid); + await this.subSpaceService.deleteSubspaces( + deleteSubspaceDtos, + queryRunner, + ); + + await this.tagService.deleteTags(deleteSpaceTagsDtos, queryRunner); + + await this.deviceService.deleteDevice( + space.devices, + orphanSpace, + queryRunner, + ); + await this.spaceLinkService.deleteSpaceLink(space, queryRunner); + await this.sceneService.deleteScenes(space, queryRunner); + } catch (error) { + await queryRunner.rollbackTransaction(); + } finally { + await queryRunner.release(); + } + } + private updateSpaceProperties( space: SpaceEntity, updateSpaceDto: UpdateSpaceDto, diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 01c3bd5..e826071 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -19,7 +19,6 @@ import { In, QueryRunner } from 'typeorm'; import { SpaceEntity, SubspaceEntity, - TagEntity, } from '@app/common/modules/space/entities'; import { SubspaceModelEntity } from '@app/common/modules/space-model'; import { ValidationService } from '../space-validation.service'; diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 7f707e8..c3b29d3 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -28,7 +28,10 @@ import { UserRepository, UserSpaceRepository, } from '@app/common/modules/user/repositories'; -import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { + DeviceRepository, + DeviceUserPermissionRepository, +} from '@app/common/modules/device/repositories'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { ProductRepository } from '@app/common/modules/product/repositories'; import { SceneService } from '../scene/services'; @@ -55,6 +58,9 @@ import { SubSpaceModelService, TagModelService, } from 'src/space-model/services'; +import { UserSpaceService } from 'src/users/services'; +import { UserDevicePermissionService } from 'src/user-device-permission/services'; +import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, CommunityModule], @@ -100,6 +106,10 @@ import { ProjectRepository, SpaceModelRepository, SubspaceModelRepository, + UserSpaceService, + UserDevicePermissionService, + DeviceUserPermissionRepository, + PermissionTypeRepository, ], exports: [SpaceService], }) diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index 1c7ac6e..0ee9af5 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -150,4 +150,20 @@ export class UserSpaceService { await Promise.all(permissionPromises); } + + async deleteUserSpace(spaceUuid: string) { + try { + await this.userSpaceRepository + .createQueryBuilder() + .delete() + .where('spaceUuid = :spaceUuid', { spaceUuid }) + .execute(); + } catch (error) { + console.error(`Error deleting user-space associations: ${error.message}`); + throw new HttpException( + `Failed to delete user-space associations: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } From 77d2e18b3663672ae55300a4a2baf1f5d7fb7953 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 25 Dec 2024 19:58:37 -0600 Subject: [PATCH 126/247] Make jobTitle and phoneNumber optional in InviteUserEntity and DTO --- .../modules/Invite-user/entities/Invite-user.entity.ts | 4 ++-- src/invite-user/dtos/add.invite-user.dto.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts index c93f94b..3eac7d6 100644 --- a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts +++ b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts @@ -34,7 +34,7 @@ export class InviteUserEntity extends AbstractEntity { email: string; @Column({ - nullable: false, + nullable: true, }) jobTitle: string; @@ -52,7 +52,7 @@ export class InviteUserEntity extends AbstractEntity { }) public lastName: string; @Column({ - nullable: false, + nullable: true, }) public phoneNumber: string; diff --git a/src/invite-user/dtos/add.invite-user.dto.ts b/src/invite-user/dtos/add.invite-user.dto.ts index 94f2e8a..0d9acbc 100644 --- a/src/invite-user/dtos/add.invite-user.dto.ts +++ b/src/invite-user/dtos/add.invite-user.dto.ts @@ -38,16 +38,16 @@ export class AddUserInvitationDto { @ApiProperty({ description: 'The job title of the user', example: 'Software Engineer', - required: true, + required: false, }) @IsString() - @IsNotEmpty() - public jobTitle: string; + @IsOptional() + public jobTitle?: string; @ApiProperty({ description: 'The phone number of the user', example: '+1234567890', - required: true, + required: false, }) @IsString() @IsOptional() From 6aff5e54e924ed6ef7e88804cbebeacc001a53ff Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 25 Dec 2024 19:58:44 -0600 Subject: [PATCH 127/247] Add email and project validation before inviting user --- src/invite-user/services/invite-user.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 2180d0b..b7b5ad0 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -46,6 +46,7 @@ export class InviteUserService { try { const userRepo = queryRunner.manager.getRepository(UserEntity); + await this.checkEmailAndProject({ email }); const user = await userRepo.findOne({ where: { From fcf19645995061107b4741d9f14a2e56ddeec6d9 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 27 Dec 2024 09:02:47 +0400 Subject: [PATCH 128/247] delete propogation --- src/space/commands/disable-space.command.ts | 10 ++ src/space/commands/index.ts | 1 + src/space/controllers/space.controller.ts | 4 +- src/space/handlers/disable-space.handler.ts | 100 +++++++++++++ src/space/handlers/index.ts | 1 + .../services/space-validation.service.ts | 10 +- src/space/services/space.service.ts | 132 ++++-------------- src/space/space.module.ts | 7 +- 8 files changed, 154 insertions(+), 111 deletions(-) create mode 100644 src/space/commands/disable-space.command.ts create mode 100644 src/space/commands/index.ts create mode 100644 src/space/handlers/disable-space.handler.ts create mode 100644 src/space/handlers/index.ts diff --git a/src/space/commands/disable-space.command.ts b/src/space/commands/disable-space.command.ts new file mode 100644 index 0000000..1c66f21 --- /dev/null +++ b/src/space/commands/disable-space.command.ts @@ -0,0 +1,10 @@ +import { SpaceEntity } from '@app/common/modules/space'; + +export class DisableSpaceCommand { + constructor( + public readonly param: { + spaceUuid: string; + orphanSpace: SpaceEntity; + }, + ) {} +} diff --git a/src/space/commands/index.ts b/src/space/commands/index.ts new file mode 100644 index 0000000..a9a7b85 --- /dev/null +++ b/src/space/commands/index.ts @@ -0,0 +1 @@ +export * from './disable-space.command'; diff --git a/src/space/controllers/space.controller.ts b/src/space/controllers/space.controller.ts index 9ada727..f54a3ea 100644 --- a/src/space/controllers/space.controller.ts +++ b/src/space/controllers/space.controller.ts @@ -67,8 +67,8 @@ export class SpaceController { description: ControllerRoute.SPACE.ACTIONS.DELETE_SPACE_DESCRIPTION, }) @Delete('/:spaceUuid') - async deleteSpace(@Param() params: GetSpaceParam){ - return await this.spaceService.deleteSpace(params); + async deleteSpace(@Param() params: GetSpaceParam) { + return await this.spaceService.delete(params); } @ApiBearerAuth() diff --git a/src/space/handlers/disable-space.handler.ts b/src/space/handlers/disable-space.handler.ts new file mode 100644 index 0000000..64669fa --- /dev/null +++ b/src/space/handlers/disable-space.handler.ts @@ -0,0 +1,100 @@ +import { SpaceEntity } from '@app/common/modules/space'; +import { HttpException, HttpStatus } from '@nestjs/common'; +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { DeviceService } from 'src/device/services'; +import { UserSpaceService } from 'src/users/services'; +import { DataSource } from 'typeorm'; +import { DisableSpaceCommand } from '../commands'; +import { + SubSpaceService, + SpaceLinkService, + SpaceSceneService, +} from '../services'; +import { TagService } from '../services/tag'; + +@CommandHandler(DisableSpaceCommand) +export class DisableSpaceHandler + implements ICommandHandler +{ + constructor( + private readonly subSpaceService: SubSpaceService, + private readonly userService: UserSpaceService, + private readonly tagService: TagService, + private readonly deviceService: DeviceService, + private readonly spaceLinkService: SpaceLinkService, + private readonly sceneService: SpaceSceneService, + private readonly dataSource: DataSource, + ) {} + + async execute(command: DisableSpaceCommand): Promise { + const queryRunner = this.dataSource.createQueryRunner(); + + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + const { spaceUuid, orphanSpace } = command.param; + + const space = await queryRunner.manager.findOne(SpaceEntity, { + where: { uuid: spaceUuid, disabled: false }, + relations: [ + 'subspaces', + 'parent', + 'tags', + 'devices', + 'outgoingConnections', + 'incomingConnections', + 'scenes', + 'children', + 'userSpaces', + ], + }); + + if (!space) { + throw new HttpException( + `Space with UUID ${spaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + if (space.children && space.children.length > 0) { + for (const child of space.children) { + await this.execute( + new DisableSpaceCommand({ spaceUuid: child.uuid, orphanSpace }), + ); + } + } + + const tagUuids = space.tags?.map((tag) => tag.uuid) || []; + const subspaceDtos = + space.subspaces?.map((subspace) => ({ + subspaceUuid: subspace.uuid, + })) || []; + const deletionTasks = [ + this.subSpaceService.deleteSubspaces(subspaceDtos, queryRunner), + this.userService.deleteUserSpace(space.uuid), + this.tagService.deleteTags(tagUuids, queryRunner), + this.deviceService.deleteDevice( + space.devices, + orphanSpace, + queryRunner, + ), + this.spaceLinkService.deleteSpaceLink(space, queryRunner), + this.sceneService.deleteScenes(space, queryRunner), + ]; + + await Promise.all(deletionTasks); + + // Mark space as disabled + space.disabled = true; + await queryRunner.manager.save(space); + + await queryRunner.commitTransaction(); + } catch (error) { + await queryRunner.rollbackTransaction(); + console.error(`Failed to disable space: ${error.message}`); + } finally { + await queryRunner.release(); + } + } +} diff --git a/src/space/handlers/index.ts b/src/space/handlers/index.ts new file mode 100644 index 0000000..ceb1ca9 --- /dev/null +++ b/src/space/handlers/index.ts @@ -0,0 +1 @@ +export * from './disable-space.handler'; diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts index 0fc7673..f58f5c3 100644 --- a/src/space/services/space-validation.service.ts +++ b/src/space/services/space-validation.service.ts @@ -30,7 +30,6 @@ export class ValidationService { return { community: community.data, project: project }; } - async validateSpaceWithinCommunityAndProject( communityUuid: string, projectUuid: string, @@ -44,7 +43,14 @@ export class ValidationService { async validateSpace(spaceUuid: string): Promise { const space = await this.spaceRepository.findOne({ where: { uuid: spaceUuid, disabled: false }, - relations: ['subspaces', 'tags'], + relations: [ + 'parent', + 'children', + 'subspaces', + 'tags', + 'subspaces.tags', + 'devices', + ], }); if (!space) { diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 685ae99..8fba2d9 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -21,13 +21,14 @@ import { SpaceLinkService } from './space-link'; import { SubSpaceService } from './subspace'; import { DataSource, Not, QueryRunner } from 'typeorm'; import { ValidationService } from './space-validation.service'; -import { ORPHAN_SPACE_NAME } from '@app/common/constants/orphan-constant'; +import { + ORPHAN_COMMUNITY_NAME, + ORPHAN_SPACE_NAME, +} from '@app/common/constants/orphan-constant'; +import { CommandBus } from '@nestjs/cqrs'; import { TagService } from './tag'; import { SpaceModelService } from 'src/space-model/services'; -import { UserSpaceService } from 'src/users/services'; -import { DeviceService } from 'src/device/services'; -import { SpaceSceneService } from './space-scene.service'; - +import { DisableSpaceCommand } from '../commands'; @Injectable() export class SpaceService { constructor( @@ -38,9 +39,7 @@ export class SpaceService { private readonly validationService: ValidationService, private readonly tagService: TagService, private readonly spaceModelService: SpaceModelService, - private readonly userService: UserSpaceService, - private readonly deviceService: DeviceService, - private readonly sceneService: SpaceSceneService, + private commandBus: CommandBus, ) {} async createSpace( @@ -228,20 +227,17 @@ export class SpaceService { } async delete(params: GetSpaceParam): Promise { - const queryRunner = this.dataSource.createQueryRunner(); - - await queryRunner.connect(); - await queryRunner.startTransaction(); try { const { communityUuid, spaceUuid, projectUuid } = params; - const space = - await this.validationService.validateSpaceWithinCommunityAndProject( + const { project } = + await this.validationService.validateCommunityAndProject( communityUuid, projectUuid, - spaceUuid, ); + const space = await this.validationService.validateSpace(spaceUuid); + if (space.spaceName === ORPHAN_SPACE_NAME) { throw new HttpException( `space ${ORPHAN_SPACE_NAME} cannot be deleted`, @@ -249,37 +245,22 @@ export class SpaceService { ); } - if (space.tags?.length) { - const deleteSpaceTagsDtos = space.tags.map((tag) => tag.uuid); - await this.tagService.deleteTags(deleteSpaceTagsDtos, queryRunner); - } + const orphanSpace = await this.spaceRepository.findOne({ + where: { + community: { + uuid: `${ORPHAN_COMMUNITY_NAME}-${project.name}`, + }, + spaceName: ORPHAN_SPACE_NAME, + }, + }); - if (space.subspaces?.length) { - const deleteSubspaceDtos = space.subspaces.map((subspace) => ({ - subspaceUuid: subspace.uuid, - })); - - await this.subSpaceService.deleteSubspaces( - deleteSubspaceDtos, - queryRunner, - ); - } - - await queryRunner.manager.update( - this.spaceRepository.target, - { uuid: params.spaceUuid }, - { disabled: true }, - ); - - await queryRunner.commitTransaction(); + await this.disableSpace(space, orphanSpace); return new SuccessResponseDto({ message: `Space with ID ${spaceUuid} successfully deleted`, statusCode: HttpStatus.OK, }); } catch (error) { - await queryRunner.rollbackTransaction(); - if (error instanceof HttpException) { throw error; } @@ -287,11 +268,15 @@ export class SpaceService { 'An error occurred while deleting the space', HttpStatus.INTERNAL_SERVER_ERROR, ); - } finally { - await queryRunner.release(); } } + async disableSpace(space: SpaceEntity, orphanSpace: SpaceEntity) { + await this.commandBus.execute( + new DisableSpaceCommand({ spaceUuid: space.uuid, orphanSpace }), + ); + } + async updateSpace( params: GetSpaceParam, updateSpaceDto: UpdateSpaceDto, @@ -368,71 +353,6 @@ export class SpaceService { } } - async deleteSpace(params: GetSpaceParam) { - const queryRunner = this.dataSource.createQueryRunner(); - const { spaceUuid } = params; - - try { - await queryRunner.connect(); - await queryRunner.startTransaction(); - - const spaces = await this.spaceRepository.find({ - where: { parent: { uuid: spaceUuid }, disabled: false }, - relations: [ - 'parent', - 'children', - 'subspaces', - 'tags', - 'subspaces.tags', - 'devices', - ], // Include parent and children relations - }); - //this.disableSpace(space, orphanSpace); - return spaces; - } catch (error) { - if (error instanceof HttpException) { - throw error; - } - - throw new HttpException( - `An error occurred while deleting the space ${error}`, - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - - async disableSpace(space: SpaceEntity, orphanSpace: SpaceEntity) { - const queryRunner = this.dataSource.createQueryRunner(); - await queryRunner.connect(); - await queryRunner.startTransaction(); - try { - const deleteSubspaceDtos = space.subspaces?.map((subspace) => ({ - subspaceUuid: subspace.uuid, - })); - const deleteSpaceTagsDtos = space.tags?.map((tag) => tag.uuid); - - await this.userService.deleteUserSpace(space.uuid); - await this.subSpaceService.deleteSubspaces( - deleteSubspaceDtos, - queryRunner, - ); - - await this.tagService.deleteTags(deleteSpaceTagsDtos, queryRunner); - - await this.deviceService.deleteDevice( - space.devices, - orphanSpace, - queryRunner, - ); - await this.spaceLinkService.deleteSpaceLink(space, queryRunner); - await this.sceneService.deleteScenes(space, queryRunner); - } catch (error) { - await queryRunner.rollbackTransaction(); - } finally { - await queryRunner.release(); - } - } - private updateSpaceProperties( space: SpaceEntity, updateSpaceDto: UpdateSpaceDto, diff --git a/src/space/space.module.ts b/src/space/space.module.ts index c3b29d3..f9a2317 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -61,9 +61,13 @@ import { import { UserSpaceService } from 'src/users/services'; import { UserDevicePermissionService } from 'src/user-device-permission/services'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; +import { CqrsModule } from '@nestjs/cqrs'; +import { DisableSpaceHandler } from './handlers'; + +export const CommandHandlers = [DisableSpaceHandler]; @Module({ - imports: [ConfigModule, SpaceRepositoryModule, CommunityModule], + imports: [ConfigModule, SpaceRepositoryModule, CommunityModule, CqrsModule], controllers: [ SpaceController, SpaceUserController, @@ -110,6 +114,7 @@ import { PermissionTypeRepository } from '@app/common/modules/permission/reposit UserDevicePermissionService, DeviceUserPermissionRepository, PermissionTypeRepository, + ...CommandHandlers, ], exports: [SpaceService], }) From 30d0866579ff2e62c6663b7b85aa21bdaf083ce4 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 27 Dec 2024 09:32:20 +0400 Subject: [PATCH 129/247] subspace check --- .../services/subspace/subspace.service.ts | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index e826071..08a7ef1 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -43,6 +43,12 @@ export class SubSpaceService { queryRunner: QueryRunner, ): Promise { try { + const subspaceNames = subspaceData.map((data) => data.subspaceName); + + await this.checkExistingNamesInSpace( + subspaceNames, + subspaceData[0].space, + ); const subspaces = subspaceData.map((data) => queryRunner.manager.create(this.subspaceRepository.target, data), ); @@ -143,6 +149,10 @@ export class SubSpaceService { ); try { + await this.checkExistingNamesInSpace( + [addSubspaceDto.subspaceName], + space, + ); const newSubspace = this.subspaceRepository.create({ ...addSubspaceDto, space, @@ -450,10 +460,7 @@ export class SubSpaceService { } } - private async validateName( - names: string[], - space: SpaceEntity, - ): Promise { + private async checkForDuplicateNames(names: string[]): Promise { const seenNames = new Set(); const duplicateNames = new Set(); @@ -469,14 +476,20 @@ export class SubSpaceService { HttpStatus.CONFLICT, ); } + } + private async checkExistingNamesInSpace( + names: string[], + space: SpaceEntity, + ): Promise { const existingNames = await this.subspaceRepository.find({ select: ['subspaceName'], where: { - subspaceName: In([...seenNames]), + subspaceName: In(names), space: { uuid: space.uuid, }, + disabled: false, }, }); @@ -490,4 +503,12 @@ export class SubSpaceService { ); } } + + private async validateName( + names: string[], + space: SpaceEntity, + ): Promise { + await this.checkForDuplicateNames(names); + await this.checkExistingNamesInSpace(names, space); + } } From 2e623d945e4334770970787f3aaf9465f21246d4 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 27 Dec 2024 09:33:13 +0400 Subject: [PATCH 130/247] remove unused --- src/space/services/space-link/space-link.service.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/space/services/space-link/space-link.service.ts b/src/space/services/space-link/space-link.service.ts index 8ad797e..80d18ed 100644 --- a/src/space/services/space-link/space-link.service.ts +++ b/src/space/services/space-link/space-link.service.ts @@ -1,17 +1,11 @@ import { SpaceEntity, SpaceLinkEntity } from '@app/common/modules/space'; -import { - SpaceLinkRepository, - SpaceRepository, -} from '@app/common/modules/space/repositories'; +import { SpaceLinkRepository } from '@app/common/modules/space/repositories'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { QueryRunner } from 'typeorm'; @Injectable() export class SpaceLinkService { - constructor( - private readonly spaceRepository: SpaceRepository, - private readonly spaceLinkRepository: SpaceLinkRepository, - ) {} + constructor(private readonly spaceLinkRepository: SpaceLinkRepository) {} async saveSpaceLink( startSpaceId: string, From cc5fd68adb78784326227a15afdba7f807d0485c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 27 Dec 2024 09:40:05 +0400 Subject: [PATCH 131/247] remove device-tag relation --- .../src/modules/device/entities/device.entity.ts | 9 ++++++++- .../src/modules/space/entities/tag.entity.ts | 15 ++++++++++++++- src/space/services/tag/tag.service.ts | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index 9a75950..bdd1ce5 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -6,10 +6,11 @@ import { Unique, Index, JoinColumn, + OneToOne, } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { DeviceDto, DeviceUserPermissionDto } from '../dtos/device.dto'; -import { SpaceEntity, SubspaceEntity } from '../../space/entities'; +import { SpaceEntity, SubspaceEntity, TagEntity } from '../../space/entities'; import { ProductEntity } from '../../product/entities'; import { UserEntity } from '../../user/entities'; import { DeviceNotificationDto } from '../dtos'; @@ -74,6 +75,11 @@ export class DeviceEntity extends AbstractEntity { @OneToMany(() => SceneDeviceEntity, (sceneDevice) => sceneDevice.device, {}) sceneDevices: SceneDeviceEntity[]; + @OneToOne(() => TagEntity, (tag) => tag.device, { + nullable: true, + }) + tag: TagEntity; + constructor(partial: Partial) { super(); Object.assign(this, partial); @@ -102,6 +108,7 @@ export class DeviceNotificationEntity extends AbstractEntity) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/space/entities/tag.entity.ts b/libs/common/src/modules/space/entities/tag.entity.ts index 77e79cc..e7f8599 100644 --- a/libs/common/src/modules/space/entities/tag.entity.ts +++ b/libs/common/src/modules/space/entities/tag.entity.ts @@ -1,10 +1,18 @@ -import { Entity, Column, ManyToOne, JoinColumn, Unique } from 'typeorm'; +import { + Entity, + Column, + ManyToOne, + JoinColumn, + Unique, + OneToOne, +} from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { ProductEntity } from '../../product/entities'; import { TagDto } from '../dtos'; import { TagModel } from '../../space-model/entities/tag-model.entity'; import { SpaceEntity } from './space.entity'; import { SubspaceEntity } from './subspace'; +import { DeviceEntity } from '../../device/entities'; @Entity({ name: 'tag' }) @Unique(['tag', 'product', 'space', 'subspace']) @@ -36,4 +44,9 @@ export class TagEntity extends AbstractEntity { default: false, }) public disabled: boolean; + + @OneToOne(() => DeviceEntity, (device) => device.tag, { + nullable: true, + }) + device: DeviceEntity; } diff --git a/src/space/services/tag/tag.service.ts b/src/space/services/tag/tag.service.ts index c9ccc1c..b89dc9c 100644 --- a/src/space/services/tag/tag.service.ts +++ b/src/space/services/tag/tag.service.ts @@ -99,7 +99,7 @@ export class TagService { queryRunner.manager.update( this.tagRepository.target, { uuid: id }, - { disabled: true }, + { disabled: true, device: null }, ), ), ); From 0d539883f16ff8699380c6a0cfd927ee3a4ba047 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 27 Dec 2024 09:57:37 +0400 Subject: [PATCH 132/247] delete subspace devices --- .../services/space-validation.service.ts | 1 + .../subspace/subspace-device.service.ts | 26 +++++++++++++++++++ .../services/subspace/subspace.service.ts | 16 +++++++++--- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts index f58f5c3..89a539a 100644 --- a/src/space/services/space-validation.service.ts +++ b/src/space/services/space-validation.service.ts @@ -49,6 +49,7 @@ export class ValidationService { 'subspaces', 'tags', 'subspaces.tags', + 'subspaces.devices', 'devices', ], }); diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index d6f6320..130da1d 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -9,6 +9,8 @@ import { ProductRepository } from '@app/common/modules/product/repositories'; import { GetDeviceDetailsInterface } from '../../../device/interfaces/get.device.interface'; import { ValidationService } from '../space-validation.service'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; +import { In, QueryRunner } from 'typeorm'; +import { DeviceEntity } from '@app/common/modules/device/entities'; @Injectable() export class SubspaceDeviceService { @@ -173,6 +175,30 @@ export class SubspaceDeviceService { return device; } + async deleteSubspaceDevices( + devices: DeviceEntity[], + queryRunner: QueryRunner, + ): Promise { + const deviceUuids = devices.map((device) => device.uuid); + + try { + if (deviceUuids.length === 0) { + return; + } + + await queryRunner.manager.update( + this.deviceRepository.target, + { uuid: In(deviceUuids) }, + { subspace: null }, + ); + } catch (error) { + throw new HttpException( + `Failed to delete devices with IDs ${deviceUuids.join(', ')}: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + private throwNotFound(entity: string, uuid: string) { throw new HttpException( `${entity} with ID ${uuid} not found`, diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 08a7ef1..dbaa3b3 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -25,6 +25,7 @@ import { ValidationService } from '../space-validation.service'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; import { TagService } from '../tag'; import { ModifyAction } from '@app/common/constants/modify-action.enum'; +import { SubspaceDeviceService } from './subspace-device.service'; @Injectable() export class SubSpaceService { @@ -32,6 +33,7 @@ export class SubSpaceService { private readonly subspaceRepository: SubspaceRepository, private readonly validationService: ValidationService, private readonly tagService: TagService, + public readonly deviceService: SubspaceDeviceService, ) {} async createSubspaces( @@ -280,7 +282,7 @@ export class SubSpaceService { const deleteResults: { uuid: string }[] = []; for (const dto of deleteDtos) { - const subspaceModel = await this.findOne(dto.subspaceUuid); + const subspace = await this.findOne(dto.subspaceUuid); await queryRunner.manager.update( this.subspaceRepository.target, @@ -288,8 +290,8 @@ export class SubSpaceService { { disabled: true }, ); - if (subspaceModel.tags?.length) { - const modifyTagDtos = subspaceModel.tags.map((tag) => ({ + if (subspace.tags?.length) { + const modifyTagDtos = subspace.tags.map((tag) => ({ uuid: tag.uuid, action: ModifyAction.DELETE, })); @@ -297,10 +299,16 @@ export class SubSpaceService { modifyTagDtos, queryRunner, null, - subspaceModel, + subspace, ); } + if (subspace.devices) + await this.deviceService.deleteSubspaceDevices( + subspace.devices, + queryRunner, + ); + deleteResults.push({ uuid: dto.subspaceUuid }); } From d56b26d3ea6ed6ec95188174a319a2347677cd47 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 28 Dec 2024 19:41:24 -0600 Subject: [PATCH 133/247] Add Mailtrap API token and invitation template functionality --- libs/common/src/config/email.config.ts | 3 ++ libs/common/src/constants/mail-trap.ts | 1 + libs/common/src/util/email.service.ts | 42 +++++++++++++++++++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 libs/common/src/constants/mail-trap.ts diff --git a/libs/common/src/config/email.config.ts b/libs/common/src/config/email.config.ts index 3a5c21e..e18d4e8 100644 --- a/libs/common/src/config/email.config.ts +++ b/libs/common/src/config/email.config.ts @@ -10,5 +10,8 @@ export default registerAs( SMTP_USER: process.env.SMTP_USER, SMTP_SENDER: process.env.SMTP_SENDER, SMTP_PASSWORD: process.env.SMTP_PASSWORD, + MAILTRAP_API_TOKEN: process.env.MAILTRAP_API_TOKEN, + MAILTRAP_INVITATION_TEMPLATE_UUID: + process.env.MAILTRAP_INVITATION_TEMPLATE_UUID, }), ); diff --git a/libs/common/src/constants/mail-trap.ts b/libs/common/src/constants/mail-trap.ts new file mode 100644 index 0000000..3d989bc --- /dev/null +++ b/libs/common/src/constants/mail-trap.ts @@ -0,0 +1 @@ +export const SEND_EMAIL_API_URL = 'https://send.api.mailtrap.io/api/send'; diff --git a/libs/common/src/util/email.service.ts b/libs/common/src/util/email.service.ts index f3a389d..c1ad34d 100644 --- a/libs/common/src/util/email.service.ts +++ b/libs/common/src/util/email.service.ts @@ -1,6 +1,8 @@ -import { Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as nodemailer from 'nodemailer'; +import axios from 'axios'; +import { SEND_EMAIL_API_URL } from '../constants/mail-trap'; @Injectable() export class EmailService { @@ -35,4 +37,42 @@ export class EmailService { await transporter.sendMail(mailOptions); } + async sendEmailWithInvitationTemplate( + email: string, + emailInvitationData: any, + ): Promise { + const API_TOKEN = this.configService.get( + 'email-config.MAILTRAP_API_TOKEN', + ); + const TEMPLATE_UUID = this.configService.get( + 'email-config.MAILTRAP_INVITATION_TEMPLATE_UUID', + ); + + const emailData = { + from: { + email: this.configService.get('email-config.SMTP_SENDER'), + }, + to: [ + { + email: email, + }, + ], + template_uuid: TEMPLATE_UUID, + template_variables: emailInvitationData, + }; + + try { + await axios.post(SEND_EMAIL_API_URL, emailData, { + headers: { + Authorization: `Bearer ${API_TOKEN}`, + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + throw new HttpException( + error.message || 'Error sending email using Mailtrap template', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } From e3ebcff78a77ac84238323f00e7ac8ca1808625d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 28 Dec 2024 19:41:30 -0600 Subject: [PATCH 134/247] Add EmailService and send invitation email in InviteUserService --- src/invite-user/invite-user.module.ts | 2 ++ .../services/invite-user.service.ts | 20 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index e77f875..402ebb1 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -9,6 +9,7 @@ import { InviteUserRepository, InviteUserSpaceRepository, } from '@app/common/modules/Invite-user/repositiories'; +import { EmailService } from '@app/common/util/email.service'; @Module({ imports: [ConfigModule, InviteUserRepositoryModule], @@ -17,6 +18,7 @@ import { InviteUserService, InviteUserRepository, UserRepository, + EmailService, InviteUserSpaceRepository, ], exports: [InviteUserService], diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 2180d0b..b49ed0b 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -4,7 +4,7 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { UserStatusEnum } from '@app/common/constants/user-status.enum'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { generateRandomString } from '@app/common/helper/randomString'; -import { IsNull, Not } from 'typeorm'; +import { In, IsNull, Not } from 'typeorm'; import { DataSource } from 'typeorm'; import { UserEntity } from '@app/common/modules/user/entities'; import { RoleType } from '@app/common/constants/role.type.enum'; @@ -14,12 +14,15 @@ import { } from '@app/common/modules/Invite-user/repositiories'; import { CheckEmailDto } from '../dtos/check-email.dto'; import { UserRepository } from '@app/common/modules/user/repositories'; +import { EmailService } from '@app/common/util/email.service'; +import { SpaceEntity } from '@app/common/modules/space'; @Injectable() export class InviteUserService { constructor( private readonly inviteUserRepository: InviteUserRepository, private readonly userRepository: UserRepository, + private readonly emailService: EmailService, private readonly inviteUserSpaceRepository: InviteUserSpaceRepository, private readonly dataSource: DataSource, ) {} @@ -75,7 +78,16 @@ export class InviteUserService { }); const invitedUser = await queryRunner.manager.save(inviteUser); + const spaceRepo = queryRunner.manager.getRepository(SpaceEntity); + const spaces = await spaceRepo.find({ + where: { + uuid: In(spaceUuids), + }, + }); + const spaceNames = spaces.map((space) => space.spaceName); + + const spaceNamesString = spaceNames.join(', '); const spacePromises = spaceUuids.map(async (spaceUuid) => { const inviteUserSpace = this.inviteUserSpaceRepository.create({ inviteUser: { uuid: invitedUser.uuid }, @@ -85,6 +97,12 @@ export class InviteUserService { }); await Promise.all(spacePromises); + await this.emailService.sendEmailWithInvitationTemplate(email, { + name: firstName + ' ' + lastName, + invitationCode, + role: roleType, + spacesList: spaceNamesString, + }); await queryRunner.commitTransaction(); From a96dccd19d78e7de69310f7d604fad63d1368f87 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 29 Dec 2024 22:31:56 -0600 Subject: [PATCH 135/247] Add endpoint to get users by project --- libs/common/src/constants/controller-route.ts | 6 ++ src/project/controllers/project.controller.ts | 14 ++++ src/project/project.module.ts | 4 + src/project/services/project.service.ts | 78 +++++++++++++++++++ 4 files changed, 102 insertions(+) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 7ef8808..80088aa 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -21,6 +21,11 @@ export class ControllerRoute { public static readonly DELETE_PROJECT_SUMMARY = 'Delete a project'; public static readonly DELETE_PROJECT_DESCRIPTION = 'This endpoint deletes an existing project by its unique identifier (UUID).'; + + public static readonly GET_USERS_BY_PROJECT_SUMMARY = + 'Get users by project'; + public static readonly GET_USERS_BY_PROJECT_DESCRIPTION = + 'This endpoint retrieves all users associated with a specific project.'; }; }; @@ -733,6 +738,7 @@ export class ControllerRoute { public static readonly CREATE_USER_INVITATION_DESCRIPTION = 'This endpoint creates an invitation for a user to assign to role and spaces.'; + public static readonly CHECK_EMAIL_SUMMARY = 'Check email'; public static readonly CHECK_EMAIL_DESCRIPTION = diff --git a/src/project/controllers/project.controller.ts b/src/project/controllers/project.controller.ts index a39ced1..c888acb 100644 --- a/src/project/controllers/project.controller.ts +++ b/src/project/controllers/project.controller.ts @@ -86,4 +86,18 @@ export class ProjectController { async findOne(@Param() params: GetProjectParam): Promise { return this.projectService.getProject(params.projectUuid); } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.PROJECT.ACTIONS.GET_USERS_BY_PROJECT_SUMMARY, + description: + ControllerRoute.PROJECT.ACTIONS.GET_USERS_BY_PROJECT_DESCRIPTION, + }) + @Get(':projectUuid/users') + async findUsersByProject( + @Param() params: GetProjectParam, + ): Promise { + return this.projectService.getUsersByProject(params.projectUuid); + } } diff --git a/src/project/project.module.ts b/src/project/project.module.ts index 8711499..0afbd1c 100644 --- a/src/project/project.module.ts +++ b/src/project/project.module.ts @@ -6,6 +6,8 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { CreateOrphanSpaceHandler } from './handler'; import { SpaceRepository } from '@app/common/modules/space'; import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; +import { UserRepository } from '@app/common/modules/user/repositories'; const CommandHandlers = [CreateOrphanSpaceHandler]; @@ -19,6 +21,8 @@ const CommandHandlers = [CreateOrphanSpaceHandler]; CommunityRepository, ProjectService, ProjectRepository, + InviteUserRepository, + UserRepository, ], exports: [ProjectService, CqrsModule], }) diff --git a/src/project/services/project.service.ts b/src/project/services/project.service.ts index 75aa67a..1d5e609 100644 --- a/src/project/services/project.service.ts +++ b/src/project/services/project.service.ts @@ -12,11 +12,17 @@ import { ProjectDto } from '@app/common/modules/project/dtos'; import { PageResponse } from '@app/common/dto/pagination.response.dto'; import { CommandBus } from '@nestjs/cqrs'; import { CreateOrphanSpaceCommand } from '../command/create-orphan-space-command'; +import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; +import { UserRepository } from '@app/common/modules/user/repositories'; +import { UserStatusEnum } from '@app/common/constants/user-status.enum'; +import { RoleType } from '@app/common/constants/role.type.enum'; @Injectable() export class ProjectService { constructor( private readonly projectRepository: ProjectRepository, + private readonly inviteUserRepository: InviteUserRepository, + private readonly userRepository: UserRepository, private commandBus: CommandBus, ) {} @@ -181,6 +187,78 @@ export class ProjectService { } } + async getUsersByProject(uuid: string): Promise { + try { + // Fetch invited users + const invitedUsers = await this.inviteUserRepository.find({ + where: { project: { uuid }, isActive: true }, + select: [ + 'firstName', + 'lastName', + 'email', + 'createdAt', + 'status', + 'phoneNumber', + 'jobTitle', + 'invitedBy', + 'isEnabled', + ], + relations: ['roleType'], + }); + + // Fetch project users + const users = await this.userRepository.find({ + where: { project: { uuid }, isActive: true }, + select: ['firstName', 'lastName', 'email', 'createdAt'], + relations: ['roleType'], + }); + + // Combine both arrays + const allUsers = [...users, ...invitedUsers]; + + const normalizedUsers = allUsers.map((user) => { + const createdAt = new Date(user.createdAt); + const createdDate = createdAt.toLocaleDateString(); + const createdTime = createdAt.toLocaleTimeString(); + + // Normalize user properties + const normalizedProps = this.normalizeUserProperties(user); + + // Return the normalized user object + return { + ...user, + createdDate, + createdTime, + ...normalizedProps, + }; + }); + + return new SuccessResponseDto({ + message: `Users in project with ID ${uuid} retrieved successfully`, + data: normalizedUsers, + statusCode: HttpStatus.OK, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + `An error occurred while retrieving users in the project with id ${uuid}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + normalizeUserProperties(user: any) { + return { + status: user.status ?? UserStatusEnum.ACTIVE, + invitedBy: user.invitedBy ?? RoleType.SPACE_MEMBER, + isEnabled: user.isEnabled ?? true, + phoneNumber: user.phoneNumber ?? null, + jobTitle: user.jobTitle ?? null, + roleType: user.roleType?.type ?? null, + }; + } async findOne(uuid: string): Promise { const project = await this.projectRepository.findOne({ where: { uuid } }); return project; From 6a89a17ce92b07e45a44308ac5b2f900bbdd3ef5 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 30 Dec 2024 10:06:33 +0400 Subject: [PATCH 136/247] disable space --- src/space-model/commands/index.ts | 1 + .../propagate-space-model-deletion.command.ts | 9 + .../propogate-subspace-update-command.ts | 12 +- src/space-model/handlers/index.ts | 1 + .../handlers/propate-subspace-handler.ts | 340 +++++++++++------- .../propogate-space-model-deletion.handler.ts | 57 +++ src/space-model/interfaces/index.ts | 3 +- .../interfaces/modify-subspace.interface.ts | 24 ++ .../services/space-model.service.ts | 34 +- .../subspace/subspace-model.service.ts | 57 ++- src/space-model/services/tag-model.service.ts | 54 ++- src/space-model/space-model.module.ts | 40 ++- .../interfaces/add-subspace.interface.ts | 5 + src/space/interfaces/index.ts | 0 src/space/services/space.service.ts | 33 +- .../services/subspace/subspace.service.ts | 50 ++- src/space/services/tag/tag.service.ts | 61 ++++ 17 files changed, 604 insertions(+), 177 deletions(-) create mode 100644 src/space-model/commands/propagate-space-model-deletion.command.ts create mode 100644 src/space-model/handlers/propogate-space-model-deletion.handler.ts create mode 100644 src/space-model/interfaces/modify-subspace.interface.ts create mode 100644 src/space/interfaces/add-subspace.interface.ts create mode 100644 src/space/interfaces/index.ts diff --git a/src/space-model/commands/index.ts b/src/space-model/commands/index.ts index 716d458..da760ea 100644 --- a/src/space-model/commands/index.ts +++ b/src/space-model/commands/index.ts @@ -1 +1,2 @@ export * from './propogate-subspace-update-command'; +export * from './propagate-space-model-deletion.command'; diff --git a/src/space-model/commands/propagate-space-model-deletion.command.ts b/src/space-model/commands/propagate-space-model-deletion.command.ts new file mode 100644 index 0000000..0cacd5b --- /dev/null +++ b/src/space-model/commands/propagate-space-model-deletion.command.ts @@ -0,0 +1,9 @@ +import { SpaceModelEntity } from '@app/common/modules/space-model'; + +export class PropogateDeleteSpaceModelCommand { + constructor( + public readonly param: { + spaceModel: SpaceModelEntity; + }, + ) {} +} diff --git a/src/space-model/commands/propogate-subspace-update-command.ts b/src/space-model/commands/propogate-subspace-update-command.ts index 96276ee..3b8ccdb 100644 --- a/src/space-model/commands/propogate-subspace-update-command.ts +++ b/src/space-model/commands/propogate-subspace-update-command.ts @@ -1,6 +1,12 @@ import { ICommand } from '@nestjs/cqrs'; -import { IModifySubspaceModelInterface } from '../interfaces'; +import { SpaceModelEntity } from '@app/common/modules/space-model'; +import { ModifyspaceModelPayload } from '../interfaces'; -export class PropogateSubspaceCommand implements ICommand { - constructor(public readonly param: IModifySubspaceModelInterface) {} +export class PropogateUpdateSpaceModelCommand implements ICommand { + constructor( + public readonly param: { + spaceModel: SpaceModelEntity; + modifiedSpaceModels: ModifyspaceModelPayload; + }, + ) {} } diff --git a/src/space-model/handlers/index.ts b/src/space-model/handlers/index.ts index 7dfa7f5..d7bb550 100644 --- a/src/space-model/handlers/index.ts +++ b/src/space-model/handlers/index.ts @@ -1 +1,2 @@ export * from './propate-subspace-handler'; +export * from './propogate-space-model-deletion.handler'; diff --git a/src/space-model/handlers/propate-subspace-handler.ts b/src/space-model/handlers/propate-subspace-handler.ts index 7d205f9..f59aeb2 100644 --- a/src/space-model/handlers/propate-subspace-handler.ts +++ b/src/space-model/handlers/propate-subspace-handler.ts @@ -1,170 +1,250 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { PropogateSubspaceCommand } from '../commands'; -import { Logger } from '@nestjs/common'; +import { PropogateUpdateSpaceModelCommand } from '../commands'; import { SpaceEntity, SpaceRepository } from '@app/common/modules/space'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; import { - IDeletedSubsaceModelInterface, - IUpdateSubspaceModelInterface, -} from '../interfaces'; + SpaceModelEntity, + SubspaceModelEntity, + TagModel, +} from '@app/common/modules/space-model'; +import { DataSource, QueryRunner } from 'typeorm'; +import { SubSpaceService } from 'src/space/services'; +import { TagService } from 'src/space/services/tag'; +import { TagModelService } from '../services'; +import { UpdatedSubspaceModelPayload } from '../interfaces'; +import { ModifyAction } from '@app/common/constants/modify-action.enum'; +import { ModifySubspaceDto } from 'src/space/dtos'; -@CommandHandler(PropogateSubspaceCommand) -export class PropogateSubspaceHandler - implements ICommandHandler +@CommandHandler(PropogateUpdateSpaceModelCommand) +export class PropogateUpdateSpaceModelHandler + implements ICommandHandler { - private readonly logger = new Logger(PropogateSubspaceHandler.name); - constructor( private readonly spaceRepository: SpaceRepository, private readonly subspaceRepository: SubspaceRepository, + private readonly dataSource: DataSource, + private readonly subSpaceService: SubSpaceService, + private readonly tagService: TagService, + private readonly tagModelService: TagModelService, ) {} - async execute(command: PropogateSubspaceCommand): Promise { + async execute(command: PropogateUpdateSpaceModelCommand): Promise { + const { spaceModel, modifiedSpaceModels } = command.param; + const queryRunner = this.dataSource.createQueryRunner(); + try { - const newSubspaceModels = command.param?.new; - const updateSubspaceModels = command.param?.update; - const deleteSubspaceModels = command.param?.delete; - - if (!newSubspaceModels && !updateSubspaceModels) { - this.logger.warn('No new or updated subspace models provided.'); - return; - } - - const spaceModelUuid = command.param.spaceModelUuid; - - if (!spaceModelUuid) { - this.logger.error( - 'Space model UUID is missing in the command parameters.', + await queryRunner.connect(); + await queryRunner.startTransaction(); + const spaces = await this.spaceRepository.find({ + where: { spaceModel }, + }); + if ( + modifiedSpaceModels.modifiedSubspaceModels.addedSubspaceModels.length > + 0 + ) { + await this.addSubspaceModels( + modifiedSpaceModels.modifiedSubspaceModels.addedSubspaceModels, + spaces, + queryRunner, ); - return; + } else if ( + modifiedSpaceModels.modifiedSubspaceModels.updatedSubspaceModels + .length > 0 + ) { + await this.updateSubspaceModels( + modifiedSpaceModels.modifiedSubspaceModels.updatedSubspaceModels, + queryRunner, + ); + } + if ( + modifiedSpaceModels.modifiedSubspaceModels.deletedSubspaceModels?.length + ) { + const dtos: ModifySubspaceDto[] = + modifiedSpaceModels.modifiedSubspaceModels.deletedSubspaceModels.map( + (model) => ({ + action: ModifyAction.DELETE, + uuid: model, + }), + ); + await this.subSpaceService.modifySubSpace(dtos, queryRunner); } - const spaces = await this.getSpacesByModel(spaceModelUuid); - - if (spaces.length === 0) { - this.logger.warn(`No spaces found for model UUID: ${spaceModelUuid}`); - return; + if (modifiedSpaceModels.modifiedTags.added.length > 0) { + await this.createTags( + modifiedSpaceModels.modifiedTags.added, + queryRunner, + null, + spaceModel, + ); } - if (newSubspaceModels && newSubspaceModels.length > 0) { - await this.processNewSubspaces(newSubspaceModels, spaces); + if (modifiedSpaceModels.modifiedTags.updated.length > 0) { + await this.updateTags( + modifiedSpaceModels.modifiedTags.updated, + queryRunner, + ); } - if (updateSubspaceModels && updateSubspaceModels.length > 0) { - await this.updateSubspaces(updateSubspaceModels); + if (modifiedSpaceModels.modifiedTags.deleted.length > 0) { + await this.deleteTags( + modifiedSpaceModels.modifiedTags.deleted, + queryRunner, + ); } - if (deleteSubspaceModels && deleteSubspaceModels.length > 0) { - await this.deleteSubspaces(deleteSubspaceModels); - } + await queryRunner.commitTransaction(); } catch (error) { - this.logger.error( - 'Error in PropogateSubspaceHandler execution', - error.stack, + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); + } + } + + async addSubspaceModels( + subspaceModels: SubspaceModelEntity[], + spaces: SpaceEntity[], + queryRunner: QueryRunner, + ) { + for (const space of spaces) { + await this.subSpaceService.createSubSpaceFromModel( + subspaceModels, + space, + queryRunner, ); } } - private async updateSubspaces( - models: IUpdateSubspaceModelInterface[], + async updateSubspaceModels( + subspaceModels: UpdatedSubspaceModelPayload[], + queryRunner: QueryRunner, ): Promise { - try { - const updatePromises = []; + const subspaceUpdatePromises = subspaceModels.map(async (model) => { + const { + updated: tagsToUpdate, + deleted: tagsToDelete, + added: tagsToAdd, + } = model.modifiedTags; - for (const model of models) { - const { uuid: subspaceModelUuid, subspaceName } = model; + // Perform tag operations concurrently + await Promise.all([ + tagsToUpdate?.length && this.updateTags(tagsToUpdate, queryRunner), + tagsToDelete?.length && this.deleteTags(tagsToDelete, queryRunner), + tagsToAdd?.length && + this.createTags( + tagsToAdd, + queryRunner, + model.subspaceModelUuid, + null, + ), + ]); - if (subspaceName) { - updatePromises.push( - this.subspaceRepository - .createQueryBuilder() - .update() - .set({ subspaceName }) - .where('subSpaceModelUuid = :uuid', { uuid: subspaceModelUuid }) - .execute(), - ); - } + // Update subspace names + const subspaces = await queryRunner.manager.find( + this.subspaceRepository.target, + { + where: { + subSpaceModel: { + uuid: model.subspaceModelUuid, + }, + }, + }, + ); + + if (subspaces.length > 0) { + const updateSubspacePromises = subspaces.map((subspace) => + queryRunner.manager.update( + this.subspaceRepository.target, + { uuid: subspace.uuid }, + { subspaceName: model.subspaceName }, + ), + ); + await Promise.all(updateSubspacePromises); } - - await Promise.all(updatePromises); - } catch (error) { - this.logger.error('Error in updateSubspaces method', error.stack); - } - } - - private async deleteSubspaces(models: IDeletedSubsaceModelInterface[]) { - try { - const updatePromises = []; - - for (const model of models) { - const { uuid: subspaceModelUuid } = model; - - if (subspaceModelUuid) { - updatePromises.push( - this.subspaceRepository - .createQueryBuilder() - .update() - .set({ disabled: true }) - .where('subSpaceModelUuid = :uuid', { uuid: subspaceModelUuid }) - .execute(), - ); - } - } - - await Promise.all(updatePromises); - } catch (error) { - this.logger.error('Error in delete subspace models', error.stack); - } - } - - private async processNewSubspaces( - newSubspaceModels: any[], - spaces: SpaceEntity[], - ) { - for (const newSubspaceModel of newSubspaceModels) { - for (const space of spaces) { - try { - await this.createSubspace(newSubspaceModel, space); - - if (newSubspaceModel.productModels?.length > 0) { - } - } catch (error) { - this.logger.error( - `Failed to create subspace for space ID: ${space.uuid}`, - error.stack, - ); - } - } - } - } - - private async createSubspace(newSubspaceModel: any, space: SpaceEntity) { - const subspace = this.subspaceRepository.create({ - subspaceName: newSubspaceModel.subspaceModel.subspaceName, - space, - subSpaceModel: newSubspaceModel.subspaceModel, }); - const createdSubspace = await this.subspaceRepository.save(subspace); - this.logger.log( - `Subspace created for space ${space.uuid} with name ${createdSubspace.subspaceName}`, + // Wait for all subspace model updates to complete + await Promise.all(subspaceUpdatePromises); + } + async updateTags(models: TagModel[], queryRunner: QueryRunner) { + if (!models?.length) return; + + const updatePromises = models.map((model) => + this.tagService.updateTagsFromModel(model, queryRunner), ); - return createdSubspace; + await Promise.all(updatePromises); } - private async getSpacesByModel(uuid: string): Promise { - try { - return await this.spaceRepository.find({ - where: { - spaceModel: { uuid }, + async deleteTags(uuids: string[], queryRunner: QueryRunner) { + const deletePromises = uuids.map((uuid) => + this.tagService.deleteTagFromModel(uuid, queryRunner), + ); + await Promise.all(deletePromises); + } + + async createTags( + models: TagModel[], + queryRunner: QueryRunner, + subspaceModelUuid?: string, + spaceModel?: SpaceModelEntity, + ): Promise { + if (!models.length) { + return; + } + + if (subspaceModelUuid) { + await this.processSubspaces(subspaceModelUuid, models, queryRunner); + } + + if (spaceModel) { + await this.processSpaces(spaceModel.uuid, models, queryRunner); + } + } + + private async processSubspaces( + subspaceModelUuid: string, + models: TagModel[], + queryRunner: QueryRunner, + ): Promise { + const subspaces = await this.subspaceRepository.find({ + where: { + subSpaceModel: { + uuid: subspaceModelUuid, }, - }); - } catch (error) { - this.logger.error( - `Failed to fetch spaces for model UUID: ${uuid}`, - error.stack, + }, + }); + + if (subspaces.length > 0) { + const subspacePromises = subspaces.map((subspace) => + this.tagService.createTagsFromModel( + queryRunner, + models, + null, + subspace, + ), ); - throw error; + await Promise.all(subspacePromises); + } + } + + private async processSpaces( + spaceModelUuid: string, + models: TagModel[], + queryRunner: QueryRunner, + ): Promise { + const spaces = await this.spaceRepository.find({ + where: { + spaceModel: { + uuid: spaceModelUuid, + }, + }, + }); + + if (spaces.length > 0) { + const spacePromises = spaces.map((space) => + this.tagService.createTagsFromModel(queryRunner, models, space, null), + ); + await Promise.all(spacePromises); } } } diff --git a/src/space-model/handlers/propogate-space-model-deletion.handler.ts b/src/space-model/handlers/propogate-space-model-deletion.handler.ts new file mode 100644 index 0000000..acd690e --- /dev/null +++ b/src/space-model/handlers/propogate-space-model-deletion.handler.ts @@ -0,0 +1,57 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { Logger } from '@nestjs/common'; + +import { PropogateDeleteSpaceModelCommand } from '../commands'; +import { SpaceRepository } from '@app/common/modules/space'; +import { SpaceService } from '../../space/services/space.service'; +import { DataSource } from 'typeorm'; + +@CommandHandler(PropogateDeleteSpaceModelCommand) +export class PropogateDeleteSpaceModelHandler + implements ICommandHandler +{ + private readonly logger = new Logger(PropogateDeleteSpaceModelHandler.name); + + constructor( + private readonly spaceRepository: SpaceRepository, + private readonly spaceService: SpaceService, + private readonly dataSource: DataSource, + ) {} + + async execute(command: PropogateDeleteSpaceModelCommand): Promise { + const { spaceModel } = command.param; + const queryRunner = this.dataSource.createQueryRunner(); + + try { + await queryRunner.connect(); + await queryRunner.startTransaction(); + const spaces = await this.spaceRepository.find({ + where: { + spaceModel: { + uuid: spaceModel.uuid, + }, + }, + relations: ['subspaces', 'tags', 'subspaces.tags'], + }); + + for (const space of spaces) { + try { + await this.spaceService.unlinkSpaceFromModel(space, queryRunner); + } catch (innerError) { + this.logger.error( + `Error unlinking space model for space with UUID ${space.uuid}:`, + innerError.stack || innerError, + ); + } + } + } catch (error) { + await queryRunner.rollbackTransaction(); + this.logger.error( + 'Error propagating delete space model:', + error.stack || error, + ); + } finally { + await queryRunner.release(); + } + } +} diff --git a/src/space-model/interfaces/index.ts b/src/space-model/interfaces/index.ts index 606eb46..0bacfcd 100644 --- a/src/space-model/interfaces/index.ts +++ b/src/space-model/interfaces/index.ts @@ -1 +1,2 @@ -export * from './update-subspace.interface' \ No newline at end of file +export * from './update-subspace.interface'; +export * from './modify-subspace.interface'; diff --git a/src/space-model/interfaces/modify-subspace.interface.ts b/src/space-model/interfaces/modify-subspace.interface.ts new file mode 100644 index 0000000..8969baf --- /dev/null +++ b/src/space-model/interfaces/modify-subspace.interface.ts @@ -0,0 +1,24 @@ +import { SubspaceModelEntity, TagModel } from '@app/common/modules/space-model'; + +export interface ModifyspaceModelPayload { + modifiedSubspaceModels?: ModifySubspaceModelPayload; + modifiedTags?: ModifiedTagsModelPayload; +} + +export interface ModifySubspaceModelPayload { + addedSubspaceModels?: SubspaceModelEntity[]; + updatedSubspaceModels?: UpdatedSubspaceModelPayload[]; + deletedSubspaceModels?: string[]; +} + +export interface UpdatedSubspaceModelPayload { + subspaceName?: string; + modifiedTags?: ModifiedTagsModelPayload; + subspaceModelUuid: string; +} + +export interface ModifiedTagsModelPayload { + added?: TagModel[]; + updated?: TagModel[]; + deleted?: string[]; +} diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 7704fd1..89e9edb 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -19,6 +19,12 @@ import { ProjectService } from 'src/project/services'; import { ProjectEntity } from '@app/common/modules/project/entities'; import { TagModelService } from './tag-model.service'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { CommandBus } from '@nestjs/cqrs'; +import { PropogateUpdateSpaceModelCommand } from '../commands'; +import { + ModifiedTagsModelPayload, + ModifySubspaceModelPayload, +} from '../interfaces'; @Injectable() export class SpaceModelService { @@ -28,6 +34,7 @@ export class SpaceModelService { private readonly projectService: ProjectService, private readonly subSpaceModelService: SubSpaceModelService, private readonly tagModelService: TagModelService, + private commandBus: CommandBus, ) {} async createSpaceModel( @@ -136,6 +143,9 @@ export class SpaceModelService { const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); + + let modifiedSubspaceModels: ModifySubspaceModelPayload = {}; + let modifiedTagsModelPayload: ModifiedTagsModelPayload = {}; try { const { modelName } = dto; if (modelName) { @@ -145,22 +155,34 @@ export class SpaceModelService { } if (dto.subspaceModels) { - await this.subSpaceModelService.modifySubSpaceModels( - dto.subspaceModels, - spaceModel, - queryRunner, - ); + modifiedSubspaceModels = + await this.subSpaceModelService.modifySubSpaceModels( + dto.subspaceModels, + spaceModel, + queryRunner, + ); } if (dto.tags) { - await this.tagModelService.modifyTags( + modifiedTagsModelPayload = await this.tagModelService.modifyTags( dto.tags, queryRunner, spaceModel, ); } + await queryRunner.commitTransaction(); + await this.commandBus.execute( + new PropogateUpdateSpaceModelCommand({ + spaceModel: spaceModel, + modifiedSpaceModels: { + modifiedSubspaceModels, + modifiedTags: modifiedTagsModelPayload, + }, + }), + ); + return new SuccessResponseDto({ message: 'SpaceModel updated successfully', data: spaceModel, diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index 6b8cb9f..e8a7bd0 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -6,7 +6,11 @@ import { import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { CreateSubspaceModelDto, CreateTagModelDto } from '../../dtos'; import { In, QueryRunner } from 'typeorm'; -import { IDeletedSubsaceModelInterface } from 'src/space-model/interfaces'; +import { + IDeletedSubsaceModelInterface, + ModifySubspaceModelPayload, + UpdatedSubspaceModelPayload, +} from 'src/space-model/interfaces'; import { DeleteSubspaceModelDto, ModifySubspaceModelDto, @@ -99,17 +103,30 @@ export class SubSpaceModelService { subspaceDtos: ModifySubspaceModelDto[], spaceModel: SpaceModelEntity, queryRunner: QueryRunner, - ): Promise { + ): Promise { + const modifiedSubspaceModels: ModifySubspaceModelPayload = {}; for (const subspace of subspaceDtos) { switch (subspace.action) { case ModifyAction.ADD: - await this.handleAddAction(subspace, spaceModel, queryRunner); + const subspaceModel = await this.handleAddAction( + subspace, + spaceModel, + queryRunner, + ); + modifiedSubspaceModels.addedSubspaceModels.push(subspaceModel); break; case ModifyAction.UPDATE: - await this.handleUpdateAction(subspace, queryRunner); + const updatedSubspaceModel = await this.handleUpdateAction( + subspace, + queryRunner, + ); + modifiedSubspaceModels.updatedSubspaceModels.push( + updatedSubspaceModel, + ); break; case ModifyAction.DELETE: await this.handleDeleteAction(subspace, queryRunner); + modifiedSubspaceModels.deletedSubspaceModels.push(subspace.uuid); break; default: throw new HttpException( @@ -118,29 +135,42 @@ export class SubSpaceModelService { ); } } + return modifiedSubspaceModels; } private async handleAddAction( subspace: ModifySubspaceModelDto, spaceModel: SpaceModelEntity, queryRunner: QueryRunner, - ): Promise { + ): Promise { const createTagDtos: CreateTagModelDto[] = subspace.tags?.map((tag) => ({ - tag: tag.tag as string, - productUuid: tag.productUuid as string, + tag: tag.tag, + productUuid: tag.productUuid, })) || []; - await this.createSubSpaceModels( - [{ subspaceName: subspace.subspaceName, tags: createTagDtos }], + + const [createdSubspaceModel] = await this.createSubSpaceModels( + [ + { + subspaceName: subspace.subspaceName, + tags: createTagDtos, + }, + ], spaceModel, queryRunner, ); + + return createdSubspaceModel; } private async handleUpdateAction( modifyDto: ModifySubspaceModelDto, queryRunner: QueryRunner, - ): Promise { + ): Promise { + const updatePayload: UpdatedSubspaceModelPayload = { + subspaceModelUuid: modifyDto.uuid, + }; + const subspace = await this.findOne(modifyDto.uuid); await this.updateSubspaceName( @@ -148,21 +178,24 @@ export class SubSpaceModelService { subspace, modifyDto.subspaceName, ); + updatePayload.subspaceName = modifyDto.subspaceName; if (modifyDto.tags?.length) { - await this.tagModelService.modifyTags( + updatePayload.modifiedTags = await this.tagModelService.modifyTags( modifyDto.tags, queryRunner, null, subspace, ); } + + return updatePayload; } private async handleDeleteAction( subspace: ModifySubspaceModelDto, queryRunner: QueryRunner, - ): Promise { + ) { const subspaceModel = await this.findOne(subspace.uuid); await queryRunner.manager.update( diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index 5682bc2..d08fe38 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -9,6 +9,7 @@ import { TagModelRepository } from '@app/common/modules/space-model'; import { CreateTagModelDto, ModifyTagModelDto } from '../dtos'; import { ProductService } from 'src/product/services'; import { ModifyAction } from '@app/common/constants/modify-action.enum'; +import { ModifiedTagsModelPayload } from '../interfaces'; @Injectable() export class TagModelService { @@ -132,9 +133,9 @@ export class TagModelService { queryRunner: QueryRunner, spaceModel?: SpaceModelEntity, subspaceModel?: SubspaceModelEntity, - ): Promise { + ): Promise { + const modifiedTagModels: ModifiedTagsModelPayload = {}; try { - console.log(tags); for (const tag of tags) { if (tag.action === ModifyAction.ADD) { const createTagDto: CreateTagModelDto = { @@ -142,20 +143,28 @@ export class TagModelService { productUuid: tag.productUuid as string, }; - await this.createTags( + const newModel = await this.createTags( [createTagDto], queryRunner, spaceModel, subspaceModel, ); + modifiedTagModels.added.push(...newModel); } else if (tag.action === ModifyAction.UPDATE) { - await this.updateTag(tag, queryRunner, spaceModel, subspaceModel); + const updatedModel = await this.updateTag( + tag, + queryRunner, + spaceModel, + subspaceModel, + ); + modifiedTagModels.updated.push(updatedModel); } else if (tag.action === ModifyAction.DELETE) { await queryRunner.manager.update( this.tagModelRepository.target, { uuid: tag.uuid }, { disabled: true }, ); + modifiedTagModels.deleted.push(tag.uuid); } else { throw new HttpException( `Invalid action "${tag.action}" provided.`, @@ -163,6 +172,7 @@ export class TagModelService { ); } } + return modifiedTagModels; } catch (error) { if (error instanceof HttpException) { throw error; @@ -235,7 +245,7 @@ export class TagModelService { }); } - private async getTagByUuid(uuid: string): Promise { + async getTagByUuid(uuid: string): Promise { const tag = await this.tagModelRepository.findOne({ where: { uuid }, relations: ['product'], @@ -248,4 +258,38 @@ export class TagModelService { } return tag; } + + async getTagByName( + tag: string, + subspaceUuid?: string, + spaceUuid?: string, + ): Promise { + const queryConditions: any = { tag }; + + if (spaceUuid) { + queryConditions.spaceModel = { uuid: spaceUuid }; + } else if (subspaceUuid) { + queryConditions.subspaceModel = { uuid: subspaceUuid }; + } else { + throw new HttpException( + 'Either spaceUuid or subspaceUuid must be provided.', + HttpStatus.BAD_REQUEST, + ); + } + queryConditions.disabled = false; + + const existingTag = await this.tagModelRepository.findOne({ + where: queryConditions, + relations: ['product'], + }); + + if (!existingTag) { + throw new HttpException( + `Tag model with tag "${tag}" not found.`, + HttpStatus.NOT_FOUND, + ); + } + + return existingTag; + } } diff --git a/src/space-model/space-model.module.ts b/src/space-model/space-model.module.ts index bc3398d..7f72860 100644 --- a/src/space-model/space-model.module.ts +++ b/src/space-model/space-model.module.ts @@ -14,12 +14,34 @@ import { } from '@app/common/modules/space-model'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProductRepository } from '@app/common/modules/product/repositories'; -import { PropogateSubspaceHandler } from './handlers'; +import { + PropogateDeleteSpaceModelHandler, + PropogateUpdateSpaceModelHandler, +} from './handlers'; import { CqrsModule } from '@nestjs/cqrs'; -import { SpaceRepository } from '@app/common/modules/space'; +import { + SpaceLinkRepository, + SpaceRepository, + TagRepository, +} from '@app/common/modules/space'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; +import { + SpaceLinkService, + SpaceService, + SubspaceDeviceService, + SubSpaceService, + ValidationService, +} from 'src/space/services'; +import { TagService } from 'src/space/services/tag'; +import { CommunityService } from 'src/community/services'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; -const CommandHandlers = [PropogateSubspaceHandler]; +const CommandHandlers = [ + PropogateUpdateSpaceModelHandler, + PropogateDeleteSpaceModelHandler, +]; @Module({ imports: [ConfigModule, SpaceRepositoryModule, CqrsModule], @@ -27,6 +49,7 @@ const CommandHandlers = [PropogateSubspaceHandler]; providers: [ ...CommandHandlers, SpaceModelService, + SpaceService, SpaceModelRepository, SpaceRepository, ProjectRepository, @@ -36,6 +59,17 @@ const CommandHandlers = [PropogateSubspaceHandler]; SubspaceRepository, TagModelService, TagModelRepository, + SubSpaceService, + ValidationService, + TagService, + SubspaceDeviceService, + CommunityService, + TagRepository, + DeviceRepository, + TuyaService, + CommunityRepository, + SpaceLinkService, + SpaceLinkRepository, ], exports: [CqrsModule, SpaceModelService], }) diff --git a/src/space/interfaces/add-subspace.interface.ts b/src/space/interfaces/add-subspace.interface.ts new file mode 100644 index 0000000..207622c --- /dev/null +++ b/src/space/interfaces/add-subspace.interface.ts @@ -0,0 +1,5 @@ +import { SubspaceEntity } from '@app/common/modules/space'; + +export interface ModifySubspacePayload { + addedSubspaces?: SubspaceEntity[]; +} diff --git a/src/space/interfaces/index.ts b/src/space/interfaces/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 8fba2d9..9badabb 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -317,8 +317,8 @@ export class SpaceService { if (hasSubspace) { await this.subSpaceService.modifySubSpace( updateSpaceDto.subspace, - space, queryRunner, + space, ); } @@ -353,6 +353,37 @@ export class SpaceService { } } + async unlinkSpaceFromModel( + space: SpaceEntity, + queryRunner: QueryRunner, + ): Promise { + try { + await queryRunner.manager.update( + this.spaceRepository.target, + { uuid: space.uuid }, + { + spaceModel: null, + }, + ); + + // Unlink subspaces and tags if they exist + if (space.subspaces || space.tags) { + if (space.tags) { + await this.tagService.unlinkModels(space.tags, queryRunner); + } + + if (space.subspaces) { + await this.subSpaceService.unlinkModels(space.subspaces, queryRunner); + } + } + } catch (error) { + throw new HttpException( + `Failed to unlink space model: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + private updateSpaceProperties( space: SpaceEntity, updateSpaceDto: UpdateSpaceDto, diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index dbaa3b3..7c5cf6d 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -26,6 +26,7 @@ import { SubspaceRepository } from '@app/common/modules/space/repositories/subsp import { TagService } from '../tag'; import { ModifyAction } from '@app/common/constants/modify-action.enum'; import { SubspaceDeviceService } from './subspace-device.service'; +import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto'; @Injectable() export class SubSpaceService { @@ -317,9 +318,9 @@ export class SubSpaceService { async modifySubSpace( subspaceDtos: ModifySubspaceDto[], - space: SpaceEntity, queryRunner: QueryRunner, - ): Promise { + space?: SpaceEntity, + ) { for (const subspace of subspaceDtos) { switch (subspace.action) { case ModifyAction.ADD: @@ -382,17 +383,18 @@ export class SubSpaceService { subspace: ModifySubspaceDto, space: SpaceEntity, queryRunner: QueryRunner, - ): Promise { + ): Promise { const createTagDtos: CreateTagDto[] = subspace.tags?.map((tag) => ({ tag: tag.tag as string, productUuid: tag.productUuid as string, })) || []; - await this.createSubspacesFromDto( + const subSpace = await this.createSubspacesFromDto( [{ subspaceName: subspace.subspaceName, tags: createTagDtos }], space, queryRunner, ); + return subSpace[0]; } private async handleUpdateAction( @@ -400,16 +402,25 @@ export class SubSpaceService { queryRunner: QueryRunner, ): Promise { const subspace = await this.findOne(modifyDto.uuid); - - await this.updateSubspaceName( + await this.update( queryRunner, subspace, modifyDto.subspaceName, + modifyDto.tags, ); + } - if (modifyDto.tags?.length) { + async update( + queryRunner: QueryRunner, + subspace: SubspaceEntity, + subspaceName?: string, + modifyTagDto?: ModifyTagDto[], + ) { + await this.updateSubspaceName(queryRunner, subspace, subspaceName); + + if (modifyTagDto?.length) { await this.tagService.modifyTags( - modifyDto.tags, + modifyTagDto, queryRunner, null, subspace, @@ -417,11 +428,11 @@ export class SubSpaceService { } } - private async handleDeleteAction( - subspace: ModifySubspaceDto, + async handleDeleteAction( + modifyDto: ModifySubspaceDto, queryRunner: QueryRunner, ): Promise { - const subspaceModel = await this.findOne(subspace.uuid); + const subspace = await this.findOne(modifyDto.uuid); await queryRunner.manager.update( this.subspaceRepository.target, @@ -429,8 +440,8 @@ export class SubSpaceService { { disabled: true }, ); - if (subspaceModel.tags?.length) { - const modifyTagDtos = subspaceModel.tags.map((tag) => ({ + if (subspace.tags?.length) { + const modifyTagDtos = subspace.tags.map((tag) => ({ uuid: tag.uuid, action: ModifyAction.DELETE, })); @@ -438,7 +449,14 @@ export class SubSpaceService { modifyTagDtos, queryRunner, null, - subspaceModel, + subspace, + ); + } + + if (subspace.devices.length > 0) { + await this.deviceService.deleteSubspaceDevices( + subspace.devices, + queryRunner, ); } } @@ -446,7 +464,7 @@ export class SubSpaceService { private async findOne(subspaceUuid: string): Promise { const subspace = await this.subspaceRepository.findOne({ where: { uuid: subspaceUuid }, - relations: ['tags', 'space'], + relations: ['tags', 'space', 'devices', 'tags.product', 'tags.device'], }); if (!subspace) { throw new HttpException( @@ -457,7 +475,7 @@ export class SubSpaceService { return subspace; } - private async updateSubspaceName( + async updateSubspaceName( queryRunner: QueryRunner, subSpace: SubspaceEntity, subspaceName?: string, diff --git a/src/space/services/tag/tag.service.ts b/src/space/services/tag/tag.service.ts index b89dc9c..4c3bb8f 100644 --- a/src/space/services/tag/tag.service.ts +++ b/src/space/services/tag/tag.service.ts @@ -63,6 +63,7 @@ export class TagService { await queryRunner.manager.save(tags); } + async updateTag( tag: ModifyTagDto, queryRunner: QueryRunner, @@ -90,6 +91,38 @@ export class TagService { } } + async updateTagsFromModel( + model: TagModel, + queryRunner: QueryRunner, + ): Promise { + try { + const tags = await this.tagRepository.find({ + where: { + model: { + uuid: model.uuid, + }, + }, + }); + + if (!tags.length) return; + + await queryRunner.manager.update( + this.tagRepository.target, + { model: { uuid: model.uuid } }, + { tag: model.tag }, + ); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + `Failed to update tags for model with UUID: ${model.uuid}. Reason: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async deleteTags(tagUuids: string[], queryRunner: QueryRunner) { if (!tagUuids?.length) return; @@ -109,6 +142,34 @@ export class TagService { } } + async deleteTagFromModel(modelUuid: string, queryRunner: QueryRunner) { + try { + const tags = await this.tagRepository.find({ + where: { + model: { + uuid: modelUuid, + }, + }, + }); + + if (!tags.length) return; + + await queryRunner.manager.update( + this.tagRepository.target, + { model: { uuid: modelUuid } }, + { disabled: true, device: null }, + ); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + `Failed to update tags for model with UUID: ${modelUuid}. Reason: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } async modifyTags( tags: ModifyTagDto[], queryRunner: QueryRunner, From e56bd8810429081d40f440ba13eed6c16f184208 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 30 Dec 2024 22:22:28 +0400 Subject: [PATCH 137/247] updated error message for update community with existing name --- src/community/services/community.service.ts | 28 +++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/community/services/community.service.ts b/src/community/services/community.service.ts index eee72d1..7bfc9bc 100644 --- a/src/community/services/community.service.ts +++ b/src/community/services/community.service.ts @@ -31,15 +31,7 @@ export class CommunityService { const project = await this.validateProject(param.projectUuid); - const existingCommunity = await this.communityRepository.findOneBy({ - name, - }); - if (existingCommunity) { - throw new HttpException( - `A community with the name '${name}' already exists.`, - HttpStatus.BAD_REQUEST, - ); - } + await this.validateName(name); // Create the new community entity const community = this.communityRepository.create({ @@ -148,6 +140,7 @@ export class CommunityService { try { const { name } = updateCommunityDto; + if (name != community.name) await this.validateName(name); community.name = name; @@ -163,7 +156,10 @@ export class CommunityService { throw err; // If it's an HttpException, rethrow it } else { // Throw a generic 404 error if anything else goes wrong - throw new HttpException('Community not found', HttpStatus.NOT_FOUND); + throw new HttpException( + `An Internal exception has been occured ${err}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } } @@ -234,4 +230,16 @@ export class CommunityService { } return project; } + + private async validateName(name: string) { + const existingCommunity = await this.communityRepository.findOneBy({ + name, + }); + if (existingCommunity) { + throw new HttpException( + `A community with the name '${name}' already exists.`, + HttpStatus.BAD_REQUEST, + ); + } + } } From 6df6716820fd6772b44d65f25c93de9b77d57068 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 30 Dec 2024 22:27:56 +0400 Subject: [PATCH 138/247] fixed the relation in fetching space --- src/space/services/space.service.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 9badabb..fc01bd7 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -174,13 +174,17 @@ export class SpaceService { where: { community: { uuid: communityUuid }, spaceName: Not(`${ORPHAN_SPACE_NAME}`), + disabled: false, }, relations: [ 'parent', 'children', 'incomingConnections', - 'spaceProducts', - 'spaceProducts.product', + 'tags', + 'tags.product', + 'subspaces', + 'subspaces.tags', + 'subspaces.tags.product', ], }); @@ -194,7 +198,7 @@ export class SpaceService { }); } catch (error) { throw new HttpException( - 'An error occurred while fetching the spaces', + `An error occurred while fetching the spaces ${error}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -423,7 +427,7 @@ export class SpaceService { }); } catch (error) { throw new HttpException( - 'An error occurred while fetching the spaces under the space', + `An error occurred while fetching the spaces under the space ${error}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } From 7e6e34a3decb3cf9d04ecb99523f49c6efaeac7b Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 30 Dec 2024 18:59:42 -0600 Subject: [PATCH 139/247] Refactor and add new user-related endpoints for projects --- libs/common/src/constants/controller-route.ts | 13 ++ .../controllers/project-user.controller.ts | 48 ++++++ src/project/controllers/project.controller.ts | 14 -- src/project/project.module.ts | 5 +- src/project/services/project-user.service.ts | 159 ++++++++++++++++++ src/project/services/project.service.ts | 78 --------- 6 files changed, 224 insertions(+), 93 deletions(-) create mode 100644 src/project/controllers/project-user.controller.ts create mode 100644 src/project/services/project-user.service.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 572bb9c..1d23d9f 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -22,6 +22,19 @@ export class ControllerRoute { public static readonly DELETE_PROJECT_DESCRIPTION = 'This endpoint deletes an existing project by its unique identifier (UUID).'; + public static readonly GET_USERS_BY_PROJECT_SUMMARY = + 'Get users by project'; + public static readonly GET_USERS_BY_PROJECT_DESCRIPTION = + 'This endpoint retrieves all users associated with a specific project.'; + public static readonly GET_USER_BY_UUID_IN_PROJECT_SUMMARY = + 'Get user by uuid in project'; + public static readonly GET_USER_BY_UUID_IN_PROJECT_DESCRIPTION = + 'This endpoint retrieves a user by their unique identifier (UUID) associated with a specific project.'; + }; + }; + static PROJECT_USER = class { + public static readonly ROUTE = '/projects/:projectUuid/user'; + static ACTIONS = class { public static readonly GET_USERS_BY_PROJECT_SUMMARY = 'Get users by project'; public static readonly GET_USERS_BY_PROJECT_DESCRIPTION = diff --git a/src/project/controllers/project-user.controller.ts b/src/project/controllers/project-user.controller.ts new file mode 100644 index 0000000..d7f5e31 --- /dev/null +++ b/src/project/controllers/project-user.controller.ts @@ -0,0 +1,48 @@ +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { Controller, Get, Param, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { GetProjectParam } from '../dto'; +import { ProjectUserService } from '../services/project-user.service'; + +@ApiTags('Project Module') +@Controller({ + version: '1', + path: ControllerRoute.PROJECT_USER.ROUTE, +}) +export class ProjectUserController { + constructor(private readonly projectUserService: ProjectUserService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: + ControllerRoute.PROJECT.ACTIONS.GET_USER_BY_UUID_IN_PROJECT_SUMMARY, + description: + ControllerRoute.PROJECT.ACTIONS.GET_USER_BY_UUID_IN_PROJECT_DESCRIPTION, + }) + @Get() + async findUsersByProject( + @Param() params: GetProjectParam, + ): Promise { + return this.projectUserService.getUsersByProject(params.projectUuid); + } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @ApiOperation({ + summary: ControllerRoute.PROJECT.ACTIONS.GET_USERS_BY_PROJECT_SUMMARY, + description: + ControllerRoute.PROJECT.ACTIONS.GET_USERS_BY_PROJECT_DESCRIPTION, + }) + @Get(':userUuid') + async findUserByUuidInProject( + @Param() params: GetProjectParam, + @Param('userUuid') userUuid: string, + ): Promise { + return this.projectUserService.getUserByUuidInProject( + params.projectUuid, + userUuid, + ); + } +} diff --git a/src/project/controllers/project.controller.ts b/src/project/controllers/project.controller.ts index c888acb..a39ced1 100644 --- a/src/project/controllers/project.controller.ts +++ b/src/project/controllers/project.controller.ts @@ -86,18 +86,4 @@ export class ProjectController { async findOne(@Param() params: GetProjectParam): Promise { return this.projectService.getProject(params.projectUuid); } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @ApiOperation({ - summary: ControllerRoute.PROJECT.ACTIONS.GET_USERS_BY_PROJECT_SUMMARY, - description: - ControllerRoute.PROJECT.ACTIONS.GET_USERS_BY_PROJECT_DESCRIPTION, - }) - @Get(':projectUuid/users') - async findUsersByProject( - @Param() params: GetProjectParam, - ): Promise { - return this.projectService.getUsersByProject(params.projectUuid); - } } diff --git a/src/project/project.module.ts b/src/project/project.module.ts index 0afbd1c..92b12f2 100644 --- a/src/project/project.module.ts +++ b/src/project/project.module.ts @@ -8,18 +8,21 @@ import { SpaceRepository } from '@app/common/modules/space'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; import { UserRepository } from '@app/common/modules/user/repositories'; +import { ProjectUserController } from './controllers/project-user.controller'; +import { ProjectUserService } from './services/project-user.service'; const CommandHandlers = [CreateOrphanSpaceHandler]; @Global() @Module({ imports: [CqrsModule], - controllers: [ProjectController], + controllers: [ProjectController, ProjectUserController], providers: [ ...CommandHandlers, SpaceRepository, CommunityRepository, ProjectService, + ProjectUserService, ProjectRepository, InviteUserRepository, UserRepository, diff --git a/src/project/services/project-user.service.ts b/src/project/services/project-user.service.ts new file mode 100644 index 0000000..f418696 --- /dev/null +++ b/src/project/services/project-user.service.ts @@ -0,0 +1,159 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; + +import { UserStatusEnum } from '@app/common/constants/user-status.enum'; +import { RoleType } from '@app/common/constants/role.type.enum'; +import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; +import { UserRepository } from '@app/common/modules/user/repositories'; + +@Injectable() +export class ProjectUserService { + constructor( + private readonly inviteUserRepository: InviteUserRepository, + private readonly userRepository: UserRepository, + ) {} + + async getUsersByProject(uuid: string): Promise { + try { + // Fetch invited users + const invitedUsers = await this.inviteUserRepository.find({ + where: { project: { uuid }, isActive: true }, + select: [ + 'uuid', + 'firstName', + 'lastName', + 'email', + 'createdAt', + 'status', + 'phoneNumber', + 'jobTitle', + 'invitedBy', + 'isEnabled', + ], + relations: ['roleType'], + }); + + // Fetch project users + const users = await this.userRepository.find({ + where: { project: { uuid }, isActive: true }, + select: ['uuid', 'firstName', 'lastName', 'email', 'createdAt'], + relations: ['roleType'], + }); + + // Combine both arrays + const allUsers = [...users, ...invitedUsers]; + + const normalizedUsers = allUsers.map((user) => { + const createdAt = new Date(user.createdAt); + const createdDate = createdAt.toLocaleDateString(); + const createdTime = createdAt.toLocaleTimeString(); + + // Normalize user properties + const normalizedProps = this.normalizeUserProperties(user); + + // Return the normalized user object + return { + ...user, + createdDate, + createdTime, + ...normalizedProps, + }; + }); + + return new SuccessResponseDto({ + message: `Users in project with ID ${uuid} retrieved successfully`, + data: normalizedUsers, + statusCode: HttpStatus.OK, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + `An error occurred while retrieving users in the project with id ${uuid}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async getUserByUuidInProject( + projectUuid: string, + userUuid: string, + ): Promise { + try { + let user; + user = await this.inviteUserRepository.findOne({ + where: { + project: { uuid: projectUuid }, + uuid: userUuid, + isActive: true, + }, + select: [ + 'uuid', + 'firstName', + 'lastName', + 'email', + 'createdAt', + 'status', + 'phoneNumber', + 'jobTitle', + 'invitedBy', + 'isEnabled', + ], + relations: ['roleType'], + }); + if (!user) { + user = await this.userRepository.findOne({ + where: { + project: { uuid: projectUuid }, + uuid: userUuid, + isActive: true, + }, + select: ['uuid', 'firstName', 'lastName', 'email', 'createdAt'], + relations: ['roleType'], + }); + } + if (!user) { + throw new HttpException( + `User with ID ${userUuid} not found in project ${projectUuid}`, + HttpStatus.NOT_FOUND, + ); + } + const responseData = this.formatUserResponse(user); + return new SuccessResponseDto({ + message: `User in project with ID ${projectUuid} retrieved successfully`, + data: responseData, + statusCode: HttpStatus.OK, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + `An error occurred while retrieving user in the project with id ${projectUuid}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + private formatUserResponse(user: any) { + const createdAt = new Date(user.createdAt); + return { + ...user, + createdDate: createdAt.toLocaleDateString(), + createdTime: createdAt.toLocaleTimeString(), + ...this.normalizeUserProperties(user), + }; + } + private normalizeUserProperties(user: any) { + return { + status: user.status ?? UserStatusEnum.ACTIVE, + invitedBy: user.invitedBy ?? RoleType.SPACE_MEMBER, + isEnabled: user.isEnabled ?? true, + phoneNumber: user.phoneNumber ?? null, + jobTitle: user.jobTitle ?? null, + roleType: user.roleType?.type ?? null, + }; + } +} diff --git a/src/project/services/project.service.ts b/src/project/services/project.service.ts index 1d5e609..75aa67a 100644 --- a/src/project/services/project.service.ts +++ b/src/project/services/project.service.ts @@ -12,17 +12,11 @@ import { ProjectDto } from '@app/common/modules/project/dtos'; import { PageResponse } from '@app/common/dto/pagination.response.dto'; import { CommandBus } from '@nestjs/cqrs'; import { CreateOrphanSpaceCommand } from '../command/create-orphan-space-command'; -import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; -import { UserRepository } from '@app/common/modules/user/repositories'; -import { UserStatusEnum } from '@app/common/constants/user-status.enum'; -import { RoleType } from '@app/common/constants/role.type.enum'; @Injectable() export class ProjectService { constructor( private readonly projectRepository: ProjectRepository, - private readonly inviteUserRepository: InviteUserRepository, - private readonly userRepository: UserRepository, private commandBus: CommandBus, ) {} @@ -187,78 +181,6 @@ export class ProjectService { } } - async getUsersByProject(uuid: string): Promise { - try { - // Fetch invited users - const invitedUsers = await this.inviteUserRepository.find({ - where: { project: { uuid }, isActive: true }, - select: [ - 'firstName', - 'lastName', - 'email', - 'createdAt', - 'status', - 'phoneNumber', - 'jobTitle', - 'invitedBy', - 'isEnabled', - ], - relations: ['roleType'], - }); - - // Fetch project users - const users = await this.userRepository.find({ - where: { project: { uuid }, isActive: true }, - select: ['firstName', 'lastName', 'email', 'createdAt'], - relations: ['roleType'], - }); - - // Combine both arrays - const allUsers = [...users, ...invitedUsers]; - - const normalizedUsers = allUsers.map((user) => { - const createdAt = new Date(user.createdAt); - const createdDate = createdAt.toLocaleDateString(); - const createdTime = createdAt.toLocaleTimeString(); - - // Normalize user properties - const normalizedProps = this.normalizeUserProperties(user); - - // Return the normalized user object - return { - ...user, - createdDate, - createdTime, - ...normalizedProps, - }; - }); - - return new SuccessResponseDto({ - message: `Users in project with ID ${uuid} retrieved successfully`, - data: normalizedUsers, - statusCode: HttpStatus.OK, - }); - } catch (error) { - if (error instanceof HttpException) { - throw error; - } - - throw new HttpException( - `An error occurred while retrieving users in the project with id ${uuid}`, - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } - normalizeUserProperties(user: any) { - return { - status: user.status ?? UserStatusEnum.ACTIVE, - invitedBy: user.invitedBy ?? RoleType.SPACE_MEMBER, - isEnabled: user.isEnabled ?? true, - phoneNumber: user.phoneNumber ?? null, - jobTitle: user.jobTitle ?? null, - roleType: user.roleType?.type ?? null, - }; - } async findOne(uuid: string): Promise { const project = await this.projectRepository.findOne({ where: { uuid } }); return project; From 3cff60688768296009017c2f2210acd3d8a45419 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 30 Dec 2024 19:41:19 -0600 Subject: [PATCH 140/247] Add environment variables and update email service for different environments --- .env.example | 6 ++++++ libs/common/src/constants/mail-trap.ts | 4 +++- libs/common/src/util/email.service.ts | 18 +++++++++++++----- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 8fb1460..9525e51 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ +NODE_ENV= + ACCESS_KEY= AZURE_POSTGRESQL_DATABASE= @@ -52,6 +54,10 @@ SMTP_SECURE= SMTP_USER= +MAILTRAP_API_TOKEN= + +MAILTRAP_INVITATION_TEMPLATE_UUID= + WEBSITES_ENABLE_APP_SERVICE_STORAGE= PORT= diff --git a/libs/common/src/constants/mail-trap.ts b/libs/common/src/constants/mail-trap.ts index 3d989bc..6642244 100644 --- a/libs/common/src/constants/mail-trap.ts +++ b/libs/common/src/constants/mail-trap.ts @@ -1 +1,3 @@ -export const SEND_EMAIL_API_URL = 'https://send.api.mailtrap.io/api/send'; +export const SEND_EMAIL_API_URL_PROD = 'https://send.api.mailtrap.io/api/send/'; +export const SEND_EMAIL_API_URL_DEV = + 'https://sandbox.api.mailtrap.io/api/send/2634012'; diff --git a/libs/common/src/util/email.service.ts b/libs/common/src/util/email.service.ts index c1ad34d..fd84ea6 100644 --- a/libs/common/src/util/email.service.ts +++ b/libs/common/src/util/email.service.ts @@ -2,7 +2,10 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as nodemailer from 'nodemailer'; import axios from 'axios'; -import { SEND_EMAIL_API_URL } from '../constants/mail-trap'; +import { + SEND_EMAIL_API_URL_DEV, + SEND_EMAIL_API_URL_PROD, +} from '../constants/mail-trap'; @Injectable() export class EmailService { @@ -41,16 +44,20 @@ export class EmailService { email: string, emailInvitationData: any, ): Promise { + const isProduction = process.env.NODE_ENV === 'production'; const API_TOKEN = this.configService.get( 'email-config.MAILTRAP_API_TOKEN', ); + const API_URL = isProduction + ? SEND_EMAIL_API_URL_PROD + : SEND_EMAIL_API_URL_DEV; const TEMPLATE_UUID = this.configService.get( 'email-config.MAILTRAP_INVITATION_TEMPLATE_UUID', ); const emailData = { from: { - email: this.configService.get('email-config.SMTP_SENDER'), + email: this.smtpConfig.sender, }, to: [ { @@ -62,7 +69,7 @@ export class EmailService { }; try { - await axios.post(SEND_EMAIL_API_URL, emailData, { + await axios.post(API_URL, emailData, { headers: { Authorization: `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json', @@ -70,8 +77,9 @@ export class EmailService { }); } catch (error) { throw new HttpException( - error.message || 'Error sending email using Mailtrap template', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, + error.response?.data?.message || + 'Error sending email using Mailtrap template', + error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR, ); } } From 337539c29366e2b5ba362f1f84c331976e2a96bf Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 30 Dec 2024 19:53:31 -0600 Subject: [PATCH 141/247] Include spaces and userSpaces in user relations --- src/project/services/project-user.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/project/services/project-user.service.ts b/src/project/services/project-user.service.ts index f418696..aa857d0 100644 --- a/src/project/services/project-user.service.ts +++ b/src/project/services/project-user.service.ts @@ -101,7 +101,7 @@ export class ProjectUserService { 'invitedBy', 'isEnabled', ], - relations: ['roleType'], + relations: ['roleType', 'spaces'], }); if (!user) { user = await this.userRepository.findOne({ @@ -111,7 +111,7 @@ export class ProjectUserService { isActive: true, }, select: ['uuid', 'firstName', 'lastName', 'email', 'createdAt'], - relations: ['roleType'], + relations: ['roleType', 'userSpaces'], }); } if (!user) { @@ -154,6 +154,7 @@ export class ProjectUserService { phoneNumber: user.phoneNumber ?? null, jobTitle: user.jobTitle ?? null, roleType: user.roleType?.type ?? null, + spaces: user.spaces ?? user.userSpaces, }; } } From 0542d13279d3167263b1895aaea4bd269104d99d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 31 Dec 2024 12:23:02 +0400 Subject: [PATCH 142/247] fix bug in community --- src/space/services/space.service.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index fc01bd7..8edd706 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -62,10 +62,11 @@ export class SpaceService { await queryRunner.connect(); await queryRunner.startTransaction(); - const community = await this.validationService.validateCommunityAndProject( - communityUuid, - projectUuid, - ); + const { community } = + await this.validationService.validateCommunityAndProject( + communityUuid, + projectUuid, + ); this.validateSpaceCreation(addSpaceDto, spaceModelUuid); @@ -78,6 +79,7 @@ export class SpaceService { : null; try { + console.log('jnkjn', community); const space = queryRunner.manager.create(SpaceEntity, { ...addSpaceDto, spaceModel, From b7eaad18e1786e2ab3ea3191b96f28bbe6636c20 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 31 Dec 2024 12:26:34 +0400 Subject: [PATCH 143/247] cleanup --- src/space/services/space.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 8edd706..5590105 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -79,7 +79,6 @@ export class SpaceService { : null; try { - console.log('jnkjn', community); const space = queryRunner.manager.create(SpaceEntity, { ...addSpaceDto, spaceModel, From 172d17950f460e6cdc849570e59d424d662541cf Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 31 Dec 2024 12:44:26 +0400 Subject: [PATCH 144/247] fixed issues in fetching orphan community --- src/space/dtos/add.space.dto.ts | 1 - src/space/services/space.service.ts | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/space/dtos/add.space.dto.ts b/src/space/dtos/add.space.dto.ts index a3c9a3b..15595be 100644 --- a/src/space/dtos/add.space.dto.ts +++ b/src/space/dtos/add.space.dto.ts @@ -1,7 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { - IsArray, IsBoolean, IsNotEmpty, IsNumber, diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 8edd706..2d581e9 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -79,7 +79,6 @@ export class SpaceService { : null; try { - console.log('jnkjn', community); const space = queryRunner.manager.create(SpaceEntity, { ...addSpaceDto, spaceModel, @@ -254,7 +253,7 @@ export class SpaceService { const orphanSpace = await this.spaceRepository.findOne({ where: { community: { - uuid: `${ORPHAN_COMMUNITY_NAME}-${project.name}`, + name: `${ORPHAN_COMMUNITY_NAME}-${project.name}`, }, spaceName: ORPHAN_SPACE_NAME, }, @@ -271,7 +270,7 @@ export class SpaceService { throw error; } throw new HttpException( - 'An error occurred while deleting the space', + `An error occurred while deleting the space ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } From 17124b9d34842e20192a493f1aaf152fd03695a1 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 2 Jan 2025 11:22:54 +0400 Subject: [PATCH 145/247] list space model should list tags --- libs/common/src/util/buildTypeORMIncludeQuery.ts | 12 +++++------- src/space-model/services/space-model.service.ts | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/libs/common/src/util/buildTypeORMIncludeQuery.ts b/libs/common/src/util/buildTypeORMIncludeQuery.ts index bffd5be..a8d4e20 100644 --- a/libs/common/src/util/buildTypeORMIncludeQuery.ts +++ b/libs/common/src/util/buildTypeORMIncludeQuery.ts @@ -21,16 +21,14 @@ const mappingInclude: { [key: string]: any } = { }, 'space-model': { subspaceModels: 'subspace-model', - spaceProductModels: 'space-product-model', + tags: 'tag_model', }, 'subspace-model': { - productModels: 'subspace-product-model', + tags: 'tag_model', }, - 'subspace-product-model': { - itemModels: true, - }, - 'space-product-model': { - items: true, + tag_model: { + tag_model: true, + product: true, }, }; diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 89e9edb..747ea39 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -113,7 +113,7 @@ export class SpaceModelService { pageable.where = { project: { uuid: param.projectUuid }, }; - pageable.include = 'subspaceModels,tags,subspaceModels.tags'; + pageable.include = 'subspaceModels,tags,subspaceModels.tags,tags.product,subspaceModels.tags.product'; const customModel = TypeORMCustomModel(this.spaceModelRepository); From ca3d63f7d0d4af603ee2bd505a607c1ae0079e87 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 2 Jan 2025 11:25:24 +0400 Subject: [PATCH 146/247] prettier --- src/space-model/services/space-model.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 747ea39..bfed573 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -113,7 +113,8 @@ export class SpaceModelService { pageable.where = { project: { uuid: param.projectUuid }, }; - pageable.include = 'subspaceModels,tags,subspaceModels.tags,tags.product,subspaceModels.tags.product'; + pageable.include = + 'subspaceModels,tags,subspaceModels.tags,tags.product,subspaceModels.tags.product'; const customModel = TypeORMCustomModel(this.spaceModelRepository); From 6d87966b22a346b77aa0714a578ef2427135466f Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 2 Jan 2025 03:46:33 -0600 Subject: [PATCH 147/247] Add project existence check in getUsersByProject method --- src/project/services/project-user.service.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/project/services/project-user.service.ts b/src/project/services/project-user.service.ts index aa857d0..fb487c3 100644 --- a/src/project/services/project-user.service.ts +++ b/src/project/services/project-user.service.ts @@ -6,16 +6,25 @@ import { UserStatusEnum } from '@app/common/constants/user-status.enum'; import { RoleType } from '@app/common/constants/role.type.enum'; import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; import { UserRepository } from '@app/common/modules/user/repositories'; +import { ProjectService } from './project.service'; @Injectable() export class ProjectUserService { constructor( private readonly inviteUserRepository: InviteUserRepository, private readonly userRepository: UserRepository, + private readonly projectService: ProjectService, ) {} async getUsersByProject(uuid: string): Promise { try { + const project = await this.projectService.getProject(uuid); + if (!project) { + throw new HttpException( + `Project with ID ${uuid} not found`, + HttpStatus.NOT_FOUND, + ); + } // Fetch invited users const invitedUsers = await this.inviteUserRepository.find({ where: { project: { uuid }, isActive: true }, From ff30d36f8e4fe748f802a3440c16fd05b8494e9b Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 2 Jan 2025 20:41:58 -0600 Subject: [PATCH 148/247] Update user relations and formatUserResponse method --- src/project/services/project-user.service.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/project/services/project-user.service.ts b/src/project/services/project-user.service.ts index fb487c3..f48ddfb 100644 --- a/src/project/services/project-user.service.ts +++ b/src/project/services/project-user.service.ts @@ -110,7 +110,7 @@ export class ProjectUserService { 'invitedBy', 'isEnabled', ], - relations: ['roleType', 'spaces'], + relations: ['roleType', 'spaces.space'], }); if (!user) { user = await this.userRepository.findOne({ @@ -120,7 +120,7 @@ export class ProjectUserService { isActive: true, }, select: ['uuid', 'firstName', 'lastName', 'email', 'createdAt'], - relations: ['roleType', 'userSpaces'], + relations: ['roleType', 'userSpaces.space'], }); } if (!user) { @@ -149,7 +149,10 @@ export class ProjectUserService { private formatUserResponse(user: any) { const createdAt = new Date(user.createdAt); return { - ...user, + uuid: user.uuid, + firstName: user.firstName, + lastName: user.lastName, + email: user.email, createdDate: createdAt.toLocaleDateString(), createdTime: createdAt.toLocaleTimeString(), ...this.normalizeUserProperties(user), @@ -163,7 +166,9 @@ export class ProjectUserService { phoneNumber: user.phoneNumber ?? null, jobTitle: user.jobTitle ?? null, roleType: user.roleType?.type ?? null, - spaces: user.spaces ?? user.userSpaces, + spaces: + user?.spaces?.map((space: any) => space?.space) ?? + user?.userSpaces?.map((space: any) => space?.space), }; } } From 93d05162fa8b8e883321505c590032bd28a6ba0a Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 04:19:19 -0600 Subject: [PATCH 149/247] Remove `isEnabled` column from `InviteUserEntity` --- .../src/modules/Invite-user/entities/Invite-user.entity.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts index 3eac7d6..491d16f 100644 --- a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts +++ b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts @@ -56,12 +56,6 @@ export class InviteUserEntity extends AbstractEntity { }) public phoneNumber: string; - @Column({ - nullable: false, - default: true, - }) - public isEnabled: boolean; - @Column({ nullable: false, default: true, From 9c03b9e95dbd533e9ad08c3358e85170f129c11c Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 04:19:34 -0600 Subject: [PATCH 150/247] Remove unused UserRepository from project.module.ts --- src/project/project.module.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/project/project.module.ts b/src/project/project.module.ts index 92b12f2..0258881 100644 --- a/src/project/project.module.ts +++ b/src/project/project.module.ts @@ -7,7 +7,6 @@ import { CreateOrphanSpaceHandler } from './handler'; import { SpaceRepository } from '@app/common/modules/space'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; -import { UserRepository } from '@app/common/modules/user/repositories'; import { ProjectUserController } from './controllers/project-user.controller'; import { ProjectUserService } from './services/project-user.service'; @@ -25,7 +24,6 @@ const CommandHandlers = [CreateOrphanSpaceHandler]; ProjectUserService, ProjectRepository, InviteUserRepository, - UserRepository, ], exports: [ProjectService, CqrsModule], }) From eac5fce859c966b363e4345f3123870f53b749ca Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 04:19:48 -0600 Subject: [PATCH 151/247] Simplify user retrieval and normalization in ProjectUserService --- src/project/services/project-user.service.ts | 78 ++++---------------- 1 file changed, 14 insertions(+), 64 deletions(-) diff --git a/src/project/services/project-user.service.ts b/src/project/services/project-user.service.ts index f48ddfb..847774f 100644 --- a/src/project/services/project-user.service.ts +++ b/src/project/services/project-user.service.ts @@ -1,18 +1,13 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; - -import { UserStatusEnum } from '@app/common/constants/user-status.enum'; -import { RoleType } from '@app/common/constants/role.type.enum'; import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; -import { UserRepository } from '@app/common/modules/user/repositories'; import { ProjectService } from './project.service'; @Injectable() export class ProjectUserService { constructor( private readonly inviteUserRepository: InviteUserRepository, - private readonly userRepository: UserRepository, private readonly projectService: ProjectService, ) {} @@ -25,8 +20,7 @@ export class ProjectUserService { HttpStatus.NOT_FOUND, ); } - // Fetch invited users - const invitedUsers = await this.inviteUserRepository.find({ + const allUsers = await this.inviteUserRepository.find({ where: { project: { uuid }, isActive: true }, select: [ 'uuid', @@ -38,35 +32,20 @@ export class ProjectUserService { 'phoneNumber', 'jobTitle', 'invitedBy', - 'isEnabled', ], relations: ['roleType'], }); - // Fetch project users - const users = await this.userRepository.find({ - where: { project: { uuid }, isActive: true }, - select: ['uuid', 'firstName', 'lastName', 'email', 'createdAt'], - relations: ['roleType'], - }); - - // Combine both arrays - const allUsers = [...users, ...invitedUsers]; - const normalizedUsers = allUsers.map((user) => { const createdAt = new Date(user.createdAt); const createdDate = createdAt.toLocaleDateString(); const createdTime = createdAt.toLocaleTimeString(); - // Normalize user properties - const normalizedProps = this.normalizeUserProperties(user); - - // Return the normalized user object return { ...user, + roleType: user.roleType.type, createdDate, createdTime, - ...normalizedProps, }; }); @@ -91,8 +70,7 @@ export class ProjectUserService { userUuid: string, ): Promise { try { - let user; - user = await this.inviteUserRepository.findOne({ + const user = await this.inviteUserRepository.findOne({ where: { project: { uuid: projectUuid }, uuid: userUuid, @@ -108,31 +86,28 @@ export class ProjectUserService { 'phoneNumber', 'jobTitle', 'invitedBy', - 'isEnabled', ], relations: ['roleType', 'spaces.space'], }); - if (!user) { - user = await this.userRepository.findOne({ - where: { - project: { uuid: projectUuid }, - uuid: userUuid, - isActive: true, - }, - select: ['uuid', 'firstName', 'lastName', 'email', 'createdAt'], - relations: ['roleType', 'userSpaces.space'], - }); - } + if (!user) { throw new HttpException( `User with ID ${userUuid} not found in project ${projectUuid}`, HttpStatus.NOT_FOUND, ); } - const responseData = this.formatUserResponse(user); + const createdAt = new Date(user.createdAt); + const createdDate = createdAt.toLocaleDateString(); + const createdTime = createdAt.toLocaleTimeString(); return new SuccessResponseDto({ message: `User in project with ID ${projectUuid} retrieved successfully`, - data: responseData, + data: { + ...user, + roleType: user.roleType.type, + createdDate, + createdTime, + spaces: user.spaces.map((space) => space.space), + }, statusCode: HttpStatus.OK, }); } catch (error) { @@ -146,29 +121,4 @@ export class ProjectUserService { ); } } - private formatUserResponse(user: any) { - const createdAt = new Date(user.createdAt); - return { - uuid: user.uuid, - firstName: user.firstName, - lastName: user.lastName, - email: user.email, - createdDate: createdAt.toLocaleDateString(), - createdTime: createdAt.toLocaleTimeString(), - ...this.normalizeUserProperties(user), - }; - } - private normalizeUserProperties(user: any) { - return { - status: user.status ?? UserStatusEnum.ACTIVE, - invitedBy: user.invitedBy ?? RoleType.SPACE_MEMBER, - isEnabled: user.isEnabled ?? true, - phoneNumber: user.phoneNumber ?? null, - jobTitle: user.jobTitle ?? null, - roleType: user.roleType?.type ?? null, - spaces: - user?.spaces?.map((space: any) => space?.space) ?? - user?.userSpaces?.map((space: any) => space?.space), - }; - } } From 77061d3ce3aa89bcf1120b2a3dd37aa4cbd354a3 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 04:26:37 -0600 Subject: [PATCH 152/247] Rename userUuid to invitedUserUuid in project-user controller and service --- src/project/controllers/project-user.controller.ts | 6 +++--- src/project/services/project-user.service.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/project/controllers/project-user.controller.ts b/src/project/controllers/project-user.controller.ts index d7f5e31..7cda75e 100644 --- a/src/project/controllers/project-user.controller.ts +++ b/src/project/controllers/project-user.controller.ts @@ -35,14 +35,14 @@ export class ProjectUserController { description: ControllerRoute.PROJECT.ACTIONS.GET_USERS_BY_PROJECT_DESCRIPTION, }) - @Get(':userUuid') + @Get(':invitedUserUuid') async findUserByUuidInProject( @Param() params: GetProjectParam, - @Param('userUuid') userUuid: string, + @Param('invitedUserUuid') invitedUserUuid: string, ): Promise { return this.projectUserService.getUserByUuidInProject( params.projectUuid, - userUuid, + invitedUserUuid, ); } } diff --git a/src/project/services/project-user.service.ts b/src/project/services/project-user.service.ts index 847774f..53d64f1 100644 --- a/src/project/services/project-user.service.ts +++ b/src/project/services/project-user.service.ts @@ -67,13 +67,13 @@ export class ProjectUserService { } async getUserByUuidInProject( projectUuid: string, - userUuid: string, + invitedUserUuid: string, ): Promise { try { const user = await this.inviteUserRepository.findOne({ where: { project: { uuid: projectUuid }, - uuid: userUuid, + uuid: invitedUserUuid, isActive: true, }, select: [ @@ -92,7 +92,7 @@ export class ProjectUserService { if (!user) { throw new HttpException( - `User with ID ${userUuid} not found in project ${projectUuid}`, + `User with ID ${invitedUserUuid} not found in project ${projectUuid}`, HttpStatus.NOT_FOUND, ); } From 33c9518ca541375b95c9c3cbe73792bf452160bf Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 04:34:11 -0600 Subject: [PATCH 153/247] Include `devices.productDevice` in space validation --- src/space/services/space-validation.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts index 89a539a..d9a9f04 100644 --- a/src/space/services/space-validation.service.ts +++ b/src/space/services/space-validation.service.ts @@ -51,6 +51,7 @@ export class ValidationService { 'subspaces.tags', 'subspaces.devices', 'devices', + 'devices.productDevice', ], }); From c616547f9b41e8b05aed9937bfed6019656c77c1 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 04:44:34 -0600 Subject: [PATCH 154/247] Add activation code endpoint --- libs/common/src/constants/controller-route.ts | 6 ++ .../controllers/invite-user.controller.ts | 16 ++++ src/invite-user/dtos/active-code.dto.ts | 22 +++++ src/invite-user/invite-user.module.ts | 29 +++++- .../services/invite-user.service.ts | 88 +++++++++++++++++++ src/users/services/user-space.service.ts | 6 +- 6 files changed, 162 insertions(+), 5 deletions(-) create mode 100644 src/invite-user/dtos/active-code.dto.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 1d23d9f..17222b1 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -760,6 +760,12 @@ export class ControllerRoute { public static readonly CREATE_USER_INVITATION_DESCRIPTION = 'This endpoint creates an invitation for a user to assign to role and spaces.'; + public static readonly ACTIVATION_CODE_SUMMARY = + 'Activate Invitation Code'; + + public static readonly ACTIVATION_CODE_DESCRIPTION = + 'This endpoint activate invitation code'; + public static readonly CHECK_EMAIL_SUMMARY = 'Check email'; public static readonly CHECK_EMAIL_DESCRIPTION = diff --git a/src/invite-user/controllers/invite-user.controller.ts b/src/invite-user/controllers/invite-user.controller.ts index 4351345..4bf493f 100644 --- a/src/invite-user/controllers/invite-user.controller.ts +++ b/src/invite-user/controllers/invite-user.controller.ts @@ -8,6 +8,7 @@ import { PermissionsGuard } from 'src/guards/permissions.guard'; import { Permissions } from 'src/decorators/permissions.decorator'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { CheckEmailDto } from '../dtos/check-email.dto'; +import { ActivateCodeDto } from '../dtos/active-code.dto'; @ApiTags('Invite User Module') @Controller({ @@ -51,4 +52,19 @@ export class InviteUserController { addUserInvitationDto, ); } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Post('activation') + @ApiOperation({ + summary: ControllerRoute.INVITE_USER.ACTIONS.ACTIVATION_CODE_SUMMARY, + description: + ControllerRoute.INVITE_USER.ACTIONS.ACTIVATION_CODE_DESCRIPTION, + }) + async activationCodeController( + @Body() activateCodeDto: ActivateCodeDto, + ): Promise { + return await this.inviteUserService.activationCodeController( + activateCodeDto, + ); + } } diff --git a/src/invite-user/dtos/active-code.dto.ts b/src/invite-user/dtos/active-code.dto.ts new file mode 100644 index 0000000..ea852aa --- /dev/null +++ b/src/invite-user/dtos/active-code.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, IsNotEmpty } from 'class-validator'; + +export class ActivateCodeDto { + @ApiProperty({ + description: 'The activation code of the user', + example: '7CvRcA', + required: true, + }) + @IsString() + @IsNotEmpty() + activationCode: string; + + @ApiProperty({ + description: 'The UUID of the user', + example: 'd7a44e8a-32d5-4f39-ae2e-013f1245aead', + required: true, + }) + @IsString() + @IsNotEmpty() + userUuid: string; +} diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index 402ebb1..3a8e20e 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -3,13 +3,27 @@ import { InviteUserService } from './services/invite-user.service'; import { InviteUserController } from './controllers/invite-user.controller'; import { ConfigModule } from '@nestjs/config'; -import { UserRepository } from '@app/common/modules/user/repositories'; +import { + UserRepository, + UserSpaceRepository, +} from '@app/common/modules/user/repositories'; import { InviteUserRepositoryModule } from '@app/common/modules/Invite-user/Invite-user.repository.module'; import { InviteUserRepository, InviteUserSpaceRepository, } from '@app/common/modules/Invite-user/repositiories'; import { EmailService } from '@app/common/util/email.service'; +import { SpaceUserService, ValidationService } from 'src/space/services'; +import { CommunityService } from 'src/community/services'; +import { SpaceRepository } from '@app/common/modules/space'; +import { SpaceModelRepository } from '@app/common/modules/space-model'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { ProjectRepository } from '@app/common/modules/project/repositiories'; +import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; +import { UserSpaceService } from 'src/users/services'; +import { UserDevicePermissionService } from 'src/user-device-permission/services'; +import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; +import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; @Module({ imports: [ConfigModule, InviteUserRepositoryModule], @@ -20,6 +34,19 @@ import { EmailService } from '@app/common/util/email.service'; UserRepository, EmailService, InviteUserSpaceRepository, + SpaceUserService, + ValidationService, + UserSpaceRepository, + CommunityService, + SpaceRepository, + SpaceModelRepository, + CommunityRepository, + ProjectRepository, + TuyaService, + UserSpaceService, + UserDevicePermissionService, + DeviceUserPermissionRepository, + PermissionTypeRepository, ], exports: [InviteUserService], }) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index aaeabd7..2376b3d 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -16,6 +16,9 @@ import { CheckEmailDto } from '../dtos/check-email.dto'; import { UserRepository } from '@app/common/modules/user/repositories'; import { EmailService } from '@app/common/util/email.service'; import { SpaceEntity } from '@app/common/modules/space'; +import { ActivateCodeDto } from '../dtos/active-code.dto'; +import { UserSpaceService } from 'src/users/services'; +import { SpaceUserService } from 'src/space/services'; @Injectable() export class InviteUserService { @@ -24,6 +27,9 @@ export class InviteUserService { private readonly userRepository: UserRepository, private readonly emailService: EmailService, private readonly inviteUserSpaceRepository: InviteUserSpaceRepository, + private readonly userSpaceService: UserSpaceService, + private readonly spaceUserService: SpaceUserService, + private readonly dataSource: DataSource, ) {} @@ -171,4 +177,86 @@ export class InviteUserService { ); } } + async activationCodeController( + dto: ActivateCodeDto, + ): Promise { + try { + const { activationCode, userUuid } = dto; + const user = await this.userRepository.findOne({ + where: { uuid: userUuid, isActive: true, isUserVerified: true }, + }); + + if (!user) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND); + } + const { email } = user; + const invitedUser = await this.inviteUserRepository.findOne({ + where: { + email, + invitationCode: activationCode, + status: UserStatusEnum.INVITED, + isActive: true, + }, + relations: ['project', 'spaces.space.community'], + }); + + if (!invitedUser) { + throw new HttpException( + 'Invalid activation code', + HttpStatus.BAD_REQUEST, + ); + } + + for (const invitedSpace of invitedUser.spaces) { + try { + const deviceUUIDs = + await this.userSpaceService.getDeviceUUIDsForSpace( + invitedSpace.space.uuid, + ); + + await this.userSpaceService.addUserPermissionsToDevices( + userUuid, + deviceUUIDs, + ); + + await this.spaceUserService.associateUserToSpace({ + communityUuid: invitedSpace.space.community.uuid, + spaceUuid: invitedSpace.space.uuid, + userUuid: user.uuid, + projectUuid: invitedUser.project.uuid, + }); + } catch (spaceError) { + console.error( + `Error processing space ${invitedSpace.space.uuid}:`, + spaceError, + ); + // Skip to the next space + continue; + } + } + await this.inviteUserRepository.update( + { uuid: invitedUser.uuid }, + { status: UserStatusEnum.ACTIVE }, + ); + await this.userRepository.update( + { uuid: userUuid }, + { + project: { uuid: invitedUser.project.uuid }, + inviteUser: { uuid: invitedUser.uuid }, + }, + ); + return new SuccessResponseDto({ + statusCode: HttpStatus.OK, + success: true, + message: 'The code has been successfully activated', + }); + } catch (error) { + console.error('Error activating the code:', error); + throw new HttpException( + error.message || + 'An unexpected error occurred while activating the code', + error.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index 0ee9af5..0ee6b03 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -117,9 +117,7 @@ export class UserSpaceService { ); } - private async getDeviceUUIDsForSpace( - unitUuid: string, - ): Promise<{ uuid: string }[]> { + async getDeviceUUIDsForSpace(unitUuid: string): Promise<{ uuid: string }[]> { const devices = await this.spaceRepository.find({ where: { uuid: unitUuid }, relations: ['devices', 'devices.productDevice'], @@ -130,7 +128,7 @@ export class UserSpaceService { return allDevices.map((device) => ({ uuid: device.uuid })); } - private async addUserPermissionsToDevices( + async addUserPermissionsToDevices( userUuid: string, deviceUUIDs: { uuid: string }[], ): Promise { From 687d88ea2ead33b31d4010c3d5867fec4ef7f649 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 06:21:39 -0600 Subject: [PATCH 155/247] Update user spaces retrieval based on user status --- src/project/services/project-user.service.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/project/services/project-user.service.ts b/src/project/services/project-user.service.ts index 53d64f1..d69332f 100644 --- a/src/project/services/project-user.service.ts +++ b/src/project/services/project-user.service.ts @@ -3,12 +3,15 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; import { ProjectService } from './project.service'; +import { UserStatusEnum } from '@app/common/constants/user-status.enum'; +import { UserSpaceRepository } from '@app/common/modules/user/repositories'; @Injectable() export class ProjectUserService { constructor( private readonly inviteUserRepository: InviteUserRepository, private readonly projectService: ProjectService, + private readonly userSpaceRepository: UserSpaceRepository, ) {} async getUsersByProject(uuid: string): Promise { @@ -70,6 +73,7 @@ export class ProjectUserService { invitedUserUuid: string, ): Promise { try { + let userSpaces; const user = await this.inviteUserRepository.findOne({ where: { project: { uuid: projectUuid }, @@ -96,6 +100,15 @@ export class ProjectUserService { HttpStatus.NOT_FOUND, ); } + if (user.status === UserStatusEnum.ACTIVE) { + const spaces = await this.userSpaceRepository.find({ + where: { user: { inviteUser: { uuid: invitedUserUuid } } }, + relations: ['space'], + }); + userSpaces = spaces.map((space) => space.space); + } else { + userSpaces = user.spaces.map((space) => space.space); + } const createdAt = new Date(user.createdAt); const createdDate = createdAt.toLocaleDateString(); const createdTime = createdAt.toLocaleTimeString(); @@ -106,7 +119,7 @@ export class ProjectUserService { roleType: user.roleType.type, createdDate, createdTime, - spaces: user.spaces.map((space) => space.space), + spaces: userSpaces, }, statusCode: HttpStatus.OK, }); From 39d73bf241ad18cc2c60d0fd0e45dce549a12bbc Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 06:25:50 -0600 Subject: [PATCH 156/247] finished Update invited user endpoint --- libs/common/src/constants/controller-route.ts | 6 + .../controllers/invite-user.controller.ts | 28 ++- .../dtos/update.invite-user.dto.ts | 74 ++++++ src/invite-user/invite-user.module.ts | 2 + .../services/invite-user.service.ts | 224 +++++++++++++++++- src/project/project.module.ts | 2 + 6 files changed, 331 insertions(+), 5 deletions(-) create mode 100644 src/invite-user/dtos/update.invite-user.dto.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 17222b1..934414d 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -760,6 +760,12 @@ export class ControllerRoute { public static readonly CREATE_USER_INVITATION_DESCRIPTION = 'This endpoint creates an invitation for a user to assign to role and spaces.'; + public static readonly UPDATE_USER_INVITATION_SUMMARY = + 'Update user invitation'; + + public static readonly UPDATE_USER_INVITATION_DESCRIPTION = + 'This endpoint updates an invitation for a user to assign to role and spaces.'; + public static readonly ACTIVATION_CODE_SUMMARY = 'Activate Invitation Code'; diff --git a/src/invite-user/controllers/invite-user.controller.ts b/src/invite-user/controllers/invite-user.controller.ts index 4bf493f..9da2224 100644 --- a/src/invite-user/controllers/invite-user.controller.ts +++ b/src/invite-user/controllers/invite-user.controller.ts @@ -1,5 +1,13 @@ import { InviteUserService } from '../services/invite-user.service'; -import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common'; +import { + Body, + Controller, + Param, + Post, + Put, + Req, + UseGuards, +} from '@nestjs/common'; import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { AddUserInvitationDto } from '../dtos/add.invite-user.dto'; import { ControllerRoute } from '@app/common/constants/controller-route'; @@ -9,6 +17,7 @@ import { Permissions } from 'src/decorators/permissions.decorator'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { CheckEmailDto } from '../dtos/check-email.dto'; import { ActivateCodeDto } from '../dtos/active-code.dto'; +import { UpdateUserInvitationDto } from '../dtos/update.invite-user.dto'; @ApiTags('Invite User Module') @Controller({ @@ -67,4 +76,21 @@ export class InviteUserController { activateCodeDto, ); } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Put(':invitedUserUuid') + @ApiOperation({ + summary: ControllerRoute.INVITE_USER.ACTIONS.UPDATE_USER_INVITATION_SUMMARY, + description: + ControllerRoute.INVITE_USER.ACTIONS.UPDATE_USER_INVITATION_DESCRIPTION, + }) + async updateUserInvitation( + @Param('invitedUserUuid') invitedUserUuid: string, + @Body() updateUserInvitationDto: UpdateUserInvitationDto, + ): Promise { + return await this.inviteUserService.updateUserInvitation( + updateUserInvitationDto, + invitedUserUuid, + ); + } } diff --git a/src/invite-user/dtos/update.invite-user.dto.ts b/src/invite-user/dtos/update.invite-user.dto.ts new file mode 100644 index 0000000..9e80306 --- /dev/null +++ b/src/invite-user/dtos/update.invite-user.dto.ts @@ -0,0 +1,74 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + ArrayMinSize, + IsArray, + IsNotEmpty, + IsOptional, + IsString, +} from 'class-validator'; + +export class UpdateUserInvitationDto { + @ApiProperty({ + description: 'The first name of the user', + example: 'John', + required: true, + }) + @IsString() + @IsNotEmpty() + public firstName: string; + + @ApiProperty({ + description: 'The last name of the user', + example: 'Doe', + required: true, + }) + @IsString() + @IsNotEmpty() + public lastName: string; + + @ApiProperty({ + description: 'The job title of the user', + example: 'Software Engineer', + required: false, + }) + @IsString() + @IsOptional() + public jobTitle?: string; + + @ApiProperty({ + description: 'The phone number of the user', + example: '+1234567890', + required: false, + }) + @IsString() + @IsOptional() + public phoneNumber?: string; + + @ApiProperty({ + description: 'The role uuid of the user', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + required: true, + }) + @IsString() + @IsNotEmpty() + public roleUuid: string; + @ApiProperty({ + description: 'The project uuid of the user', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + required: true, + }) + @IsString() + @IsNotEmpty() + public projectUuid: string; + @ApiProperty({ + description: 'The array of space UUIDs (at least one required)', + example: ['b5f3c9d2-58b7-4377-b3f7-60acb711d5d9'], + required: true, + }) + @IsArray() + @ArrayMinSize(1) + public spaceUuids: string[]; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index 3a8e20e..443b928 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -24,6 +24,7 @@ import { UserSpaceService } from 'src/users/services'; import { UserDevicePermissionService } from 'src/user-device-permission/services'; import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; +import { ProjectUserService } from 'src/project/services/project-user.service'; @Module({ imports: [ConfigModule, InviteUserRepositoryModule], @@ -47,6 +48,7 @@ import { PermissionTypeRepository } from '@app/common/modules/permission/reposit UserDevicePermissionService, DeviceUserPermissionRepository, PermissionTypeRepository, + ProjectUserService, ], exports: [InviteUserService], }) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 2376b3d..fd41f96 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -1,10 +1,15 @@ -import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; +import { + Injectable, + HttpException, + HttpStatus, + BadRequestException, +} from '@nestjs/common'; import { AddUserInvitationDto } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { UserStatusEnum } from '@app/common/constants/user-status.enum'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { generateRandomString } from '@app/common/helper/randomString'; -import { In, IsNull, Not } from 'typeorm'; +import { In, IsNull, Not, QueryRunner } from 'typeorm'; import { DataSource } from 'typeorm'; import { UserEntity } from '@app/common/modules/user/entities'; import { RoleType } from '@app/common/constants/role.type.enum'; @@ -15,10 +20,11 @@ import { import { CheckEmailDto } from '../dtos/check-email.dto'; import { UserRepository } from '@app/common/modules/user/repositories'; import { EmailService } from '@app/common/util/email.service'; -import { SpaceEntity } from '@app/common/modules/space'; +import { SpaceEntity, SpaceRepository } from '@app/common/modules/space'; import { ActivateCodeDto } from '../dtos/active-code.dto'; import { UserSpaceService } from 'src/users/services'; import { SpaceUserService } from 'src/space/services'; +import { UpdateUserInvitationDto } from '../dtos/update.invite-user.dto'; @Injectable() export class InviteUserService { @@ -29,7 +35,7 @@ export class InviteUserService { private readonly inviteUserSpaceRepository: InviteUserSpaceRepository, private readonly userSpaceService: UserSpaceService, private readonly spaceUserService: SpaceUserService, - + private readonly spaceRepository: SpaceRepository, private readonly dataSource: DataSource, ) {} @@ -259,4 +265,214 @@ export class InviteUserService { ); } } + async updateUserInvitation( + dto: UpdateUserInvitationDto, + invitedUserUuid: string, + ): Promise { + const { projectUuid } = dto; + + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.startTransaction(); + + try { + // Fetch the user's existing data in the project + const userOldData = await this.inviteUserRepository.findOne({ + where: { uuid: invitedUserUuid, project: { uuid: projectUuid } }, + }); + + if (!userOldData) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND); + } + + // Perform update actions if status is 'INVITED' + if ( + userOldData.status === UserStatusEnum.INVITED || + userOldData.status === UserStatusEnum.DISABLED + ) { + await this.updateWhenUserIsInviteOrDisable( + queryRunner, + dto, + invitedUserUuid, + ); + } else if (userOldData.status === UserStatusEnum.ACTIVE) { + await this.updateWhenUserIsActive(queryRunner, dto, invitedUserUuid); + } + + await queryRunner.commitTransaction(); + + return new SuccessResponseDto({ + statusCode: HttpStatus.OK, + success: true, + message: 'User invitation updated successfully', + }); + } catch (error) { + await queryRunner.rollbackTransaction(); + + // Throw an appropriate HTTP exception + throw error instanceof HttpException + ? error + : new HttpException( + 'An unexpected error occurred while updating the user', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } finally { + await queryRunner.release(); + } + } + + private async updateWhenUserIsInviteOrDisable( + queryRunner: QueryRunner, + dto: UpdateUserInvitationDto, + invitedUserUuid: string, + ): Promise { + const { firstName, lastName, jobTitle, phoneNumber, roleUuid, spaceUuids } = + dto; + + // Update user invitation details + await queryRunner.manager.update( + this.inviteUserRepository.target, + { uuid: invitedUserUuid }, + { + firstName, + lastName, + jobTitle, + phoneNumber, + roleType: { uuid: roleUuid }, + }, + ); + + // Remove old space associations + await queryRunner.manager.delete(this.inviteUserSpaceRepository.target, { + inviteUser: { uuid: invitedUserUuid }, + }); + + // Save new space associations + const spaceData = spaceUuids.map((spaceUuid) => ({ + inviteUser: { uuid: invitedUserUuid }, + space: { uuid: spaceUuid }, + })); + await queryRunner.manager.save( + this.inviteUserSpaceRepository.target, + spaceData, + ); + } + private async updateWhenUserIsActive( + queryRunner: QueryRunner, + dto: UpdateUserInvitationDto, + invitedUserUuid: string, + ): Promise { + const { + firstName, + lastName, + jobTitle, + phoneNumber, + roleUuid, + spaceUuids, + projectUuid, + } = dto; + + const user = await this.userRepository.findOne({ + where: { inviteUser: { uuid: invitedUserUuid } }, + relations: ['userSpaces.space', 'userSpaces.space.community'], + }); + + if (!user) { + throw new HttpException( + `User with invitedUserUuid ${invitedUserUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + + // Update user details + await queryRunner.manager.update( + this.inviteUserRepository.target, + { uuid: invitedUserUuid }, + { + firstName, + lastName, + jobTitle, + phoneNumber, + roleType: { uuid: roleUuid }, + }, + ); + + // Disassociate the user from all current spaces + const disassociatePromises = user.userSpaces.map((userSpace) => + this.spaceUserService + .disassociateUserFromSpace({ + communityUuid: userSpace.space.community.uuid, + spaceUuid: userSpace.space.uuid, + userUuid: user.uuid, + projectUuid, + }) + .catch((error) => { + console.error( + `Failed to disassociate user from space ${userSpace.space.uuid}:`, + error, + ); + throw error; + }), + ); + + await Promise.allSettled(disassociatePromises); + + // Process new spaces + const associatePromises = spaceUuids.map(async (spaceUuid) => { + try { + // Fetch space details + const spaceDetails = await this.getSpaceByUuid(spaceUuid); + + // Fetch device UUIDs for the space + const deviceUUIDs = + await this.userSpaceService.getDeviceUUIDsForSpace(spaceUuid); + + // Grant permissions to the user for all devices in the space + await this.userSpaceService.addUserPermissionsToDevices( + user.uuid, + deviceUUIDs, + ); + + // Associate the user with the new space + await this.spaceUserService.associateUserToSpace({ + communityUuid: spaceDetails.communityUuid, + spaceUuid: spaceUuid, + userUuid: user.uuid, + projectUuid, + }); + } catch (error) { + console.error(`Failed to process space ${spaceUuid}:`, error); + throw error; + } + }); + + await Promise.all(associatePromises); + } + + async getSpaceByUuid(spaceUuid: string) { + try { + const space = await this.spaceRepository.findOne({ + where: { + uuid: spaceUuid, + }, + relations: ['community'], + }); + if (!space) { + throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); + } + return { + uuid: space.uuid, + createdAt: space.createdAt, + updatedAt: space.updatedAt, + name: space.spaceName, + spaceTuyaUuid: space.community.externalId, + communityUuid: space.community.uuid, + }; + } catch (err) { + if (err instanceof BadRequestException) { + throw err; // Re-throw BadRequestException + } else { + throw new HttpException('Space not found', HttpStatus.NOT_FOUND); + } + } + } } diff --git a/src/project/project.module.ts b/src/project/project.module.ts index 0258881..260df68 100644 --- a/src/project/project.module.ts +++ b/src/project/project.module.ts @@ -9,6 +9,7 @@ import { CommunityRepository } from '@app/common/modules/community/repositories' import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; import { ProjectUserController } from './controllers/project-user.controller'; import { ProjectUserService } from './services/project-user.service'; +import { UserSpaceRepository } from '@app/common/modules/user/repositories'; const CommandHandlers = [CreateOrphanSpaceHandler]; @@ -24,6 +25,7 @@ const CommandHandlers = [CreateOrphanSpaceHandler]; ProjectUserService, ProjectRepository, InviteUserRepository, + UserSpaceRepository, ], exports: [ProjectService, CqrsModule], }) From a7615975b0c017366820cffeb0515fa36cb0ac7d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 18:57:19 -0600 Subject: [PATCH 157/247] Remove DISABLED status and add isEnabled field --- libs/common/src/constants/user-status.enum.ts | 1 - .../src/modules/Invite-user/entities/Invite-user.entity.ts | 5 +++++ src/project/services/project-user.service.ts | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/common/src/constants/user-status.enum.ts b/libs/common/src/constants/user-status.enum.ts index b0b9817..859fb04 100644 --- a/libs/common/src/constants/user-status.enum.ts +++ b/libs/common/src/constants/user-status.enum.ts @@ -1,5 +1,4 @@ export enum UserStatusEnum { ACTIVE = 'active', INVITED = 'invited', - DISABLED = 'disabled', } diff --git a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts index 491d16f..22bbbdb 100644 --- a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts +++ b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts @@ -61,6 +61,11 @@ export class InviteUserEntity extends AbstractEntity { default: true, }) public isActive: boolean; + @Column({ + nullable: false, + default: true, + }) + public isEnabled: boolean; @Column({ nullable: false, unique: true, diff --git a/src/project/services/project-user.service.ts b/src/project/services/project-user.service.ts index d69332f..290f606 100644 --- a/src/project/services/project-user.service.ts +++ b/src/project/services/project-user.service.ts @@ -35,6 +35,7 @@ export class ProjectUserService { 'phoneNumber', 'jobTitle', 'invitedBy', + 'isEnabled', ], relations: ['roleType'], }); @@ -90,6 +91,7 @@ export class ProjectUserService { 'phoneNumber', 'jobTitle', 'invitedBy', + 'isEnabled', ], relations: ['roleType', 'spaces.space'], }); From 5f26e7b7d2f3f4762a24a172141f33a76fb5e630 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 19:12:11 -0600 Subject: [PATCH 158/247] Simplify user space retrieval in ProjectUserService --- src/project/services/project-user.service.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/project/services/project-user.service.ts b/src/project/services/project-user.service.ts index 290f606..5564f13 100644 --- a/src/project/services/project-user.service.ts +++ b/src/project/services/project-user.service.ts @@ -3,7 +3,6 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; import { ProjectService } from './project.service'; -import { UserStatusEnum } from '@app/common/constants/user-status.enum'; import { UserSpaceRepository } from '@app/common/modules/user/repositories'; @Injectable() @@ -74,7 +73,6 @@ export class ProjectUserService { invitedUserUuid: string, ): Promise { try { - let userSpaces; const user = await this.inviteUserRepository.findOne({ where: { project: { uuid: projectUuid }, @@ -102,15 +100,7 @@ export class ProjectUserService { HttpStatus.NOT_FOUND, ); } - if (user.status === UserStatusEnum.ACTIVE) { - const spaces = await this.userSpaceRepository.find({ - where: { user: { inviteUser: { uuid: invitedUserUuid } } }, - relations: ['space'], - }); - userSpaces = spaces.map((space) => space.space); - } else { - userSpaces = user.spaces.map((space) => space.space); - } + const createdAt = new Date(user.createdAt); const createdDate = createdAt.toLocaleDateString(); const createdTime = createdAt.toLocaleTimeString(); @@ -121,7 +111,7 @@ export class ProjectUserService { roleType: user.roleType.type, createdDate, createdTime, - spaces: userSpaces, + spaces: user.spaces.map((space) => space.space), }, statusCode: HttpStatus.OK, }); From fe5170573010b4de6a0329b60aa88281c969775e Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 19:13:33 -0600 Subject: [PATCH 159/247] Add endpoint to disable user invitation --- libs/common/src/constants/controller-route.ts | 6 + .../controllers/invite-user.controller.ts | 23 ++- .../dtos/update.invite-user.dto.ts | 22 +++ .../services/invite-user.service.ts | 145 +++++++++++++++++- 4 files changed, 190 insertions(+), 6 deletions(-) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 934414d..2e627e9 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -766,6 +766,12 @@ export class ControllerRoute { public static readonly UPDATE_USER_INVITATION_DESCRIPTION = 'This endpoint updates an invitation for a user to assign to role and spaces.'; + public static readonly DISABLE_USER_INVITATION_SUMMARY = + 'Disable user invitation'; + + public static readonly DISABLE_USER_INVITATION_DESCRIPTION = + 'This endpoint disables an invitation for a user to assign to role and spaces.'; + public static readonly ACTIVATION_CODE_SUMMARY = 'Activate Invitation Code'; diff --git a/src/invite-user/controllers/invite-user.controller.ts b/src/invite-user/controllers/invite-user.controller.ts index 9da2224..f090bc5 100644 --- a/src/invite-user/controllers/invite-user.controller.ts +++ b/src/invite-user/controllers/invite-user.controller.ts @@ -17,7 +17,10 @@ import { Permissions } from 'src/decorators/permissions.decorator'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { CheckEmailDto } from '../dtos/check-email.dto'; import { ActivateCodeDto } from '../dtos/active-code.dto'; -import { UpdateUserInvitationDto } from '../dtos/update.invite-user.dto'; +import { + DisableUserInvitationDto, + UpdateUserInvitationDto, +} from '../dtos/update.invite-user.dto'; @ApiTags('Invite User Module') @Controller({ @@ -93,4 +96,22 @@ export class InviteUserController { invitedUserUuid, ); } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Put(':invitedUserUuid/disable') + @ApiOperation({ + summary: + ControllerRoute.INVITE_USER.ACTIONS.DISABLE_USER_INVITATION_SUMMARY, + description: + ControllerRoute.INVITE_USER.ACTIONS.DISABLE_USER_INVITATION_DESCRIPTION, + }) + async disableUserInvitation( + @Param('invitedUserUuid') invitedUserUuid: string, + @Body() disableUserInvitationDto: DisableUserInvitationDto, + ): Promise { + return await this.inviteUserService.disableUserInvitation( + disableUserInvitationDto, + invitedUserUuid, + ); + } } diff --git a/src/invite-user/dtos/update.invite-user.dto.ts b/src/invite-user/dtos/update.invite-user.dto.ts index 9e80306..6890ed1 100644 --- a/src/invite-user/dtos/update.invite-user.dto.ts +++ b/src/invite-user/dtos/update.invite-user.dto.ts @@ -2,6 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { ArrayMinSize, IsArray, + IsBoolean, IsNotEmpty, IsOptional, IsString, @@ -72,3 +73,24 @@ export class UpdateUserInvitationDto { Object.assign(this, dto); } } +export class DisableUserInvitationDto { + @ApiProperty({ + description: 'The disable status of the user', + example: 'true', + required: true, + }) + @IsBoolean() + @IsNotEmpty() + public disable: boolean; + @ApiProperty({ + description: 'The project uuid of the user', + example: 'd290f1ee-6c54-4b01-90e6-d701748f0851', + required: true, + }) + @IsString() + @IsNotEmpty() + public projectUuid: string; + constructor(dto: Partial) { + Object.assign(this, dto); + } +} diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index fd41f96..c56f71c 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -24,7 +24,10 @@ import { SpaceEntity, SpaceRepository } from '@app/common/modules/space'; import { ActivateCodeDto } from '../dtos/active-code.dto'; import { UserSpaceService } from 'src/users/services'; import { SpaceUserService } from 'src/space/services'; -import { UpdateUserInvitationDto } from '../dtos/update.invite-user.dto'; +import { + DisableUserInvitationDto, + UpdateUserInvitationDto, +} from '../dtos/update.invite-user.dto'; @Injectable() export class InviteUserService { @@ -285,10 +288,7 @@ export class InviteUserService { } // Perform update actions if status is 'INVITED' - if ( - userOldData.status === UserStatusEnum.INVITED || - userOldData.status === UserStatusEnum.DISABLED - ) { + if (userOldData.status === UserStatusEnum.INVITED) { await this.updateWhenUserIsInviteOrDisable( queryRunner, dto, @@ -475,4 +475,139 @@ export class InviteUserService { } } } + async disableUserInvitation( + dto: DisableUserInvitationDto, + invitedUserUuid: string, + ): Promise { + const { disable, projectUuid } = dto; + + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.startTransaction(); + + try { + const userData = await this.inviteUserRepository.findOne({ + where: { uuid: invitedUserUuid, project: { uuid: projectUuid } }, + relations: ['roleType', 'spaces.space'], + }); + + if (!userData) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND); + } + + if (userData.status === UserStatusEnum.INVITED) { + await this.updateUserStatus(invitedUserUuid, projectUuid, disable); + } else if (userData.status === UserStatusEnum.ACTIVE) { + const user = await this.userRepository.findOne({ + where: { inviteUser: { uuid: invitedUserUuid } }, + relations: ['userSpaces.space', 'userSpaces.space.community'], + }); + + if (!user) { + throw new HttpException( + 'User account not found', + HttpStatus.NOT_FOUND, + ); + } + + if (!disable) { + await this.disassociateUserFromSpaces(user, projectUuid); + await this.updateUserStatus(invitedUserUuid, projectUuid, disable); + } else if (disable) { + await this.associateUserToSpaces( + user, + userData, + projectUuid, + invitedUserUuid, + disable, + ); + } + } else { + throw new HttpException( + 'Invalid user status or action', + HttpStatus.BAD_REQUEST, + ); + } + + await queryRunner.commitTransaction(); + + return new SuccessResponseDto({ + statusCode: HttpStatus.OK, + success: true, + message: 'User invitation status updated successfully', + }); + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); + } + } + + private async updateUserStatus( + invitedUserUuid: string, + projectUuid: string, + disable: boolean, + ) { + await this.inviteUserRepository.update( + { uuid: invitedUserUuid, project: { uuid: projectUuid } }, + { isEnabled: disable }, + ); + } + + private async disassociateUserFromSpaces(user: any, projectUuid: string) { + const disassociatePromises = user.userSpaces.map((userSpace) => + this.spaceUserService.disassociateUserFromSpace({ + communityUuid: userSpace.space.community.uuid, + spaceUuid: userSpace.space.uuid, + userUuid: user.uuid, + projectUuid, + }), + ); + + const results = await Promise.allSettled(disassociatePromises); + + results.forEach((result, index) => { + if (result.status === 'rejected') { + console.error( + `Failed to disassociate user from space ${user.userSpaces[index].space.uuid}:`, + result.reason, + ); + } + }); + } + private async associateUserToSpaces( + user: any, + userData: any, + projectUuid: string, + invitedUserUuid: string, + disable: boolean, + ) { + const spaceUuids = userData.spaces.map((space) => space.space.uuid); + + const associatePromises = spaceUuids.map(async (spaceUuid) => { + try { + const spaceDetails = await this.getSpaceByUuid(spaceUuid); + + const deviceUUIDs = + await this.userSpaceService.getDeviceUUIDsForSpace(spaceUuid); + await this.userSpaceService.addUserPermissionsToDevices( + user.uuid, + deviceUUIDs, + ); + + await this.spaceUserService.associateUserToSpace({ + communityUuid: spaceDetails.communityUuid, + spaceUuid, + userUuid: user.uuid, + projectUuid, + }); + + await this.updateUserStatus(invitedUserUuid, projectUuid, disable); + } catch (error) { + console.error(`Failed to associate user to space ${spaceUuid}:`, error); + } + }); + + await Promise.allSettled(associatePromises); + } } From 10a2036fa098fc59a25b7bc370170eeaf7595720 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 19:18:55 -0600 Subject: [PATCH 160/247] Refactor update logic for invited users --- src/invite-user/services/invite-user.service.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index c56f71c..9792c8d 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -289,12 +289,9 @@ export class InviteUserService { // Perform update actions if status is 'INVITED' if (userOldData.status === UserStatusEnum.INVITED) { - await this.updateWhenUserIsInviteOrDisable( - queryRunner, - dto, - invitedUserUuid, - ); + await this.updateWhenUserIsInvite(queryRunner, dto, invitedUserUuid); } else if (userOldData.status === UserStatusEnum.ACTIVE) { + await this.updateWhenUserIsInvite(queryRunner, dto, invitedUserUuid); await this.updateWhenUserIsActive(queryRunner, dto, invitedUserUuid); } @@ -320,7 +317,7 @@ export class InviteUserService { } } - private async updateWhenUserIsInviteOrDisable( + private async updateWhenUserIsInvite( queryRunner: QueryRunner, dto: UpdateUserInvitationDto, invitedUserUuid: string, From 7ca9c19a2ee01168e677c1562e8762074794151f Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 19:45:01 -0600 Subject: [PATCH 161/247] Add Mailtrap disable and enable template UUIDs and update email service --- .env.example | 4 ++ libs/common/src/config/email.config.ts | 2 + libs/common/src/util/email.service.ts | 52 +++++++++++++++++++ .../services/invite-user.service.ts | 8 ++- 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 9525e51..3fc9868 100644 --- a/.env.example +++ b/.env.example @@ -58,6 +58,10 @@ MAILTRAP_API_TOKEN= MAILTRAP_INVITATION_TEMPLATE_UUID= +MAILTRAP_DISABLE_TEMPLATE_UUID= + +MAILTRAP_ENABLE_TEMPLATE_UUID= + WEBSITES_ENABLE_APP_SERVICE_STORAGE= PORT= diff --git a/libs/common/src/config/email.config.ts b/libs/common/src/config/email.config.ts index e18d4e8..88e012a 100644 --- a/libs/common/src/config/email.config.ts +++ b/libs/common/src/config/email.config.ts @@ -13,5 +13,7 @@ export default registerAs( MAILTRAP_API_TOKEN: process.env.MAILTRAP_API_TOKEN, MAILTRAP_INVITATION_TEMPLATE_UUID: process.env.MAILTRAP_INVITATION_TEMPLATE_UUID, + MAILTRAP_DISABLE_TEMPLATE_UUID: process.env.MAILTRAP_DISABLE_TEMPLATE_UUID, + MAILTRAP_ENABLE_TEMPLATE_UUID: process.env.MAILTRAP_ENABLE_TEMPLATE_UUID, }), ); diff --git a/libs/common/src/util/email.service.ts b/libs/common/src/util/email.service.ts index fd84ea6..cfdc509 100644 --- a/libs/common/src/util/email.service.ts +++ b/libs/common/src/util/email.service.ts @@ -68,6 +68,58 @@ export class EmailService { template_variables: emailInvitationData, }; + try { + await axios.post(API_URL, emailData, { + headers: { + Authorization: `Bearer ${API_TOKEN}`, + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + throw new HttpException( + error.response?.data?.message || + 'Error sending email using Mailtrap template', + error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async sendEmailWithDisableOrEnableTemplate( + email: string, + name: string, + isEnable: boolean, + ): Promise { + console.log(isEnable); + + const isProduction = process.env.NODE_ENV === 'production'; + const API_TOKEN = this.configService.get( + 'email-config.MAILTRAP_API_TOKEN', + ); + const API_URL = isProduction + ? SEND_EMAIL_API_URL_PROD + : SEND_EMAIL_API_URL_DEV; + const TEMPLATE_UUID = isEnable + ? this.configService.get( + 'email-config.MAILTRAP_ENABLE_TEMPLATE_UUID', + ) + : this.configService.get( + 'email-config.MAILTRAP_DISABLE_TEMPLATE_UUID', + ); + + const emailData = { + from: { + email: this.smtpConfig.sender, + }, + to: [ + { + email: email, + }, + ], + template_uuid: TEMPLATE_UUID, + template_variables: { + name: name, + }, + }; + try { await axios.post(API_URL, emailData, { headers: { diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 9792c8d..e87ea6d 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -114,7 +114,7 @@ export class InviteUserService { await Promise.all(spacePromises); await this.emailService.sendEmailWithInvitationTemplate(email, { - name: firstName + ' ' + lastName, + name: firstName, invitationCode, role: roleType, spacesList: spaceNamesString, @@ -524,7 +524,11 @@ export class InviteUserService { HttpStatus.BAD_REQUEST, ); } - + await this.emailService.sendEmailWithDisableOrEnableTemplate( + userData.email, + userData.firstName, + disable, + ); await queryRunner.commitTransaction(); return new SuccessResponseDto({ From 97da38e31d2d0353c6868f5f84b727b2053b6a5b Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 20:06:44 -0600 Subject: [PATCH 162/247] Add user active check and improve error handling in auth services --- libs/common/src/auth/services/auth.service.ts | 3 +++ src/auth/services/user-auth.service.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index 7f4f03a..95d592e 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -45,6 +45,9 @@ export class AuthService { if (!user.isUserVerified) { throw new BadRequestException('User is not verified'); } + if (!user.isActive) { + throw new BadRequestException('User is not active'); + } if (user) { const passwordMatch = this.helperHashService.bcryptCompare( pass, diff --git a/src/auth/services/user-auth.service.ts b/src/auth/services/user-auth.service.ts index 89eda76..afdf6d2 100644 --- a/src/auth/services/user-auth.service.ts +++ b/src/auth/services/user-auth.service.ts @@ -151,7 +151,7 @@ export class UserAuthService { }); return res; } catch (error) { - throw new BadRequestException('Invalid credentials'); + throw new BadRequestException(error.message || 'Invalid credentials'); } } From 81ec60d94745a8b58340d8002aa89d197340fa57 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 20:10:37 -0600 Subject: [PATCH 163/247] Add delete user invitation functionality --- .env.example | 2 + libs/common/src/config/email.config.ts | 2 + libs/common/src/constants/controller-route.ts | 6 ++ libs/common/src/util/email.service.ts | 21 +++--- .../controllers/invite-user.controller.ts | 14 ++++ .../services/invite-user.service.ts | 68 ++++++++++++++++++- 6 files changed, 103 insertions(+), 10 deletions(-) diff --git a/.env.example b/.env.example index 3fc9868..71a6505 100644 --- a/.env.example +++ b/.env.example @@ -62,6 +62,8 @@ MAILTRAP_DISABLE_TEMPLATE_UUID= MAILTRAP_ENABLE_TEMPLATE_UUID= +MAILTRAP_DELETE_USER_TEMPLATE_UUID= + WEBSITES_ENABLE_APP_SERVICE_STORAGE= PORT= diff --git a/libs/common/src/config/email.config.ts b/libs/common/src/config/email.config.ts index 88e012a..dba9e54 100644 --- a/libs/common/src/config/email.config.ts +++ b/libs/common/src/config/email.config.ts @@ -15,5 +15,7 @@ export default registerAs( process.env.MAILTRAP_INVITATION_TEMPLATE_UUID, MAILTRAP_DISABLE_TEMPLATE_UUID: process.env.MAILTRAP_DISABLE_TEMPLATE_UUID, MAILTRAP_ENABLE_TEMPLATE_UUID: process.env.MAILTRAP_ENABLE_TEMPLATE_UUID, + MAILTRAP_DELETE_USER_TEMPLATE_UUID: + process.env.MAILTRAP_DELETE_USER_TEMPLATE_UUID, }), ); diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 2e627e9..67a0085 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -772,6 +772,12 @@ export class ControllerRoute { public static readonly DISABLE_USER_INVITATION_DESCRIPTION = 'This endpoint disables an invitation for a user to assign to role and spaces.'; + public static readonly DELETE_USER_INVITATION_SUMMARY = + 'Delete user invitation'; + + public static readonly DELETE_USER_INVITATION_DESCRIPTION = + 'This endpoint deletes an invitation for a user to assign to role and spaces.'; + public static readonly ACTIVATION_CODE_SUMMARY = 'Activate Invitation Code'; diff --git a/libs/common/src/util/email.service.ts b/libs/common/src/util/email.service.ts index cfdc509..acdcd55 100644 --- a/libs/common/src/util/email.service.ts +++ b/libs/common/src/util/email.service.ts @@ -83,13 +83,12 @@ export class EmailService { ); } } - async sendEmailWithDisableOrEnableTemplate( + async sendEmailWithTemplate( email: string, name: string, isEnable: boolean, + isDelete: boolean, ): Promise { - console.log(isEnable); - const isProduction = process.env.NODE_ENV === 'production'; const API_TOKEN = this.configService.get( 'email-config.MAILTRAP_API_TOKEN', @@ -97,12 +96,16 @@ export class EmailService { const API_URL = isProduction ? SEND_EMAIL_API_URL_PROD : SEND_EMAIL_API_URL_DEV; - const TEMPLATE_UUID = isEnable + + // Determine the template UUID based on the arguments + const templateUuid = isDelete ? this.configService.get( - 'email-config.MAILTRAP_ENABLE_TEMPLATE_UUID', + 'email-config.MAILTRAP_DELETE_USER_TEMPLATE_UUID', ) : this.configService.get( - 'email-config.MAILTRAP_DISABLE_TEMPLATE_UUID', + isEnable + ? 'email-config.MAILTRAP_ENABLE_TEMPLATE_UUID' + : 'email-config.MAILTRAP_DISABLE_TEMPLATE_UUID', ); const emailData = { @@ -111,12 +114,12 @@ export class EmailService { }, to: [ { - email: email, + email, }, ], - template_uuid: TEMPLATE_UUID, + template_uuid: templateUuid, template_variables: { - name: name, + name, }, }; diff --git a/src/invite-user/controllers/invite-user.controller.ts b/src/invite-user/controllers/invite-user.controller.ts index f090bc5..8f65993 100644 --- a/src/invite-user/controllers/invite-user.controller.ts +++ b/src/invite-user/controllers/invite-user.controller.ts @@ -2,6 +2,7 @@ import { InviteUserService } from '../services/invite-user.service'; import { Body, Controller, + Delete, Param, Post, Put, @@ -114,4 +115,17 @@ export class InviteUserController { invitedUserUuid, ); } + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Delete(':invitedUserUuid') + @ApiOperation({ + summary: ControllerRoute.INVITE_USER.ACTIONS.DELETE_USER_INVITATION_SUMMARY, + description: + ControllerRoute.INVITE_USER.ACTIONS.DELETE_USER_INVITATION_DESCRIPTION, + }) + async deleteUserInvitation( + @Param('invitedUserUuid') invitedUserUuid: string, + ): Promise { + return await this.inviteUserService.deleteUserInvitation(invitedUserUuid); + } } diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index e87ea6d..2b5521c 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -524,10 +524,11 @@ export class InviteUserService { HttpStatus.BAD_REQUEST, ); } - await this.emailService.sendEmailWithDisableOrEnableTemplate( + await this.emailService.sendEmailWithTemplate( userData.email, userData.firstName, disable, + false, ); await queryRunner.commitTransaction(); @@ -611,4 +612,69 @@ export class InviteUserService { await Promise.allSettled(associatePromises); } + + async deleteUserInvitation( + invitedUserUuid: string, + ): Promise { + const queryRunner = this.dataSource.createQueryRunner(); + await queryRunner.startTransaction(); + + try { + const userData = await this.inviteUserRepository.findOne({ + where: { uuid: invitedUserUuid }, + relations: ['roleType', 'spaces.space', 'project'], + }); + + if (!userData) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND); + } + + if (userData.status === UserStatusEnum.INVITED) { + await this.inviteUserRepository.update( + { uuid: invitedUserUuid }, + { isActive: false }, + ); + } else if (userData.status === UserStatusEnum.ACTIVE) { + const user = await this.userRepository.findOne({ + where: { inviteUser: { uuid: invitedUserUuid } }, + relations: ['userSpaces.space', 'userSpaces.space.community'], + }); + + if (!user) { + throw new HttpException( + 'User account not found', + HttpStatus.NOT_FOUND, + ); + } + + await this.disassociateUserFromSpaces(user, userData.project.uuid); + await this.inviteUserRepository.update( + { uuid: invitedUserUuid }, + { isActive: false }, + ); + await this.userRepository.update( + { uuid: user.uuid }, + { isActive: false }, + ); + } + await this.emailService.sendEmailWithTemplate( + userData.email, + userData.firstName, + false, + true, + ); + await queryRunner.commitTransaction(); + + return new SuccessResponseDto({ + statusCode: HttpStatus.OK, + success: true, + message: 'User invitation deleted successfully', + }); + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); + } + } } From 5ce016f4291f3ce990250ec77be5755872ca3da0 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 4 Jan 2025 22:42:07 -0600 Subject: [PATCH 164/247] Add support for sending edit user email with template --- .env.example | 2 + libs/common/src/config/email.config.ts | 2 + libs/common/src/util/email.service.ts | 78 +++++++++++++++++++ src/invite-user/invite-user.module.ts | 2 + .../services/invite-user.service.ts | 65 +++++++++++++++- 5 files changed, 148 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 71a6505..c194e08 100644 --- a/.env.example +++ b/.env.example @@ -58,6 +58,8 @@ MAILTRAP_API_TOKEN= MAILTRAP_INVITATION_TEMPLATE_UUID= +MAILTRAP_EDIT_USER_TEMPLATE_UUID= + MAILTRAP_DISABLE_TEMPLATE_UUID= MAILTRAP_ENABLE_TEMPLATE_UUID= diff --git a/libs/common/src/config/email.config.ts b/libs/common/src/config/email.config.ts index dba9e54..7c9b776 100644 --- a/libs/common/src/config/email.config.ts +++ b/libs/common/src/config/email.config.ts @@ -17,5 +17,7 @@ export default registerAs( MAILTRAP_ENABLE_TEMPLATE_UUID: process.env.MAILTRAP_ENABLE_TEMPLATE_UUID, MAILTRAP_DELETE_USER_TEMPLATE_UUID: process.env.MAILTRAP_DELETE_USER_TEMPLATE_UUID, + MAILTRAP_EDIT_USER_TEMPLATE_UUID: + process.env.MAILTRAP_EDIT_USER_TEMPLATE_UUID, }), ); diff --git a/libs/common/src/util/email.service.ts b/libs/common/src/util/email.service.ts index acdcd55..c6e09ec 100644 --- a/libs/common/src/util/email.service.ts +++ b/libs/common/src/util/email.service.ts @@ -138,4 +138,82 @@ export class EmailService { ); } } + async sendEditUserEmailWithTemplate( + email: string, + emailEditData: any, + ): Promise { + const isProduction = process.env.NODE_ENV === 'production'; + const API_TOKEN = this.configService.get( + 'email-config.MAILTRAP_API_TOKEN', + ); + const API_URL = isProduction + ? SEND_EMAIL_API_URL_PROD + : SEND_EMAIL_API_URL_DEV; + const TEMPLATE_UUID = this.configService.get( + 'email-config.MAILTRAP_EDIT_USER_TEMPLATE_UUID', + ); + + const emailData = { + from: { + email: this.smtpConfig.sender, + }, + to: [ + { + email: email, + }, + ], + template_uuid: TEMPLATE_UUID, + template_variables: emailEditData, + }; + + try { + await axios.post(API_URL, emailData, { + headers: { + Authorization: `Bearer ${API_TOKEN}`, + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + throw new HttpException( + error.response?.data?.message || + 'Error sending email using Mailtrap template', + error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + generateUserChangesEmailBody( + addedSpaceNames: string[], + removedSpaceNames: string[], + oldRole: string, + newRole: string, + oldName: string, + newName: string, + ) { + const addedSpaceNamesChanged = + addedSpaceNames.length > 0 + ? `Access to the following spaces were added: ${addedSpaceNames.join(', ')}` + : ''; + + const removedSpaceNamesChanged = + removedSpaceNames.length > 0 + ? `Access to the following spaces were deleted: ${removedSpaceNames.join(', ')}` + : ''; + + const roleChanged = + oldRole !== newRole + ? `Your user role has been changed from [${oldRole}] to [${newRole}]` + : ''; + + const nameChanged = + oldName !== newName + ? `The name associated with your account has changed from [${oldName}] to [${newName}]` + : ''; + + return { + addedSpaceNamesChanged, + removedSpaceNamesChanged, + roleChanged, + nameChanged, + }; + } } diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index 443b928..fc0b579 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -25,6 +25,7 @@ import { UserDevicePermissionService } from 'src/user-device-permission/services import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; import { ProjectUserService } from 'src/project/services/project-user.service'; +import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; @Module({ imports: [ConfigModule, InviteUserRepositoryModule], @@ -49,6 +50,7 @@ import { ProjectUserService } from 'src/project/services/project-user.service'; DeviceUserPermissionRepository, PermissionTypeRepository, ProjectUserService, + RoleTypeRepository, ], exports: [InviteUserService], }) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 2b5521c..c62a364 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -28,6 +28,7 @@ import { DisableUserInvitationDto, UpdateUserInvitationDto, } from '../dtos/update.invite-user.dto'; +import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; @Injectable() export class InviteUserService { @@ -39,6 +40,7 @@ export class InviteUserService { private readonly userSpaceService: UserSpaceService, private readonly spaceUserService: SpaceUserService, private readonly spaceRepository: SpaceRepository, + private readonly roleTypeRepository: RoleTypeRepository, private readonly dataSource: DataSource, ) {} @@ -272,7 +274,7 @@ export class InviteUserService { dto: UpdateUserInvitationDto, invitedUserUuid: string, ): Promise { - const { projectUuid } = dto; + const { projectUuid, spaceUuids } = dto; const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.startTransaction(); @@ -281,6 +283,7 @@ export class InviteUserService { // Fetch the user's existing data in the project const userOldData = await this.inviteUserRepository.findOne({ where: { uuid: invitedUserUuid, project: { uuid: projectUuid } }, + relations: ['project', 'spaces.space', 'roleType'], }); if (!userOldData) { @@ -294,7 +297,60 @@ export class InviteUserService { await this.updateWhenUserIsInvite(queryRunner, dto, invitedUserUuid); await this.updateWhenUserIsActive(queryRunner, dto, invitedUserUuid); } + // Extract existing space UUIDs + const oldSpaceUuids = userOldData.spaces.map((space) => space.space.uuid); + // Compare spaces + const addedSpaces = spaceUuids.filter( + (uuid) => !oldSpaceUuids.includes(uuid), + ); + const removedSpaces = oldSpaceUuids.filter( + (uuid) => !spaceUuids.includes(uuid), + ); + + // Fetch the space names for added and removed spaces + const spaceRepo = queryRunner.manager.getRepository(SpaceEntity); + const addedSpacesDetails = await spaceRepo.find({ + where: { + uuid: In(addedSpaces), + }, + }); + + const removedSpacesDetails = await spaceRepo.find({ + where: { + uuid: In(removedSpaces), + }, + }); + + // Extract the names of the added and removed spaces + const addedSpaceNames = addedSpacesDetails.map( + (space) => space.spaceName, + ); + const removedSpaceNames = removedSpacesDetails.map( + (space) => space.spaceName, + ); + + // Check for role and name change + const oldRole = userOldData.roleType.type; + const newRole = await this.getRoleTypeByUuid(dto.roleUuid); + const oldFullName = `${userOldData.firstName} ${userOldData.lastName}`; + const newFullName = `${dto.firstName} ${dto.lastName}`; + + // Generate email body + const emailMessage = this.emailService.generateUserChangesEmailBody( + addedSpaceNames, + removedSpaceNames, + oldRole, + newRole, + oldFullName, + newFullName, + ); + await this.emailService.sendEditUserEmailWithTemplate( + userOldData.email, + emailMessage, + ); + + // Proceed with other updates (e.g., roles, names, etc.) await queryRunner.commitTransaction(); return new SuccessResponseDto({ @@ -316,6 +372,13 @@ export class InviteUserService { await queryRunner.release(); } } + private async getRoleTypeByUuid(roleUuid: string) { + const role = await this.roleTypeRepository.findOne({ + where: { uuid: roleUuid }, + }); + + return role.type; + } private async updateWhenUserIsInvite( queryRunner: QueryRunner, From 20356cceda219a73fb58be7db6450d43a856873c Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 5 Jan 2025 04:17:11 -0600 Subject: [PATCH 165/247] Update RoleController to use JwtAuthGuard --- src/role/controllers/role.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/role/controllers/role.controller.ts b/src/role/controllers/role.controller.ts index 8d0d401..c684d68 100644 --- a/src/role/controllers/role.controller.ts +++ b/src/role/controllers/role.controller.ts @@ -1,9 +1,9 @@ import { Controller, Get, HttpStatus, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { RoleService } from '../services/role.service'; -import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { ControllerRoute } from '@app/common/constants/controller-route'; // Assuming this is where the routes are defined +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; @ApiTags('Role Module') @Controller({ @@ -14,7 +14,7 @@ export class RoleController { constructor(private readonly roleService: RoleService) {} @ApiBearerAuth() - @UseGuards(SuperAdminRoleGuard) + @UseGuards(JwtAuthGuard) @Get('types') @ApiOperation({ summary: ControllerRoute.ROLE.ACTIONS.FETCH_ROLE_TYPES_SUMMARY, From 799f12ce49eebf6aa4ebe02540500220b9798392 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 22:01:14 +0400 Subject: [PATCH 166/247] hide deleted --- src/space-model/services/space-model.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index bfed573..3840cbf 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -112,6 +112,7 @@ export class SpaceModelService { pageable.modelName = 'space-model'; pageable.where = { project: { uuid: param.projectUuid }, + disabled: true, }; pageable.include = 'subspaceModels,tags,subspaceModels.tags,tags.product,subspaceModels.tags.product'; From 225bb403985d15161be4b2ce62d11d7d0e533e73 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 5 Jan 2025 22:03:25 +0400 Subject: [PATCH 167/247] fix delete --- src/space-model/services/space-model.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 3840cbf..ed40153 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -112,7 +112,7 @@ export class SpaceModelService { pageable.modelName = 'space-model'; pageable.where = { project: { uuid: param.projectUuid }, - disabled: true, + disabled: false, }; pageable.include = 'subspaceModels,tags,subspaceModels.tags,tags.product,subspaceModels.tags.product'; From c0c876c49189ed6ee8a7293508f95c8822d27824 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 6 Jan 2025 12:39:07 +0400 Subject: [PATCH 168/247] fixed bugs --- .../entities/space-model.entity.ts | 10 +- .../propogate-subspace-update-command.ts | 2 + .../handlers/propate-subspace-handler.ts | 46 ++- .../services/space-model.service.ts | 48 ++- .../subspace/subspace-model.service.ts | 281 +++++++++++------- src/space-model/services/tag-model.service.ts | 109 ++++--- 6 files changed, 308 insertions(+), 188 deletions(-) diff --git a/libs/common/src/modules/space-model/entities/space-model.entity.ts b/libs/common/src/modules/space-model/entities/space-model.entity.ts index 18d0688..648e90d 100644 --- a/libs/common/src/modules/space-model/entities/space-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/space-model.entity.ts @@ -1,11 +1,4 @@ -import { - Entity, - Column, - OneToMany, - ManyToOne, - JoinColumn, - Unique, -} from 'typeorm'; +import { Entity, Column, OneToMany, ManyToOne, JoinColumn } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { SpaceModelDto } from '../dtos'; import { SubspaceModelEntity } from './subspace-model'; @@ -14,7 +7,6 @@ import { SpaceEntity } from '../../space/entities'; import { TagModel } from './tag-model.entity'; @Entity({ name: 'space-model' }) -@Unique(['modelName', 'project']) export class SpaceModelEntity extends AbstractEntity { @Column({ type: 'uuid', diff --git a/src/space-model/commands/propogate-subspace-update-command.ts b/src/space-model/commands/propogate-subspace-update-command.ts index 3b8ccdb..a2b5203 100644 --- a/src/space-model/commands/propogate-subspace-update-command.ts +++ b/src/space-model/commands/propogate-subspace-update-command.ts @@ -1,12 +1,14 @@ import { ICommand } from '@nestjs/cqrs'; import { SpaceModelEntity } from '@app/common/modules/space-model'; import { ModifyspaceModelPayload } from '../interfaces'; +import { QueryRunner } from 'typeorm'; export class PropogateUpdateSpaceModelCommand implements ICommand { constructor( public readonly param: { spaceModel: SpaceModelEntity; modifiedSpaceModels: ModifyspaceModelPayload; + queryRunner: QueryRunner; }, ) {} } diff --git a/src/space-model/handlers/propate-subspace-handler.ts b/src/space-model/handlers/propate-subspace-handler.ts index f59aeb2..7c0bfd9 100644 --- a/src/space-model/handlers/propate-subspace-handler.ts +++ b/src/space-model/handlers/propate-subspace-handler.ts @@ -29,36 +29,37 @@ export class PropogateUpdateSpaceModelHandler ) {} async execute(command: PropogateUpdateSpaceModelCommand): Promise { - const { spaceModel, modifiedSpaceModels } = command.param; - const queryRunner = this.dataSource.createQueryRunner(); + const { spaceModel, modifiedSpaceModels, queryRunner } = command.param; try { - await queryRunner.connect(); - await queryRunner.startTransaction(); - const spaces = await this.spaceRepository.find({ + const spaces = await queryRunner.manager.find(SpaceEntity, { where: { spaceModel }, }); - if ( - modifiedSpaceModels.modifiedSubspaceModels.addedSubspaceModels.length > - 0 - ) { + + const { modifiedSubspaceModels = {}, modifiedTags = {} } = + modifiedSpaceModels; + + const { + addedSubspaceModels = [], + updatedSubspaceModels = [], + deletedSubspaceModels = [], + } = modifiedSubspaceModels; + + const { added = [], updated = [], deleted = [] } = modifiedTags; + + if (addedSubspaceModels.length > 0) { await this.addSubspaceModels( modifiedSpaceModels.modifiedSubspaceModels.addedSubspaceModels, spaces, queryRunner, ); - } else if ( - modifiedSpaceModels.modifiedSubspaceModels.updatedSubspaceModels - .length > 0 - ) { + } else if (updatedSubspaceModels.length > 0) { await this.updateSubspaceModels( modifiedSpaceModels.modifiedSubspaceModels.updatedSubspaceModels, queryRunner, ); } - if ( - modifiedSpaceModels.modifiedSubspaceModels.deletedSubspaceModels?.length - ) { + if (deletedSubspaceModels.length > 0) { const dtos: ModifySubspaceDto[] = modifiedSpaceModels.modifiedSubspaceModels.deletedSubspaceModels.map( (model) => ({ @@ -69,7 +70,7 @@ export class PropogateUpdateSpaceModelHandler await this.subSpaceService.modifySubSpace(dtos, queryRunner); } - if (modifiedSpaceModels.modifiedTags.added.length > 0) { + if (added.length > 0) { await this.createTags( modifiedSpaceModels.modifiedTags.added, queryRunner, @@ -78,26 +79,21 @@ export class PropogateUpdateSpaceModelHandler ); } - if (modifiedSpaceModels.modifiedTags.updated.length > 0) { + if (updated.length > 0) { await this.updateTags( modifiedSpaceModels.modifiedTags.updated, queryRunner, ); } - if (modifiedSpaceModels.modifiedTags.deleted.length > 0) { + if (deleted.length > 0) { await this.deleteTags( modifiedSpaceModels.modifiedTags.deleted, queryRunner, ); } - - await queryRunner.commitTransaction(); } catch (error) { - await queryRunner.rollbackTransaction(); - throw error; - } finally { - await queryRunner.release(); + console.error(error); } } diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index ed40153..723267e 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -7,7 +7,7 @@ import { CreateSpaceModelDto, UpdateSpaceModelDto } from '../dtos'; import { ProjectParam } from 'src/community/dtos'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { SubSpaceModelService } from './subspace/subspace-model.service'; -import { DataSource } from 'typeorm'; +import { DataSource, QueryRunner } from 'typeorm'; import { TypeORMCustomModel, TypeORMCustomModelFindAllQuery, @@ -49,7 +49,11 @@ export class SpaceModelService { try { const project = await this.validateProject(params.projectUuid); - await this.validateName(modelName, params.projectUuid); + await this.validateNameUsingQueryRunner( + modelName, + params.projectUuid, + queryRunner, + ); const spaceModel = this.spaceModelRepository.create({ modelName, @@ -140,20 +144,29 @@ export class SpaceModelService { } async update(dto: UpdateSpaceModelDto, param: SpaceModelParam) { + const queryRunner = this.dataSource.createQueryRunner(); + await this.validateProject(param.projectUuid); const spaceModel = await this.validateSpaceModel(param.spaceModelUuid); - const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); - await queryRunner.startTransaction(); let modifiedSubspaceModels: ModifySubspaceModelPayload = {}; let modifiedTagsModelPayload: ModifiedTagsModelPayload = {}; try { + await queryRunner.startTransaction(); + const { modelName } = dto; if (modelName) { - await this.validateName(modelName, param.projectUuid); + await this.validateNameUsingQueryRunner( + modelName, + param.projectUuid, + queryRunner, + ); spaceModel.modelName = modelName; - await queryRunner.manager.save(spaceModel); + await queryRunner.manager.save( + this.spaceModelRepository.target, + spaceModel, + ); } if (dto.subspaceModels) { @@ -182,6 +195,7 @@ export class SpaceModelService { modifiedSubspaceModels, modifiedTags: modifiedTagsModelPayload, }, + queryRunner, }), ); @@ -191,6 +205,11 @@ export class SpaceModelService { }); } catch (error) { await queryRunner.rollbackTransaction(); + + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( error.message || 'Failed to update SpaceModel', HttpStatus.INTERNAL_SERVER_ERROR, @@ -271,6 +290,23 @@ export class SpaceModelService { } } + async validateNameUsingQueryRunner( + modelName: string, + projectUuid: string, + queryRunner: QueryRunner, + ): Promise { + const isModelExist = await queryRunner.manager.findOne(SpaceModelEntity, { + where: { modelName, project: { uuid: projectUuid }, disabled: false }, + }); + + if (isModelExist) { + throw new HttpException( + `Model name ${modelName} already exists in the project with UUID ${projectUuid}.`, + HttpStatus.CONFLICT, + ); + } + } + async validateSpaceModel(uuid: string): Promise { const spaceModel = await this.spaceModelRepository.findOne({ where: { diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index e8a7bd0..9a52565 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -31,38 +31,50 @@ export class SubSpaceModelService { queryRunner: QueryRunner, otherTags?: CreateTagModelDto[], ): Promise { - this.validateInputDtos(subSpaceModelDtos, spaceModel); + try { + await this.validateInputDtos(subSpaceModelDtos, spaceModel); - const subspaces = subSpaceModelDtos.map((subspaceDto) => - queryRunner.manager.create(this.subspaceModelRepository.target, { - subspaceName: subspaceDto.subspaceName, - spaceModel, - }), - ); + const subspaces = subSpaceModelDtos.map((subspaceDto) => + queryRunner.manager.create(this.subspaceModelRepository.target, { + subspaceName: subspaceDto.subspaceName, + spaceModel, + }), + ); - const savedSubspaces = await queryRunner.manager.save(subspaces); + const savedSubspaces = await queryRunner.manager.save(subspaces); - await Promise.all( - subSpaceModelDtos.map(async (dto, index) => { - const subspace = savedSubspaces[index]; + await Promise.all( + subSpaceModelDtos.map(async (dto, index) => { + const subspace = savedSubspaces[index]; - const otherDtoTags = subSpaceModelDtos - .filter((_, i) => i !== index) - .flatMap((otherDto) => otherDto.tags || []); + const otherDtoTags = subSpaceModelDtos + .filter((_, i) => i !== index) + .flatMap((otherDto) => otherDto.tags || []); - if (dto.tags?.length) { - subspace.tags = await this.tagModelService.createTags( - dto.tags, - queryRunner, - null, - subspace, - [...(otherTags || []), ...otherDtoTags], - ); - } - }), - ); + if (dto.tags?.length) { + subspace.tags = await this.tagModelService.createTags( + dto.tags, + queryRunner, + null, + subspace, + [...(otherTags || []), ...otherDtoTags], + ); + } + }), + ); - return savedSubspaces; + return savedSubspaces; + } catch (error) { + if (error instanceof HttpException) { + throw error; // Rethrow known HttpExceptions + } + + // Handle unexpected errors + throw new HttpException( + `An error occurred while creating subspace models: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } } async deleteSubspaceModels( @@ -104,38 +116,53 @@ export class SubSpaceModelService { spaceModel: SpaceModelEntity, queryRunner: QueryRunner, ): Promise { - const modifiedSubspaceModels: ModifySubspaceModelPayload = {}; - for (const subspace of subspaceDtos) { - switch (subspace.action) { - case ModifyAction.ADD: - const subspaceModel = await this.handleAddAction( - subspace, - spaceModel, - queryRunner, - ); - modifiedSubspaceModels.addedSubspaceModels.push(subspaceModel); - break; - case ModifyAction.UPDATE: - const updatedSubspaceModel = await this.handleUpdateAction( - subspace, - queryRunner, - ); - modifiedSubspaceModels.updatedSubspaceModels.push( - updatedSubspaceModel, - ); - break; - case ModifyAction.DELETE: - await this.handleDeleteAction(subspace, queryRunner); - modifiedSubspaceModels.deletedSubspaceModels.push(subspace.uuid); - break; - default: - throw new HttpException( - `Invalid action "${subspace.action}".`, - HttpStatus.BAD_REQUEST, - ); + const modifiedSubspaceModels: ModifySubspaceModelPayload = { + addedSubspaceModels: [], + updatedSubspaceModels: [], + deletedSubspaceModels: [], + }; + try { + for (const subspace of subspaceDtos) { + switch (subspace.action) { + case ModifyAction.ADD: + const subspaceModel = await this.handleAddAction( + subspace, + spaceModel, + queryRunner, + ); + modifiedSubspaceModels.addedSubspaceModels.push(subspaceModel); + break; + case ModifyAction.UPDATE: + const updatedSubspaceModel = await this.handleUpdateAction( + subspace, + queryRunner, + ); + modifiedSubspaceModels.updatedSubspaceModels.push( + updatedSubspaceModel, + ); + break; + case ModifyAction.DELETE: + await this.handleDeleteAction(subspace, queryRunner); + modifiedSubspaceModels.deletedSubspaceModels.push(subspace.uuid); + break; + default: + throw new HttpException( + `Invalid action "${subspace.action}".`, + HttpStatus.BAD_REQUEST, + ); + } } + return modifiedSubspaceModels; + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + `An error occurred while modifying subspace models: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); } - return modifiedSubspaceModels; } private async handleAddAction( @@ -143,24 +170,35 @@ export class SubSpaceModelService { spaceModel: SpaceModelEntity, queryRunner: QueryRunner, ): Promise { - const createTagDtos: CreateTagModelDto[] = - subspace.tags?.map((tag) => ({ - tag: tag.tag, - productUuid: tag.productUuid, - })) || []; + try { + const createTagDtos: CreateTagModelDto[] = + subspace.tags?.map((tag) => ({ + tag: tag.tag, + productUuid: tag.productUuid, + })) || []; - const [createdSubspaceModel] = await this.createSubSpaceModels( - [ - { - subspaceName: subspace.subspaceName, - tags: createTagDtos, - }, - ], - spaceModel, - queryRunner, - ); + const [createdSubspaceModel] = await this.createSubSpaceModels( + [ + { + subspaceName: subspace.subspaceName, + tags: createTagDtos, + }, + ], + spaceModel, + queryRunner, + ); - return createdSubspaceModel; + return createdSubspaceModel; + } catch (error) { + if (error instanceof HttpException) { + throw error; // Rethrow known HttpExceptions + } + + throw new HttpException( + `An error occurred while adding subspace: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } } private async handleUpdateAction( @@ -221,7 +259,7 @@ export class SubSpaceModelService { private async findOne(subspaceUuid: string): Promise { const subspace = await this.subspaceModelRepository.findOne({ where: { uuid: subspaceUuid }, - relations: ['tags'], + relations: ['tags', 'spaceModel'], }); if (!subspace) { throw new HttpException( @@ -232,59 +270,86 @@ export class SubSpaceModelService { return subspace; } - private validateInputDtos( + private async validateInputDtos( subSpaceModelDtos: CreateSubspaceModelDto[], spaceModel: SpaceModelEntity, - ): void { - if (subSpaceModelDtos.length === 0) { + ): Promise { + try { + if (subSpaceModelDtos.length === 0) { + throw new HttpException( + 'Subspace models cannot be empty.', + HttpStatus.BAD_REQUEST, + ); + } + + await this.validateName( + subSpaceModelDtos.map((dto) => dto.subspaceName), + spaceModel, + ); + } catch (error) { + if (error instanceof HttpException) { + throw error; // Rethrow known HttpExceptions to preserve their message and status + } + + // Wrap unexpected errors throw new HttpException( - 'Subspace models cannot be empty.', - HttpStatus.BAD_REQUEST, + `An error occurred while validating subspace models: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, ); } - this.validateName( - subSpaceModelDtos.map((dto) => dto.subspaceName), - spaceModel, - ); } private async validateName( names: string[], spaceModel: SpaceModelEntity, ): Promise { - const seenNames = new Set(); - const duplicateNames = new Set(); + try { + const seenNames = new Set(); + const duplicateNames = new Set(); - for (const name of names) { - if (!seenNames.add(name)) { - duplicateNames.add(name); + // Check for duplicate names within the input array + for (const name of names) { + if (!seenNames.add(name)) { + duplicateNames.add(name); + } } - } - if (duplicateNames.size > 0) { - throw new HttpException( - `Duplicate subspace model names found: ${[...duplicateNames].join(', ')}`, - HttpStatus.CONFLICT, - ); - } + if (duplicateNames.size > 0) { + throw new HttpException( + `Duplicate subspace model names found: ${[...duplicateNames].join(', ')}`, + HttpStatus.CONFLICT, + ); + } - const existingNames = await this.subspaceModelRepository.find({ - select: ['subspaceName'], - where: { - subspaceName: In([...seenNames]), - spaceModel: { - uuid: spaceModel.uuid, + // Check for existing names in the database + const existingNames = await this.subspaceModelRepository.find({ + select: ['subspaceName'], + where: { + subspaceName: In([...seenNames]), + spaceModel: { + uuid: spaceModel.uuid, + }, }, - }, - }); + }); - if (existingNames.length > 0) { - const existingNamesList = existingNames - .map((e) => e.subspaceName) - .join(', '); + if (existingNames.length > 0) { + const existingNamesList = existingNames + .map((e) => e.subspaceName) + .join(', '); + throw new HttpException( + `Subspace model names already exist in the space: ${existingNamesList}`, + HttpStatus.BAD_REQUEST, + ); + } + } catch (error) { + if (error instanceof HttpException) { + throw error; // Rethrow known HttpExceptions + } + + // Handle unexpected errors throw new HttpException( - `Subspace model names already exist in the space: ${existingNamesList}`, - HttpStatus.BAD_REQUEST, + `An error occurred while validating subspace model names: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, ); } } diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index d08fe38..b251b28 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -134,7 +134,11 @@ export class TagModelService { spaceModel?: SpaceModelEntity, subspaceModel?: SubspaceModelEntity, ): Promise { - const modifiedTagModels: ModifiedTagsModelPayload = {}; + const modifiedTagModels: ModifiedTagsModelPayload = { + added: [], + updated: [], + deleted: [], + }; try { for (const tag of tags) { if (tag.action === ModifyAction.ADD) { @@ -190,25 +194,35 @@ export class TagModelService { productUuid: string, spaceModel: SpaceModelEntity, ): Promise { - const tagExists = await this.tagModelRepository.exists({ - where: [ - { - tag, - spaceModel: { uuid: spaceModel.uuid }, - product: { uuid: productUuid }, - disabled: false, - }, - { - tag, - subspaceModel: { spaceModel: { uuid: spaceModel.uuid } }, - product: { uuid: productUuid }, - disabled: false, - }, - ], - }); + try { + const tagExists = await this.tagModelRepository.exists({ + where: [ + { + tag, + spaceModel: { uuid: spaceModel.uuid }, + product: { uuid: productUuid }, + disabled: false, + }, + { + tag, + subspaceModel: { spaceModel: { uuid: spaceModel.uuid } }, + product: { uuid: productUuid }, + disabled: false, + }, + ], + }); - if (tagExists) { - throw new HttpException(`Tag can't be reused`, HttpStatus.CONFLICT); + if (tagExists) { + throw new HttpException(`Tag can't be reused`, HttpStatus.CONFLICT); + } + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + throw new HttpException( + `An error occurred while checking tag reuse: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } @@ -218,31 +232,46 @@ export class TagModelService { spaceModel?: SpaceModelEntity, subspaceModel?: SubspaceModelEntity, ): Promise { - const product = await this.productService.findOne(tagDto.productUuid); + try { + const product = await this.productService.findOne(tagDto.productUuid); - if (!product) { + if (!product) { + throw new HttpException( + `Product with UUID ${tagDto.productUuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + + if (spaceModel) { + await this.checkTagReuse(tagDto.tag, tagDto.productUuid, spaceModel); + } else if (subspaceModel && subspaceModel.spaceModel) { + await this.checkTagReuse( + tagDto.tag, + tagDto.productUuid, + subspaceModel.spaceModel, + ); + } else { + throw new HttpException( + `Invalid subspaceModel or spaceModel provided.`, + HttpStatus.BAD_REQUEST, + ); + } + + return queryRunner.manager.create(TagModel, { + tag: tagDto.tag, + product: product.data, + spaceModel, + subspaceModel, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } throw new HttpException( - `Product with UUID ${tagDto.productUuid} not found.`, - HttpStatus.NOT_FOUND, + `An error occurred while preparing the tag entity: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, ); } - - if (spaceModel) { - await this.checkTagReuse(tagDto.tag, tagDto.productUuid, spaceModel); - } else { - await this.checkTagReuse( - tagDto.tag, - tagDto.productUuid, - subspaceModel.spaceModel, - ); - } - - return queryRunner.manager.create(TagModel, { - tag: tagDto.tag, - product: product.data, - spaceModel, - subspaceModel, - }); } async getTagByUuid(uuid: string): Promise { From d719ad60f4d5492e179909aa17acb5353dc60aa7 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 6 Jan 2025 13:42:08 +0400 Subject: [PATCH 169/247] fixed delete space --- .../src/modules/space/entities/tag.entity.ts | 1 + src/space/services/space.service.ts | 39 ++++++++++--------- src/space/services/tag/tag.service.ts | 2 +- src/users/services/user-space.service.ts | 5 ++- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/libs/common/src/modules/space/entities/tag.entity.ts b/libs/common/src/modules/space/entities/tag.entity.ts index e7f8599..6ba1289 100644 --- a/libs/common/src/modules/space/entities/tag.entity.ts +++ b/libs/common/src/modules/space/entities/tag.entity.ts @@ -48,5 +48,6 @@ export class TagEntity extends AbstractEntity { @OneToOne(() => DeviceEntity, (device) => device.tag, { nullable: true, }) + @JoinColumn({ name: 'device_id' }) device: DeviceEntity; } diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 2d581e9..d0394c6 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -19,7 +19,7 @@ import { SpaceEntity } from '@app/common/modules/space/entities'; import { generateRandomString } from '@app/common/helper/randomString'; import { SpaceLinkService } from './space-link'; import { SubSpaceService } from './subspace'; -import { DataSource, Not, QueryRunner } from 'typeorm'; +import { DataSource, QueryRunner } from 'typeorm'; import { ValidationService } from './space-validation.service'; import { ORPHAN_COMMUNITY_NAME, @@ -171,25 +171,28 @@ export class SpaceService { ); try { // Get all spaces related to the community, including the parent-child relations - const spaces = await this.spaceRepository.find({ - where: { - community: { uuid: communityUuid }, - spaceName: Not(`${ORPHAN_SPACE_NAME}`), - disabled: false, - }, - relations: [ - 'parent', + const spaces = await this.spaceRepository + .createQueryBuilder('space') + .leftJoinAndSelect('space.parent', 'parent') + .leftJoinAndSelect( + 'space.children', 'children', - 'incomingConnections', - 'tags', - 'tags.product', - 'subspaces', - 'subspaces.tags', - 'subspaces.tags.product', - ], - }); + 'children.disabled = :disabled', + { disabled: false }, + ) + .leftJoinAndSelect('space.incomingConnections', 'incomingConnections') + .leftJoinAndSelect('space.tags', 'tags') + .leftJoinAndSelect('tags.product', 'tagProduct') + .leftJoinAndSelect('space.subspaces', 'subspaces') + .leftJoinAndSelect('subspaces.tags', 'subspaceTags') + .leftJoinAndSelect('subspaceTags.product', 'subspaceTagProduct') + .where('space.community_id = :communityUuid', { communityUuid }) + .andWhere('space.spaceName != :orphanSpaceName', { + orphanSpaceName: ORPHAN_SPACE_NAME, + }) + .andWhere('space.disabled = :disabled', { disabled: false }) + .getMany(); - // Organize spaces into a hierarchical structure const spaceHierarchy = this.buildSpaceHierarchy(spaces); return new SuccessResponseDto({ diff --git a/src/space/services/tag/tag.service.ts b/src/space/services/tag/tag.service.ts index 4c3bb8f..be32a2e 100644 --- a/src/space/services/tag/tag.service.ts +++ b/src/space/services/tag/tag.service.ts @@ -138,7 +138,7 @@ export class TagService { ); return { message: 'Tags deleted successfully', tagUuids }; } catch (error) { - throw this.handleUnexpectedError('Failed to update tags', error); + throw this.handleUnexpectedError('Failed to delete tags', error); } } diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index 0ee6b03..71611bc 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -152,9 +152,10 @@ export class UserSpaceService { async deleteUserSpace(spaceUuid: string) { try { await this.userSpaceRepository - .createQueryBuilder() + .createQueryBuilder('userSpace') + .leftJoin('userSpace.space', 'space') .delete() - .where('spaceUuid = :spaceUuid', { spaceUuid }) + .where('space.uuid = :spaceUuid', { spaceUuid }) .execute(); } catch (error) { console.error(`Error deleting user-space associations: ${error.message}`); From 92089698e48dcfbabba1a5e400a7063c5b7db914 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 7 Jan 2025 02:02:48 -0600 Subject: [PATCH 170/247] Add roleType to inviteUser creation payload --- src/invite-user/services/invite-user.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index c62a364..6ec5194 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -254,6 +254,7 @@ export class InviteUserService { { project: { uuid: invitedUser.project.uuid }, inviteUser: { uuid: invitedUser.uuid }, + roleType: { uuid: invitedUser.roleType.uuid }, }, ); return new SuccessResponseDto({ From 983c49c5033bda1fc495fad42fc63ca50ec40fc6 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 7 Jan 2025 04:19:35 -0600 Subject: [PATCH 171/247] Add roleType relation and debug logs in InviteUserService --- .../services/invite-user.service.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 6ec5194..686fbae 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -208,7 +208,7 @@ export class InviteUserService { status: UserStatusEnum.INVITED, isActive: true, }, - relations: ['project', 'spaces.space.community'], + relations: ['project', 'spaces.space.community', 'roleType'], }); if (!invitedUser) { @@ -245,10 +245,26 @@ export class InviteUserService { continue; } } + console.log( + 'invitedUser', + invitedUser, + { uuid: invitedUser.uuid }, + { status: UserStatusEnum.ACTIVE }, + ); + await this.inviteUserRepository.update( { uuid: invitedUser.uuid }, { status: UserStatusEnum.ACTIVE }, ); + console.log('test', { + project: { uuid: invitedUser.project.uuid }, + }); + console.log('test2', { + inviteUser: { uuid: invitedUser.uuid }, + }); + console.log('test3', { + roleType: { uuid: invitedUser.roleType.uuid }, + }); await this.userRepository.update( { uuid: userUuid }, { @@ -554,6 +570,7 @@ export class InviteUserService { if (!userData) { throw new HttpException('User not found', HttpStatus.NOT_FOUND); } + console.log('userData', userData); if (userData.status === UserStatusEnum.INVITED) { await this.updateUserStatus(invitedUserUuid, projectUuid, disable); @@ -562,6 +579,7 @@ export class InviteUserService { where: { inviteUser: { uuid: invitedUserUuid } }, relations: ['userSpaces.space', 'userSpaces.space.community'], }); + console.log('user', user); if (!user) { throw new HttpException( From ddb0d27d4fd9d1c790f86a1cb7e41cef98b423e7 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 7 Jan 2025 04:20:58 -0600 Subject: [PATCH 172/247] Remove unnecessary console logs from InviteUserService --- src/invite-user/services/invite-user.service.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 686fbae..5b7807b 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -245,26 +245,11 @@ export class InviteUserService { continue; } } - console.log( - 'invitedUser', - invitedUser, - { uuid: invitedUser.uuid }, - { status: UserStatusEnum.ACTIVE }, - ); await this.inviteUserRepository.update( { uuid: invitedUser.uuid }, { status: UserStatusEnum.ACTIVE }, ); - console.log('test', { - project: { uuid: invitedUser.project.uuid }, - }); - console.log('test2', { - inviteUser: { uuid: invitedUser.uuid }, - }); - console.log('test3', { - roleType: { uuid: invitedUser.roleType.uuid }, - }); await this.userRepository.update( { uuid: userUuid }, { @@ -570,7 +555,6 @@ export class InviteUserService { if (!userData) { throw new HttpException('User not found', HttpStatus.NOT_FOUND); } - console.log('userData', userData); if (userData.status === UserStatusEnum.INVITED) { await this.updateUserStatus(invitedUserUuid, projectUuid, disable); @@ -579,7 +563,6 @@ export class InviteUserService { where: { inviteUser: { uuid: invitedUserUuid } }, relations: ['userSpaces.space', 'userSpaces.space.community'], }); - console.log('user', user); if (!user) { throw new HttpException( From 38b6cd1a6295ba1b1de85c88aa8aad1f1278f206 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 8 Jan 2025 00:14:11 -0600 Subject: [PATCH 173/247] Refactor HttpExceptionFilter to unify the error response --- .../http-exception/http-exception.filter.ts | 49 +++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/src/common/filters/http-exception/http-exception.filter.ts b/src/common/filters/http-exception/http-exception.filter.ts index c587769..40f6bb1 100644 --- a/src/common/filters/http-exception/http-exception.filter.ts +++ b/src/common/filters/http-exception/http-exception.filter.ts @@ -5,34 +5,55 @@ import { HttpException, HttpStatus, } from '@nestjs/common'; -import { Response } from 'express'; +import { Request, Response } from 'express'; @Catch() export class HttpExceptionFilter implements ExceptionFilter { - catch(exception: unknown, host: ArgumentsHost) { + catch(exception: unknown, host: ArgumentsHost): void { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); - const status = - exception instanceof HttpException - ? exception.getStatus() - : HttpStatus.INTERNAL_SERVER_ERROR; - const message = - exception instanceof HttpException - ? exception.getResponse() - : 'Internal server error'; + const status = this.getStatus(exception); + const errorMessage = this.getErrorMessage(exception); + const formattedStatus = this.formatStatus(status); const errorResponse = { statusCode: status, timestamp: new Date().toISOString(), path: request.url, - error: message, + error: + typeof errorMessage === 'string' + ? { + message: errorMessage, + error: formattedStatus, + statusCode: status, + } + : errorMessage, }; - // Optionally log the exception - console.error(`Error occurred:`, exception); - + console.error('Error occurred:', exception); response.status(status).json(errorResponse); } + + private getStatus(exception: unknown): HttpStatus { + return exception instanceof HttpException + ? exception.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + } + + private getErrorMessage(exception: unknown): string | object { + return exception instanceof HttpException + ? exception.getResponse() + : 'Internal server error'; + } + + private formatStatus(status: HttpStatus): string { + return HttpStatus[status] + .toLowerCase() + .replace('_', ' ') + .split(' ') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + } } From a8ac2ecae36f18aedbf2bd3aa712283ad8da09fc Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 8 Jan 2025 00:14:21 -0600 Subject: [PATCH 174/247] Refactor and enhance user authentication in AuthService --- libs/common/src/auth/services/auth.service.ts | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index 95d592e..bc25e0e 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -33,32 +33,33 @@ export class AuthService { const user = await this.userRepository.findOne({ where: { email, - region: regionUuid - ? { - uuid: regionUuid, - } - : undefined, + region: regionUuid ? { uuid: regionUuid } : undefined, }, relations: ['roleType'], }); + if (!user) { + throw new BadRequestException('Invalid credentials'); + } + if (!user.isUserVerified) { throw new BadRequestException('User is not verified'); } if (!user.isActive) { throw new BadRequestException('User is not active'); } - if (user) { - const passwordMatch = this.helperHashService.bcryptCompare( - pass, - user.password, - ); - if (passwordMatch) { - const { ...result } = user; - return result; - } + + const passwordMatch = await this.helperHashService.bcryptCompare( + pass, + user.password, + ); + if (!passwordMatch) { + throw new BadRequestException('Invalid credentials'); } - return null; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { password, ...result } = user; + return result; } async createSession(data): Promise { From 0ba681d93bdf9657023ac570fed294a6206211a6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 8 Jan 2025 11:18:28 +0400 Subject: [PATCH 175/247] subspace update fixed disabled --- .../services/space-model.service.ts | 7 +- .../subspace/subspace-model.service.ts | 90 +++++++++---------- src/space-model/services/tag-model.service.ts | 8 +- 3 files changed, 57 insertions(+), 48 deletions(-) diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 723267e..245fffa 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -117,6 +117,12 @@ export class SpaceModelService { pageable.where = { project: { uuid: param.projectUuid }, disabled: false, + subspaceModels: { + disabled: false, + }, + tags: { + disabled: false, + }, }; pageable.include = 'subspaceModels,tags,subspaceModels.tags,tags.product,subspaceModels.tags.product'; @@ -201,7 +207,6 @@ export class SpaceModelService { return new SuccessResponseDto({ message: 'SpaceModel updated successfully', - data: spaceModel, }); } catch (error) { await queryRunner.rollbackTransaction(); diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index 9a52565..9b1c5d2 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -5,7 +5,7 @@ import { } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { CreateSubspaceModelDto, CreateTagModelDto } from '../../dtos'; -import { In, QueryRunner } from 'typeorm'; +import { Not, QueryRunner } from 'typeorm'; import { IDeletedSubsaceModelInterface, ModifySubspaceModelPayload, @@ -123,6 +123,7 @@ export class SubSpaceModelService { }; try { for (const subspace of subspaceDtos) { + console.log(subspace.action); switch (subspace.action) { case ModifyAction.ADD: const subspaceModel = await this.handleAddAction( @@ -258,7 +259,7 @@ export class SubSpaceModelService { private async findOne(subspaceUuid: string): Promise { const subspace = await this.subspaceModelRepository.findOne({ - where: { uuid: subspaceUuid }, + where: { uuid: subspaceUuid, disabled: false }, relations: ['tags', 'spaceModel'], }); if (!subspace) { @@ -299,59 +300,52 @@ export class SubSpaceModelService { } } + private async checkDuplicateNames( + subspaceName: string, + spaceModelUuid: string, + excludeUuid?: string, + ): Promise { + const duplicateSubspace = await this.subspaceModelRepository.findOne({ + where: { + subspaceName, + spaceModel: { + uuid: spaceModelUuid, + }, + ...(excludeUuid && { uuid: Not(excludeUuid) }), + }, + }); + + if (duplicateSubspace) { + throw new HttpException( + `A subspace with the name '${subspaceName}' already exists within the same space model. ${spaceModelUuid}`, + HttpStatus.CONFLICT, + ); + } + } + private async validateName( names: string[], spaceModel: SpaceModelEntity, ): Promise { - try { - const seenNames = new Set(); - const duplicateNames = new Set(); + const seenNames = new Set(); + const duplicateNames = new Set(); - // Check for duplicate names within the input array - for (const name of names) { - if (!seenNames.add(name)) { - duplicateNames.add(name); - } + for (const name of names) { + if (!seenNames.add(name)) { + duplicateNames.add(name); } + } - if (duplicateNames.size > 0) { - throw new HttpException( - `Duplicate subspace model names found: ${[...duplicateNames].join(', ')}`, - HttpStatus.CONFLICT, - ); - } - - // Check for existing names in the database - const existingNames = await this.subspaceModelRepository.find({ - select: ['subspaceName'], - where: { - subspaceName: In([...seenNames]), - spaceModel: { - uuid: spaceModel.uuid, - }, - }, - }); - - if (existingNames.length > 0) { - const existingNamesList = existingNames - .map((e) => e.subspaceName) - .join(', '); - throw new HttpException( - `Subspace model names already exist in the space: ${existingNamesList}`, - HttpStatus.BAD_REQUEST, - ); - } - } catch (error) { - if (error instanceof HttpException) { - throw error; // Rethrow known HttpExceptions - } - - // Handle unexpected errors + if (duplicateNames.size > 0) { throw new HttpException( - `An error occurred while validating subspace model names: ${error.message}`, - HttpStatus.INTERNAL_SERVER_ERROR, + `Duplicate subspace model names found: ${[...duplicateNames].join(', ')}`, + HttpStatus.CONFLICT, ); } + + for (const name of names) { + await this.checkDuplicateNames(name, spaceModel.uuid); + } } private async updateSubspaceName( @@ -360,6 +354,12 @@ export class SubSpaceModelService { subspaceName?: string, ): Promise { if (subspaceName) { + await this.checkDuplicateNames( + subspaceName, + subSpaceModel.spaceModel.uuid, + subSpaceModel.uuid, + ); + subSpaceModel.subspaceName = subspaceName; await queryRunner.manager.save(subSpaceModel); } diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index b251b28..c364816 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -96,7 +96,11 @@ export class TagModelService { async deleteTags(tagUuids: string[], queryRunner: QueryRunner) { try { const deletePromises = tagUuids.map((id) => - queryRunner.manager.softDelete(this.tagModelRepository.target, id), + queryRunner.manager.update( + this.tagModelRepository.target, + { uuid: id }, + { disabled: true }, + ), ); await Promise.all(deletePromises); @@ -276,7 +280,7 @@ export class TagModelService { async getTagByUuid(uuid: string): Promise { const tag = await this.tagModelRepository.findOne({ - where: { uuid }, + where: { uuid, disabled: false }, relations: ['product'], }); if (!tag) { From cdba0f655473968c0b45ecbbddbbf2cfb3b85ae1 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 8 Jan 2025 12:16:20 +0400 Subject: [PATCH 176/247] fix pagination --- libs/common/src/models/typeOrmCustom.model.ts | 52 ++++++++++++------- .../services/space-model.service.ts | 31 +++++++---- 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/libs/common/src/models/typeOrmCustom.model.ts b/libs/common/src/models/typeOrmCustom.model.ts index 2f11102..76054d0 100644 --- a/libs/common/src/models/typeOrmCustom.model.ts +++ b/libs/common/src/models/typeOrmCustom.model.ts @@ -1,4 +1,4 @@ -import { FindManyOptions, Repository } from 'typeorm'; +import { FindManyOptions, Repository, SelectQueryBuilder } from 'typeorm'; import { InternalServerErrorException } from '@nestjs/common'; import { BaseResponseDto } from '../dto/base.response.dto'; import { PageResponseDto } from '../dto/pagination.response.dto'; @@ -70,8 +70,8 @@ export function TypeORMCustomModel(repository: Repository) { return Object.assign(repository, { findAll: async function ( query: Partial, + customQueryBuilder?: SelectQueryBuilder, ): Promise { - // Extract values from the query const { page = 1, size = 10, @@ -82,12 +82,7 @@ export function TypeORMCustomModel(repository: Repository) { select, } = getDefaultQueryOptions(query); - // Ensure modelName is set before proceeding if (!modelName) { - console.error( - 'modelName is missing after getDefaultQueryOptions:', - query, - ); throw new InternalServerErrorException( `[TypeORMCustomModel] Cannot findAll with unknown modelName`, ); @@ -96,19 +91,40 @@ export function TypeORMCustomModel(repository: Repository) { const skip = (page - 1) * size; const order = buildTypeORMSortQuery(sort); const relations = buildTypeORMIncludeQuery(modelName, include); - - // Use the where clause directly, without wrapping it under 'where' const whereClause = buildTypeORMWhereClause({ where }); - // Ensure the whereClause is passed directly to findAndCount - const [data, count] = await repository.findAndCount({ - where: whereClause, - take: size, - skip: skip, - order: order, - select: select, - relations: relations, - }); + let data: any[] = []; + let count = 0; + + if (customQueryBuilder) { + const qb = customQueryBuilder.skip(skip).take(size); + if (order) { + Object.keys(order).forEach((key) => { + qb.addOrderBy(key, order[key]); + }); + } + if (whereClause) { + qb.where(whereClause); + } + if (select) { + const selectColumns = Array.isArray(select) + ? select + : Object.keys(select).map( + (key) => `${customQueryBuilder.alias}.${key}`, + ); + qb.select(selectColumns as string[]); + } + [data, count] = await qb.getManyAndCount(); + } else { + [data, count] = await repository.findAndCount({ + where: whereClause, + take: size, + skip: skip, + order: order, + select: select, + relations: relations, + }); + } const paginationResponseDto = getPaginationResponseDto(count, page, size); const baseResponseDto: BaseResponseDto = { diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 245fffa..7d46eec 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -13,7 +13,6 @@ import { TypeORMCustomModelFindAllQuery, } from '@app/common/models/typeOrmCustom.model'; import { PageResponse } from '@app/common/dto/pagination.response.dto'; -import { SpaceModelDto } from '@app/common/modules/space-model/dtos'; import { SpaceModelParam } from '../dtos/space-model-param'; import { ProjectService } from 'src/project/services'; import { ProjectEntity } from '@app/common/modules/project/entities'; @@ -25,6 +24,7 @@ import { ModifiedTagsModelPayload, ModifySubspaceModelPayload, } from '../interfaces'; +import { SpaceModelDto } from '@app/common/modules/space-model/dtos'; @Injectable() export class SpaceModelService { @@ -117,20 +117,30 @@ export class SpaceModelService { pageable.where = { project: { uuid: param.projectUuid }, disabled: false, - subspaceModels: { - disabled: false, - }, - tags: { - disabled: false, - }, }; pageable.include = 'subspaceModels,tags,subspaceModels.tags,tags.product,subspaceModels.tags.product'; - const customModel = TypeORMCustomModel(this.spaceModelRepository); + const queryBuilder = this.spaceModelRepository + .createQueryBuilder('spaceModel') + .leftJoinAndSelect('spaceModel.subspaceModels', 'subspaceModels') + .leftJoinAndSelect('spaceModel.tags', 'tags') + .leftJoinAndSelect('subspaceModels.tags', 'subspaceModelTags') + .where('spaceModel.disabled = :disabled', { disabled: false }) + .andWhere('spaceModel.project = :projectUuid', { + projectUuid: param.projectUuid, + }); + const customModel = TypeORMCustomModel(this.spaceModelRepository); const { baseResponseDto, paginationResponseDto } = - await customModel.findAll(pageable); + await customModel.findAll( + { + page: pageable.page || 1, + size: pageable.size || 10, + modelName: 'spaceModel', + }, + queryBuilder, + ); return new PageResponse( baseResponseDto, @@ -138,8 +148,7 @@ export class SpaceModelService { ); } catch (error) { throw new HttpException( - error.message || - 'An error occurred while fetching space models in project.', + `Error fetching paginated list: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } From 5e31529f8bfa7fbee610bcf119956cdb7e3793c1 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 8 Jan 2025 12:48:06 +0400 Subject: [PATCH 177/247] added queries --- src/space-model/services/space-model.service.ts | 2 ++ src/space-model/services/subspace/subspace-model.service.ts | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 7d46eec..321e942 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -125,7 +125,9 @@ export class SpaceModelService { .createQueryBuilder('spaceModel') .leftJoinAndSelect('spaceModel.subspaceModels', 'subspaceModels') .leftJoinAndSelect('spaceModel.tags', 'tags') + .leftJoinAndSelect('tags.product', 'spaceTagproduct') .leftJoinAndSelect('subspaceModels.tags', 'subspaceModelTags') + .leftJoinAndSelect('subspaceModelTags.product', 'subspaceTagproduct') .where('spaceModel.disabled = :disabled', { disabled: false }) .andWhere('spaceModel.project = :projectUuid', { projectUuid: param.projectUuid, diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index 9b1c5d2..e045f36 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -123,7 +123,6 @@ export class SubSpaceModelService { }; try { for (const subspace of subspaceDtos) { - console.log(subspace.action); switch (subspace.action) { case ModifyAction.ADD: const subspaceModel = await this.handleAddAction( From 4744b0844c06ee54628c3c6137dbc429cda4cdec Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 8 Jan 2025 21:34:59 +0400 Subject: [PATCH 178/247] get space model by id --- libs/common/src/constants/controller-route.ts | 4 ++++ .../controllers/space-model.controller.ts | 13 +++++++++++++ src/space-model/services/space-model.service.ts | 16 ++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 67a0085..4012c1a 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -292,6 +292,10 @@ export class ControllerRoute { public static readonly CREATE_SPACE_MODEL_DESCRIPTION = 'This endpoint allows you to create a new space model within a specified project. A space model defines the structure of spaces, including subspaces, products, and product items, and is uniquely identifiable within the project.'; + public static readonly GET_SPACE_MODEL_SUMMARY = 'Get a New Space Model'; + public static readonly GET_SPACE_MODEL_DESCRIPTION = + 'Fetch a space model details'; + public static readonly LIST_SPACE_MODEL_SUMMARY = 'List Space Models'; public static readonly LIST_SPACE_MODEL_DESCRIPTION = 'This endpoint allows you to retrieve a list of space models within a specified project. Each space model includes its structure, associated subspaces, products, and product items.'; diff --git a/src/space-model/controllers/space-model.controller.ts b/src/space-model/controllers/space-model.controller.ts index 5708e3f..57e2db6 100644 --- a/src/space-model/controllers/space-model.controller.ts +++ b/src/space-model/controllers/space-model.controller.ts @@ -66,6 +66,19 @@ export class SpaceModelController { return await this.spaceModelService.list(projectParam, query); } + @ApiBearerAuth() + @UseGuards(PermissionsGuard) + @Permissions('SPACE_MODEL_VIEW') + @ApiOperation({ + summary: ControllerRoute.SPACE_MODEL.ACTIONS.LIST_SPACE_MODEL_SUMMARY, + description: + ControllerRoute.SPACE_MODEL.ACTIONS.LIST_SPACE_MODEL_DESCRIPTION, + }) + @Get(':spaceModelUuid') + async get(@Param() param: SpaceModelParam): Promise { + return await this.spaceModelService.findOne(param); + } + @ApiBearerAuth() @UseGuards(PermissionsGuard) @Permissions('SPACE_MODEL_UPDATE') diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 321e942..c7ad97a 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -323,6 +323,22 @@ export class SpaceModelService { } } + async findOne(params: SpaceModelParam): Promise { + try { + await this.validateProject(params.projectUuid); + const spaceModel = await this.validateSpaceModel(params.spaceModelUuid); + return new SuccessResponseDto({ + message: 'SpaceModel retrieved successfully', + data: spaceModel, + }); + } catch (error) { + throw new HttpException( + `Failed to retrieve space model ${error}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async validateSpaceModel(uuid: string): Promise { const spaceModel = await this.spaceModelRepository.findOne({ where: { From 0145f72651d66b14fce33e9606e2717d52a1e389 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 9 Jan 2025 05:37:40 -0600 Subject: [PATCH 179/247] Add InviteSpaceEntity and InviteSpaceRepository --- libs/common/src/database/database.module.ts | 2 + .../space/entities/invite-space.entity.ts | 39 +++++++++++++++++++ .../modules/space/entities/space.entity.ts | 14 +------ .../space/repositories/space.repository.ts | 8 ++++ .../modules/space/space.repository.module.ts | 10 ++++- src/space-model/space-model.module.ts | 2 + src/space/space.module.ts | 2 + 7 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 libs/common/src/modules/space/entities/invite-space.entity.ts diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index 472772b..cd83fa0 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -36,6 +36,7 @@ import { InviteUserEntity, InviteUserSpaceEntity, } from '../modules/Invite-user/entities'; +import { InviteSpaceEntity } from '../modules/space/entities/invite-space.entity'; @Module({ imports: [ TypeOrmModule.forRootAsync({ @@ -80,6 +81,7 @@ import { TagModel, InviteUserEntity, InviteUserSpaceEntity, + InviteSpaceEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), diff --git a/libs/common/src/modules/space/entities/invite-space.entity.ts b/libs/common/src/modules/space/entities/invite-space.entity.ts new file mode 100644 index 0000000..38e24a2 --- /dev/null +++ b/libs/common/src/modules/space/entities/invite-space.entity.ts @@ -0,0 +1,39 @@ +import { Column, Entity, ManyToOne, Unique } from 'typeorm'; +import { UserSpaceDto } from '../../user/dtos'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { UserEntity } from '../../user/entities'; +import { SpaceEntity } from './space.entity'; + +@Entity({ name: 'invite-space' }) +@Unique(['invitationCode']) +export class InviteSpaceEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', // Use gen_random_uuid() for default value + nullable: false, + }) + public uuid: string; + + @ManyToOne(() => UserEntity, (user) => user.userSpaces, { nullable: true }) + user: UserEntity; + + @ManyToOne(() => SpaceEntity, (space) => space.userSpaces, { + nullable: false, + }) + space: SpaceEntity; + + @Column({ + nullable: true, + }) + public invitationCode: string; + + @Column({ + nullable: false, + default: true, + }) + public isActive: boolean; + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/space/entities/space.entity.ts b/libs/common/src/modules/space/entities/space.entity.ts index 8ecbc70..a0b908d 100644 --- a/libs/common/src/modules/space/entities/space.entity.ts +++ b/libs/common/src/modules/space/entities/space.entity.ts @@ -1,11 +1,4 @@ -import { - Column, - Entity, - JoinColumn, - ManyToOne, - OneToMany, - Unique, -} from 'typeorm'; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; import { SpaceDto } from '../dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { UserSpaceEntity } from '../../user/entities'; @@ -19,7 +12,6 @@ import { InviteUserSpaceEntity } from '../../Invite-user/entities'; import { TagEntity } from './tag.entity'; @Entity({ name: 'space' }) -@Unique(['invitationCode']) export class SpaceEntity extends AbstractEntity { @Column({ type: 'uuid', @@ -44,10 +36,6 @@ export class SpaceEntity extends AbstractEntity { @JoinColumn({ name: 'community_id' }) community: CommunityEntity; - @Column({ - nullable: true, - }) - public invitationCode: string; @ManyToOne(() => SpaceEntity, (space) => space.children, { nullable: true }) parent: SpaceEntity; diff --git a/libs/common/src/modules/space/repositories/space.repository.ts b/libs/common/src/modules/space/repositories/space.repository.ts index b2aacc0..9bc2fee 100644 --- a/libs/common/src/modules/space/repositories/space.repository.ts +++ b/libs/common/src/modules/space/repositories/space.repository.ts @@ -1,6 +1,7 @@ import { DataSource, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; import { SpaceEntity, SpaceLinkEntity, TagEntity } from '../entities'; +import { InviteSpaceEntity } from '../entities/invite-space.entity'; @Injectable() export class SpaceRepository extends Repository { @@ -22,3 +23,10 @@ export class TagRepository extends Repository { super(TagEntity, dataSource.createEntityManager()); } } + +@Injectable() +export class InviteSpaceRepository extends Repository { + constructor(private dataSource: DataSource) { + super(InviteSpaceEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/modules/space/space.repository.module.ts b/libs/common/src/modules/space/space.repository.module.ts index 030c684..a475a9a 100644 --- a/libs/common/src/modules/space/space.repository.module.ts +++ b/libs/common/src/modules/space/space.repository.module.ts @@ -1,11 +1,19 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { SpaceEntity, SubspaceEntity, TagEntity } from './entities'; +import { InviteSpaceEntity } from './entities/invite-space.entity'; @Module({ providers: [], exports: [], controllers: [], - imports: [TypeOrmModule.forFeature([SpaceEntity, SubspaceEntity, TagEntity])], + imports: [ + TypeOrmModule.forFeature([ + SpaceEntity, + SubspaceEntity, + TagEntity, + InviteSpaceEntity, + ]), + ], }) export class SpaceRepositoryModule {} diff --git a/src/space-model/space-model.module.ts b/src/space-model/space-model.module.ts index 7f72860..c00a00d 100644 --- a/src/space-model/space-model.module.ts +++ b/src/space-model/space-model.module.ts @@ -20,6 +20,7 @@ import { } from './handlers'; import { CqrsModule } from '@nestjs/cqrs'; import { + InviteSpaceRepository, SpaceLinkRepository, SpaceRepository, TagRepository, @@ -70,6 +71,7 @@ const CommandHandlers = [ CommunityRepository, SpaceLinkService, SpaceLinkRepository, + InviteSpaceRepository, ], exports: [CqrsModule, SpaceModelService], }) diff --git a/src/space/space.module.ts b/src/space/space.module.ts index f9a2317..b92e342 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -22,6 +22,7 @@ import { SpaceRepository, SpaceLinkRepository, TagRepository, + InviteSpaceRepository, } from '@app/common/modules/space/repositories'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { @@ -114,6 +115,7 @@ export const CommandHandlers = [DisableSpaceHandler]; UserDevicePermissionService, DeviceUserPermissionRepository, PermissionTypeRepository, + InviteSpaceRepository, ...CommandHandlers, ], exports: [SpaceService], From 5cd356443491b856c991fe7a0a731dc24a2385b2 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 9 Jan 2025 05:38:00 -0600 Subject: [PATCH 180/247] Change HTTP method for generating space invitation code --- src/space/controllers/space.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/space/controllers/space.controller.ts b/src/space/controllers/space.controller.ts index f54a3ea..ee8e5d2 100644 --- a/src/space/controllers/space.controller.ts +++ b/src/space/controllers/space.controller.ts @@ -121,7 +121,7 @@ export class SpaceController { description: ControllerRoute.SPACE.ACTIONS.CREATE_INVITATION_CODE_SPACE_DESCRIPTION, }) - @Get(':spaceUuid/invitation-code') + @Post(':spaceUuid/invitation-code') async generateSpaceInvitationCode( @Param() params: GetSpaceParam, ): Promise { From 8eb1fd472ac4d11c2f92447ee42f7fac1b165c04 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 9 Jan 2025 05:38:43 -0600 Subject: [PATCH 181/247] Add InviteSpaceRepository and update invitation code handling --- src/space/controllers/space.controller.ts | 1 - src/space/services/space.service.ts | 19 ++++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/space/controllers/space.controller.ts b/src/space/controllers/space.controller.ts index ee8e5d2..5a577b2 100644 --- a/src/space/controllers/space.controller.ts +++ b/src/space/controllers/space.controller.ts @@ -112,7 +112,6 @@ export class SpaceController { return this.spaceService.getSpacesHierarchyForSpace(params); } - //should it be post? @ApiBearerAuth() @UseGuards(PermissionsGuard) @Permissions('SPACE_MEMBER_ADD') diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index d0394c6..fb375d9 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -1,4 +1,7 @@ -import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { + InviteSpaceRepository, + SpaceRepository, +} from '@app/common/modules/space/repositories'; import { BadRequestException, HttpException, @@ -34,6 +37,7 @@ export class SpaceService { constructor( private readonly dataSource: DataSource, private readonly spaceRepository: SpaceRepository, + private readonly inviteSpaceRepository: InviteSpaceRepository, private readonly spaceLinkService: SpaceLinkService, private readonly subSpaceService: SubSpaceService, private readonly validationService: ValidationService, @@ -448,13 +452,18 @@ export class SpaceService { projectUuid, spaceUuid, ); - - space.invitationCode = invitationCode; - await this.spaceRepository.save(space); + await this.inviteSpaceRepository.save({ + space: { uuid: spaceUuid }, + invitationCode, + }); return new SuccessResponseDto({ message: `Invitation code has been successfuly added to the space`, - data: space, + data: { + invitationCode, + spaceName: space.spaceName, + spaceUuid: space.uuid, + }, }); } catch (err) { if (err instanceof BadRequestException) { From ec467d124eb997980f06d32cd0355c1b4aea2244 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 9 Jan 2025 05:53:08 -0600 Subject: [PATCH 182/247] Remove unused UserEntity import and relationship in InviteSpaceEntity --- libs/common/src/modules/space/entities/invite-space.entity.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libs/common/src/modules/space/entities/invite-space.entity.ts b/libs/common/src/modules/space/entities/invite-space.entity.ts index 38e24a2..72737ca 100644 --- a/libs/common/src/modules/space/entities/invite-space.entity.ts +++ b/libs/common/src/modules/space/entities/invite-space.entity.ts @@ -1,7 +1,6 @@ import { Column, Entity, ManyToOne, Unique } from 'typeorm'; import { UserSpaceDto } from '../../user/dtos'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; -import { UserEntity } from '../../user/entities'; import { SpaceEntity } from './space.entity'; @Entity({ name: 'invite-space' }) @@ -14,9 +13,6 @@ export class InviteSpaceEntity extends AbstractEntity { }) public uuid: string; - @ManyToOne(() => UserEntity, (user) => user.userSpaces, { nullable: true }) - user: UserEntity; - @ManyToOne(() => SpaceEntity, (space) => space.userSpaces, { nullable: false, }) From 45181704a7799ca846b16c451bc1c0a73c48977d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 9 Jan 2025 05:53:43 -0600 Subject: [PATCH 183/247] Add InviteSpaceRepository and update invite handling logic --- src/invite-user/invite-user.module.ts | 6 ++- src/users/services/user-space.service.ts | 47 ++++++++++++++---------- src/users/user.module.ts | 6 ++- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index fc0b579..ad78d63 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -15,7 +15,10 @@ import { import { EmailService } from '@app/common/util/email.service'; import { SpaceUserService, ValidationService } from 'src/space/services'; import { CommunityService } from 'src/community/services'; -import { SpaceRepository } from '@app/common/modules/space'; +import { + InviteSpaceRepository, + SpaceRepository, +} from '@app/common/modules/space'; import { SpaceModelRepository } from '@app/common/modules/space-model'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; @@ -51,6 +54,7 @@ import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; PermissionTypeRepository, ProjectUserService, RoleTypeRepository, + InviteSpaceRepository, ], exports: [InviteUserService], }) diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index 71611bc..58a66b4 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -3,17 +3,21 @@ import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { AddUserSpaceDto, AddUserSpaceUsingCodeDto } from '../dtos'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { + InviteSpaceRepository, + SpaceRepository, +} from '@app/common/modules/space/repositories'; import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; import { UserDevicePermissionService } from 'src/user-device-permission/services'; import { PermissionType } from '@app/common/constants/permission-type.enum'; -import { SpaceEntity } from '@app/common/modules/space/entities'; +import { InviteSpaceEntity } from '@app/common/modules/space/entities/invite-space.entity'; @Injectable() export class UserSpaceService { constructor( private readonly userSpaceRepository: UserSpaceRepository, private readonly spaceRepository: SpaceRepository, + private readonly inviteSpaceRepository: InviteSpaceRepository, private readonly userDevicePermissionService: UserDevicePermissionService, ) {} @@ -38,15 +42,17 @@ export class UserSpaceService { userUuid: string, ) { try { - const space = await this.findSpaceByInviteCode(params.inviteCode); + const inviteSpace = await this.findInviteSpaceByInviteCode( + params.inviteCode, + ); - await this.addUserToSpace(userUuid, space.uuid); + await this.addUserToSpace(userUuid, inviteSpace.space.uuid); - await this.clearUnitInvitationCode(space.uuid); - - const deviceUUIDs = await this.getDeviceUUIDsForSpace(space.uuid); + const deviceUUIDs = await this.getDeviceUUIDsForSpace(inviteSpace.uuid); await this.addUserPermissionsToDevices(userUuid, deviceUUIDs); + + await this.clearSpaceInvitationCode(inviteSpace.uuid); } catch (err) { if (err instanceof HttpException) { throw err; @@ -59,23 +65,31 @@ export class UserSpaceService { } } - private async findSpaceByInviteCode( + private async findInviteSpaceByInviteCode( inviteCode: string, - ): Promise { + ): Promise { try { - const space = await this.spaceRepository.findOneOrFail({ + const inviteSpace = await this.inviteSpaceRepository.findOneOrFail({ where: { invitationCode: inviteCode, + isActive: true, }, + relations: ['space'], }); - return space; + return inviteSpace; } catch (error) { throw new HttpException( - 'Space with the provided invite code not found', - HttpStatus.NOT_FOUND, + 'Invalid invitation code', + HttpStatus.BAD_REQUEST, ); } } + private async clearSpaceInvitationCode(inviteSpaceUuid: string) { + await this.inviteSpaceRepository.update( + { uuid: inviteSpaceUuid }, + { isActive: false }, + ); + } private async addUserToSpace(userUuid: string, spaceUuid: string) { try { @@ -110,13 +124,6 @@ export class UserSpaceService { } } - private async clearUnitInvitationCode(spaceUuid: string) { - await this.spaceRepository.update( - { uuid: spaceUuid }, - { invitationCode: null }, - ); - } - async getDeviceUUIDsForSpace(unitUuid: string): Promise<{ uuid: string }[]> { const devices = await this.spaceRepository.find({ where: { uuid: unitUuid }, diff --git a/src/users/user.module.ts b/src/users/user.module.ts index 20bd06f..46e3903 100644 --- a/src/users/user.module.ts +++ b/src/users/user.module.ts @@ -12,7 +12,10 @@ import { UserSpaceController } from './controllers'; import { CommunityModule } from 'src/community/community.module'; import { UserSpaceService } from './services'; import { CommunityRepository } from '@app/common/modules/community/repositories'; -import { SpaceRepository } from '@app/common/modules/space/repositories'; +import { + InviteSpaceRepository, + SpaceRepository, +} from '@app/common/modules/space/repositories'; import { UserDevicePermissionService } from 'src/user-device-permission/services'; import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; @@ -32,6 +35,7 @@ import { PermissionTypeRepository } from '@app/common/modules/permission/reposit DeviceUserPermissionRepository, PermissionTypeRepository, UserSpaceService, + InviteSpaceRepository, ], exports: [UserService], }) From 59a191e5799b5e1f2748b0bd7b7475988fd9a135 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Fri, 10 Jan 2025 06:14:22 -0600 Subject: [PATCH 184/247] Add 'SPACE_MEMBER_ADD' permission to VISITOR and ADMIN roles --- libs/common/src/constants/role-permissions.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/common/src/constants/role-permissions.ts b/libs/common/src/constants/role-permissions.ts index c457c3c..8d3d0ed 100644 --- a/libs/common/src/constants/role-permissions.ts +++ b/libs/common/src/constants/role-permissions.ts @@ -46,6 +46,7 @@ export const RolePermissions = { 'VISITOR_PASSWORD_VIEW', 'VISITOR_PASSWORD_ADD', 'USER_ADD', + 'SPACE_MEMBER_ADD', ], [RoleType.ADMIN]: [ 'DEVICE_SINGLE_CONTROL', @@ -92,6 +93,7 @@ export const RolePermissions = { 'VISITOR_PASSWORD_VIEW', 'VISITOR_PASSWORD_ADD', 'USER_ADD', + 'SPACE_MEMBER_ADD', ], [RoleType.SPACE_MEMBER]: [ 'DEVICE_SINGLE_CONTROL', From 145ef3262938d3fd78d5fd31f97d3053f0e0f979 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Fri, 10 Jan 2025 06:14:34 -0600 Subject: [PATCH 185/247] Update InviteUserEntity to ensure unique combination of email, invitationCode, and project --- .../src/modules/Invite-user/entities/Invite-user.entity.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts index 22bbbdb..345c1b8 100644 --- a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts +++ b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts @@ -18,7 +18,7 @@ import { InviteUserDto, InviteUserSpaceDto } from '../dtos'; import { ProjectEntity } from '../../project/entities'; @Entity({ name: 'invite-user' }) -@Unique(['email', 'invitationCode']) +@Unique(['email', 'invitationCode', 'project']) export class InviteUserEntity extends AbstractEntity { @Column({ type: 'uuid', @@ -29,7 +29,6 @@ export class InviteUserEntity extends AbstractEntity { @Column({ nullable: false, - unique: true, }) email: string; From 56fba355fcd7a74f9ac2ec284df442b364228342 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Fri, 10 Jan 2025 06:14:45 -0600 Subject: [PATCH 186/247] Refactor PermissionsGuard to throw UnauthorizedException with detailed message --- src/guards/permissions.guard.ts | 34 ++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/guards/permissions.guard.ts b/src/guards/permissions.guard.ts index ac5881e..d50ff24 100644 --- a/src/guards/permissions.guard.ts +++ b/src/guards/permissions.guard.ts @@ -1,4 +1,8 @@ -import { Injectable, ExecutionContext } from '@nestjs/common'; +import { + Injectable, + ExecutionContext, + UnauthorizedException, +} from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { Reflector } from '@nestjs/core'; import { RolePermissions } from '@app/common/constants/role-permissions'; @@ -30,14 +34,34 @@ export class PermissionsGuard extends AuthGuard('jwt') { const request = context.switchToHttp().getRequest(); const user = request.user; // User is now available after AuthGuard - const userRole = user?.role.type as RoleType; + const userRole = user?.role?.type as RoleType; if (!userRole || !RolePermissions[userRole]) { - return false; // Deny if role or permissions are missing + throw new UnauthorizedException({ + message: `Only ${this.getAllowedRoles(requiredPermissions)} role(s) can access this route.`, + }); } const userPermissions = RolePermissions[userRole]; + const hasRequiredPermissions = requiredPermissions.every((perm) => + userPermissions.includes(perm), + ); - // Check if the user has the required permissions - return requiredPermissions.every((perm) => userPermissions.includes(perm)); + if (!hasRequiredPermissions) { + throw new UnauthorizedException({ + message: `Only ${this.getAllowedRoles(requiredPermissions)} role(s) can access this route.`, + }); + } + + return true; + } + + private getAllowedRoles(requiredPermissions: string[]): string { + const allowedRoles = Object.entries(RolePermissions) + .filter(([, permissions]) => + requiredPermissions.every((perm) => permissions.includes(perm)), + ) + .map(([role]) => role); + + return allowedRoles.join(', '); } } From 79b3b14a9cb40790b42e6e762996994626addab0 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Fri, 10 Jan 2025 06:15:41 -0600 Subject: [PATCH 187/247] Rename `getSpaceInvitationCode` to `generateSpaceInvitationCode` and update imports --- src/space/controllers/space.controller.ts | 2 +- src/space/services/space.service.ts | 2 +- src/space/space.module.ts | 9 ++++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/space/controllers/space.controller.ts b/src/space/controllers/space.controller.ts index 5a577b2..bb18516 100644 --- a/src/space/controllers/space.controller.ts +++ b/src/space/controllers/space.controller.ts @@ -124,6 +124,6 @@ export class SpaceController { async generateSpaceInvitationCode( @Param() params: GetSpaceParam, ): Promise { - return this.spaceService.getSpaceInvitationCode(params); + return this.spaceService.generateSpaceInvitationCode(params); } } diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index fb375d9..87700e4 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -441,7 +441,7 @@ export class SpaceService { } } - async getSpaceInvitationCode(params: GetSpaceParam): Promise { + async generateSpaceInvitationCode(params: GetSpaceParam): Promise { const { communityUuid, spaceUuid, projectUuid } = params; try { const invitationCode = generateRandomString(6); diff --git a/src/space/space.module.ts b/src/space/space.module.ts index b92e342..e4bddfc 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -59,11 +59,14 @@ import { SubSpaceModelService, TagModelService, } from 'src/space-model/services'; -import { UserSpaceService } from 'src/users/services'; +import { UserService, UserSpaceService } from 'src/users/services'; import { UserDevicePermissionService } from 'src/user-device-permission/services'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; import { CqrsModule } from '@nestjs/cqrs'; import { DisableSpaceHandler } from './handlers'; +import { RegionRepository } from '@app/common/modules/region/repositories'; +import { TimeZoneRepository } from '@app/common/modules/timezone/repositories'; +import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; export const CommandHandlers = [DisableSpaceHandler]; @@ -117,6 +120,10 @@ export const CommandHandlers = [DisableSpaceHandler]; PermissionTypeRepository, InviteSpaceRepository, ...CommandHandlers, + UserService, + RegionRepository, + TimeZoneRepository, + InviteUserRepository, ], exports: [SpaceService], }) From bdbab026f52a5d2d80e58ebe2f812a51fa2b294d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Fri, 10 Jan 2025 06:18:48 -0600 Subject: [PATCH 188/247] Refactor activation code handling and user space verification --- .../controllers/invite-user.controller.ts | 4 +- src/invite-user/invite-user.module.ts | 7 +- .../services/invite-user.service.ts | 143 +++++++++++------- .../controllers/user-space.controller.ts | 45 +----- src/users/services/user-space.service.ts | 83 +++++++++- src/users/services/user.service.ts | 19 ++- src/users/user.module.ts | 2 + 7 files changed, 184 insertions(+), 119 deletions(-) diff --git a/src/invite-user/controllers/invite-user.controller.ts b/src/invite-user/controllers/invite-user.controller.ts index 8f65993..1a5ac35 100644 --- a/src/invite-user/controllers/invite-user.controller.ts +++ b/src/invite-user/controllers/invite-user.controller.ts @@ -76,9 +76,7 @@ export class InviteUserController { async activationCodeController( @Body() activateCodeDto: ActivateCodeDto, ): Promise { - return await this.inviteUserService.activationCodeController( - activateCodeDto, - ); + return await this.inviteUserService.activationCode(activateCodeDto); } @ApiBearerAuth() @UseGuards(JwtAuthGuard) diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index ad78d63..6b77d1d 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -23,12 +23,14 @@ import { SpaceModelRepository } from '@app/common/modules/space-model'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; -import { UserSpaceService } from 'src/users/services'; +import { UserService, UserSpaceService } from 'src/users/services'; import { UserDevicePermissionService } from 'src/user-device-permission/services'; import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; import { ProjectUserService } from 'src/project/services/project-user.service'; import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; +import { RegionRepository } from '@app/common/modules/region/repositories'; +import { TimeZoneRepository } from '@app/common/modules/timezone/repositories'; @Module({ imports: [ConfigModule, InviteUserRepositoryModule], @@ -55,6 +57,9 @@ import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; ProjectUserService, RoleTypeRepository, InviteSpaceRepository, + UserService, + RegionRepository, + TimeZoneRepository, ], exports: [InviteUserService], }) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 5b7807b..8d63959 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -29,6 +29,7 @@ import { UpdateUserInvitationDto, } from '../dtos/update.invite-user.dto'; import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; +import { InviteUserEntity } from '@app/common/modules/Invite-user/entities'; @Injectable() export class InviteUserService { @@ -188,76 +189,35 @@ export class InviteUserService { ); } } - async activationCodeController( - dto: ActivateCodeDto, - ): Promise { - try { - const { activationCode, userUuid } = dto; - const user = await this.userRepository.findOne({ - where: { uuid: userUuid, isActive: true, isUserVerified: true }, - }); + async activationCode(dto: ActivateCodeDto): Promise { + const { activationCode, userUuid } = dto; + + try { + const user = await this.getUser(userUuid); - if (!user) { - throw new HttpException('User not found', HttpStatus.NOT_FOUND); - } - const { email } = user; const invitedUser = await this.inviteUserRepository.findOne({ where: { - email, - invitationCode: activationCode, + email: user.email, status: UserStatusEnum.INVITED, isActive: true, }, relations: ['project', 'spaces.space.community', 'roleType'], }); - if (!invitedUser) { - throw new HttpException( - 'Invalid activation code', - HttpStatus.BAD_REQUEST, - ); - } - - for (const invitedSpace of invitedUser.spaces) { - try { - const deviceUUIDs = - await this.userSpaceService.getDeviceUUIDsForSpace( - invitedSpace.space.uuid, - ); - - await this.userSpaceService.addUserPermissionsToDevices( - userUuid, - deviceUUIDs, + if (invitedUser) { + if (invitedUser.invitationCode !== activationCode) { + throw new HttpException( + 'Invalid activation code', + HttpStatus.BAD_REQUEST, ); - - await this.spaceUserService.associateUserToSpace({ - communityUuid: invitedSpace.space.community.uuid, - spaceUuid: invitedSpace.space.uuid, - userUuid: user.uuid, - projectUuid: invitedUser.project.uuid, - }); - } catch (spaceError) { - console.error( - `Error processing space ${invitedSpace.space.uuid}:`, - spaceError, - ); - // Skip to the next space - continue; } - } - await this.inviteUserRepository.update( - { uuid: invitedUser.uuid }, - { status: UserStatusEnum.ACTIVE }, - ); - await this.userRepository.update( - { uuid: userUuid }, - { - project: { uuid: invitedUser.project.uuid }, - inviteUser: { uuid: invitedUser.uuid }, - roleType: { uuid: invitedUser.roleType.uuid }, - }, - ); + // Handle invited user with valid activation code + await this.handleInvitedUser(user, invitedUser); + } else { + // Handle case for non-invited user + await this.handleNonInvitedUser(activationCode, userUuid); + } return new SuccessResponseDto({ statusCode: HttpStatus.OK, success: true, @@ -272,6 +232,73 @@ export class InviteUserService { ); } } + + private async getUser(userUuid: string): Promise { + const user = await this.userRepository.findOne({ + where: { uuid: userUuid, isActive: true, isUserVerified: true }, + }); + + if (!user) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND); + } + return user; + } + + private async handleNonInvitedUser( + activationCode: string, + userUuid: string, + ): Promise { + await this.userSpaceService.verifyCodeAndAddUserSpace( + { inviteCode: activationCode }, + userUuid, + ); + } + + private async handleInvitedUser( + user: UserEntity, + invitedUser: InviteUserEntity, + ): Promise { + for (const invitedSpace of invitedUser.spaces) { + try { + const deviceUUIDs = await this.userSpaceService.getDeviceUUIDsForSpace( + invitedSpace.space.uuid, + ); + + await this.userSpaceService.addUserPermissionsToDevices( + user.uuid, + deviceUUIDs, + ); + + await this.spaceUserService.associateUserToSpace({ + communityUuid: invitedSpace.space.community.uuid, + spaceUuid: invitedSpace.space.uuid, + userUuid: user.uuid, + projectUuid: invitedUser.project.uuid, + }); + } catch (spaceError) { + console.error( + `Error processing space ${invitedSpace.space.uuid}:`, + spaceError, + ); + continue; // Skip to the next space + } + } + + // Update invited user and associated user data + await this.inviteUserRepository.update( + { uuid: invitedUser.uuid }, + { status: UserStatusEnum.ACTIVE }, + ); + await this.userRepository.update( + { uuid: user.uuid }, + { + project: { uuid: invitedUser.project.uuid }, + inviteUser: { uuid: invitedUser.uuid }, + roleType: { uuid: invitedUser.roleType.uuid }, + }, + ); + } + async updateUserInvitation( dto: UpdateUserInvitationDto, invitedUserUuid: string, diff --git a/src/users/controllers/user-space.controller.ts b/src/users/controllers/user-space.controller.ts index aed945a..519a033 100644 --- a/src/users/controllers/user-space.controller.ts +++ b/src/users/controllers/user-space.controller.ts @@ -1,20 +1,11 @@ import { ControllerRoute } from '@app/common/constants/controller-route'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; -import { - Body, - Controller, - Get, - HttpException, - HttpStatus, - Param, - Post, - UseGuards, -} from '@nestjs/common'; +import { Controller, Get, Param, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { UserSpaceService } from '../services'; import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; -import { AddUserSpaceUsingCodeDto, UserParamDto } from '../dtos'; +import { UserParamDto } from '../dtos'; @ApiTags('User Module') @Controller({ @@ -36,36 +27,4 @@ export class UserSpaceController { ): Promise { return this.userSpaceService.getSpacesForUser(params.userUuid); } - - @ApiBearerAuth() - @UseGuards(JwtAuthGuard) - @Post('/verify-code') - @ApiOperation({ - summary: - ControllerRoute.USER_SPACE.ACTIONS.VERIFY_CODE_AND_ADD_USER_SPACE_SUMMARY, - description: - ControllerRoute.USER_SPACE.ACTIONS - .VERIFY_CODE_AND_ADD_USER_SPACE_DESCRIPTION, - }) - async verifyCodeAndAddUserSpace( - @Body() dto: AddUserSpaceUsingCodeDto, - @Param() params: UserParamDto, - ) { - try { - await this.userSpaceService.verifyCodeAndAddUserSpace( - dto, - params.userUuid, - ); - return { - statusCode: HttpStatus.CREATED, - success: true, - message: 'user space added successfully', - }; - } catch (error) { - throw new HttpException( - error.message || 'Internal server error', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); - } - } } diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index 58a66b4..074dcf1 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -1,4 +1,9 @@ -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { + BadRequestException, + HttpException, + HttpStatus, + Injectable, +} from '@nestjs/common'; import { UserSpaceRepository } from '@app/common/modules/user/repositories'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; @@ -11,6 +16,10 @@ import { CommonErrorCodes } from '@app/common/constants/error-codes.enum'; import { UserDevicePermissionService } from 'src/user-device-permission/services'; import { PermissionType } from '@app/common/constants/permission-type.enum'; import { InviteSpaceEntity } from '@app/common/modules/space/entities/invite-space.entity'; +import { UserService } from './user.service'; +import { RoleType } from '@app/common/constants/role.type.enum'; +import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; +import { UserStatusEnum } from '@app/common/constants/user-status.enum'; @Injectable() export class UserSpaceService { @@ -18,6 +27,8 @@ export class UserSpaceService { private readonly userSpaceRepository: UserSpaceRepository, private readonly spaceRepository: SpaceRepository, private readonly inviteSpaceRepository: InviteSpaceRepository, + private readonly userService: UserService, + private readonly inviteUserRepository: InviteUserRepository, private readonly userDevicePermissionService: UserDevicePermissionService, ) {} @@ -41,17 +52,24 @@ export class UserSpaceService { params: AddUserSpaceUsingCodeDto, userUuid: string, ) { + const { inviteCode } = params; try { - const inviteSpace = await this.findInviteSpaceByInviteCode( - params.inviteCode, + const inviteSpace = await this.findInviteSpaceByInviteCode(inviteCode); + const user = await this.userService.getUserDetailsByUserUuid( + userUuid, + true, ); - + await this.checkSpaceMemberRole(user); await this.addUserToSpace(userUuid, inviteSpace.space.uuid); const deviceUUIDs = await this.getDeviceUUIDsForSpace(inviteSpace.uuid); await this.addUserPermissionsToDevices(userUuid, deviceUUIDs); - + await this.addUserAsActiveInvitation( + user, + inviteSpace.space.uuid, + inviteCode, + ); await this.clearSpaceInvitationCode(inviteSpace.uuid); } catch (err) { if (err instanceof HttpException) { @@ -64,7 +82,20 @@ export class UserSpaceService { } } } - + private async checkSpaceMemberRole(user: any) { + try { + if (user.role.type !== RoleType.SPACE_MEMBER) { + throw new BadRequestException( + 'You have to be a space member to join this space', + ); + } + } catch (err) { + throw new HttpException( + err.message || 'User not found', + err.status || HttpStatus.NOT_FOUND, + ); + } + } private async findInviteSpaceByInviteCode( inviteCode: string, ): Promise { @@ -90,7 +121,47 @@ export class UserSpaceService { { isActive: false }, ); } + async getProjectBySpaceUuid(spaceUuid: string) { + try { + const project = await this.spaceRepository.findOne({ + where: { + uuid: spaceUuid, + }, + relations: ['community.project'], + }); + return project; + } catch (error) { + throw new HttpException('Space not found', HttpStatus.NOT_FOUND); + } + } + private async addUserAsActiveInvitation( + user: any, + spaceUuid: string, + inviteCode: string, + ) { + try { + const space = await this.getProjectBySpaceUuid(spaceUuid); + const inviteUser = this.inviteUserRepository.create({ + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + jobTitle: null, + phoneNumber: null, + roleType: { uuid: user.role.uuid }, + status: UserStatusEnum.ACTIVE, + invitationCode: inviteCode, + invitedBy: RoleType.SPACE_OWNER, + project: { uuid: space.community.project.uuid }, + }); + await this.inviteUserRepository.save(inviteUser); + } catch (err) { + throw new HttpException( + err.message || 'Internal Server Error', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } private async addUserToSpace(userUuid: string, spaceUuid: string) { try { const user = await this.addUserSpace({ userUuid, spaceUuid }); diff --git a/src/users/services/user.service.ts b/src/users/services/user.service.ts index 80dad24..96a669b 100644 --- a/src/users/services/user.service.ts +++ b/src/users/services/user.service.ts @@ -23,13 +23,15 @@ export class UserService { private readonly regionRepository: RegionRepository, private readonly timeZoneRepository: TimeZoneRepository, ) {} - async getUserDetailsByUserUuid(userUuid: string) { + async getUserDetailsByUserUuid(userUuid: string, withRole = false) { try { const user = await this.userRepository.findOne({ where: { uuid: userUuid, }, - relations: ['region', 'timezone'], + ...(withRole + ? { relations: ['roleType'] } + : { relations: ['region', 'timezone'] }), }); if (!user) { throw new BadRequestException('Invalid room UUID'); @@ -39,13 +41,14 @@ export class UserService { const cleanedProfilePicture = removeBase64Prefix(user.profilePicture); return { - uuid: user.uuid, - email: user.email, - firstName: user.firstName, - lastName: user.lastName, + uuid: user?.uuid, + email: user?.email, + firstName: user?.firstName, + lastName: user?.lastName, profilePicture: cleanedProfilePicture, - region: user.region, - timeZone: user.timezone, + region: user?.region, + timeZone: user?.timezone, + ...(withRole && { role: user?.roleType }), }; } catch (err) { if (err instanceof BadRequestException) { diff --git a/src/users/user.module.ts b/src/users/user.module.ts index 46e3903..bf5164d 100644 --- a/src/users/user.module.ts +++ b/src/users/user.module.ts @@ -19,6 +19,7 @@ import { import { UserDevicePermissionService } from 'src/user-device-permission/services'; import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; +import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; @Module({ imports: [ConfigModule, CommunityModule], @@ -36,6 +37,7 @@ import { PermissionTypeRepository } from '@app/common/modules/permission/reposit PermissionTypeRepository, UserSpaceService, InviteSpaceRepository, + InviteUserRepository, ], exports: [UserService], }) From 78617dde83a051061c9735c8266b97b8dd8c6038 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 11 Jan 2025 01:39:52 -0600 Subject: [PATCH 189/247] Remove invitationCode from unique constraint and update invite user logic --- .../entities/Invite-user.entity.ts | 2 +- .../services/invite-user.service.ts | 69 +++++++++++-------- src/space/space.module.ts | 6 +- src/users/services/user-space.service.ts | 47 +++++++++---- src/users/user.module.ts | 6 +- 5 files changed, 83 insertions(+), 47 deletions(-) diff --git a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts index 345c1b8..04e5119 100644 --- a/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts +++ b/libs/common/src/modules/Invite-user/entities/Invite-user.entity.ts @@ -18,7 +18,7 @@ import { InviteUserDto, InviteUserSpaceDto } from '../dtos'; import { ProjectEntity } from '../../project/entities'; @Entity({ name: 'invite-user' }) -@Unique(['email', 'invitationCode', 'project']) +@Unique(['email', 'project']) export class InviteUserEntity extends AbstractEntity { @Column({ type: 'uuid', diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 8d63959..f8aa1df 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -287,13 +287,12 @@ export class InviteUserService { // Update invited user and associated user data await this.inviteUserRepository.update( { uuid: invitedUser.uuid }, - { status: UserStatusEnum.ACTIVE }, + { status: UserStatusEnum.ACTIVE, user: { uuid: user.uuid } }, ); await this.userRepository.update( { uuid: user.uuid }, { project: { uuid: invitedUser.project.uuid }, - inviteUser: { uuid: invitedUser.uuid }, roleType: { uuid: invitedUser.roleType.uuid }, }, ); @@ -460,12 +459,12 @@ export class InviteUserService { projectUuid, } = dto; - const user = await this.userRepository.findOne({ - where: { inviteUser: { uuid: invitedUserUuid } }, - relations: ['userSpaces.space', 'userSpaces.space.community'], + const userData = await this.inviteUserRepository.findOne({ + where: { uuid: invitedUserUuid }, + relations: ['user.userSpaces.space', 'user.userSpaces.space.community'], }); - if (!user) { + if (!userData) { throw new HttpException( `User with invitedUserUuid ${invitedUserUuid} not found`, HttpStatus.NOT_FOUND, @@ -486,12 +485,12 @@ export class InviteUserService { ); // Disassociate the user from all current spaces - const disassociatePromises = user.userSpaces.map((userSpace) => + const disassociatePromises = userData.user.userSpaces.map((userSpace) => this.spaceUserService .disassociateUserFromSpace({ communityUuid: userSpace.space.community.uuid, spaceUuid: userSpace.space.uuid, - userUuid: user.uuid, + userUuid: userData.user.uuid, projectUuid, }) .catch((error) => { @@ -517,7 +516,7 @@ export class InviteUserService { // Grant permissions to the user for all devices in the space await this.userSpaceService.addUserPermissionsToDevices( - user.uuid, + userData.user.uuid, deviceUUIDs, ); @@ -525,7 +524,7 @@ export class InviteUserService { await this.spaceUserService.associateUserToSpace({ communityUuid: spaceDetails.communityUuid, spaceUuid: spaceUuid, - userUuid: user.uuid, + userUuid: userData.user.uuid, projectUuid, }); } catch (error) { @@ -584,30 +583,36 @@ export class InviteUserService { } if (userData.status === UserStatusEnum.INVITED) { - await this.updateUserStatus(invitedUserUuid, projectUuid, disable); + await this.updateUserStatus(invitedUserUuid, projectUuid, !disable); } else if (userData.status === UserStatusEnum.ACTIVE) { - const user = await this.userRepository.findOne({ - where: { inviteUser: { uuid: invitedUserUuid } }, - relations: ['userSpaces.space', 'userSpaces.space.community'], + const invitedUserData = await this.inviteUserRepository.findOne({ + where: { uuid: invitedUserUuid }, + relations: [ + 'user.userSpaces.space', + 'user.userSpaces.space.community', + ], }); - if (!user) { + if (!invitedUserData.user) { throw new HttpException( 'User account not found', HttpStatus.NOT_FOUND, ); } - if (!disable) { - await this.disassociateUserFromSpaces(user, projectUuid); - await this.updateUserStatus(invitedUserUuid, projectUuid, disable); - } else if (disable) { + if (disable) { + await this.disassociateUserFromSpaces( + invitedUserData.user, + projectUuid, + ); + await this.updateUserStatus(invitedUserUuid, projectUuid, !disable); + } else if (!disable) { await this.associateUserToSpaces( - user, + invitedUserData.user, userData, projectUuid, invitedUserUuid, - disable, + !disable, ); } } else { @@ -640,11 +645,11 @@ export class InviteUserService { private async updateUserStatus( invitedUserUuid: string, projectUuid: string, - disable: boolean, + isEnabled: boolean, ) { await this.inviteUserRepository.update( { uuid: invitedUserUuid, project: { uuid: projectUuid } }, - { isEnabled: disable }, + { isEnabled }, ); } @@ -727,25 +732,31 @@ export class InviteUserService { { isActive: false }, ); } else if (userData.status === UserStatusEnum.ACTIVE) { - const user = await this.userRepository.findOne({ - where: { inviteUser: { uuid: invitedUserUuid } }, - relations: ['userSpaces.space', 'userSpaces.space.community'], + const invitedUserData = await this.inviteUserRepository.findOne({ + where: { uuid: invitedUserUuid }, + relations: [ + 'user.userSpaces.space', + 'user.userSpaces.space.community', + ], }); - if (!user) { + if (!invitedUserData.user) { throw new HttpException( 'User account not found', HttpStatus.NOT_FOUND, ); } - await this.disassociateUserFromSpaces(user, userData.project.uuid); + await this.disassociateUserFromSpaces( + invitedUserData.user, + userData.project.uuid, + ); await this.inviteUserRepository.update( { uuid: invitedUserUuid }, { isActive: false }, ); await this.userRepository.update( - { uuid: user.uuid }, + { uuid: invitedUserData.user.uuid }, { isActive: false }, ); } diff --git a/src/space/space.module.ts b/src/space/space.module.ts index e4bddfc..9cc0519 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -66,7 +66,10 @@ import { CqrsModule } from '@nestjs/cqrs'; import { DisableSpaceHandler } from './handlers'; import { RegionRepository } from '@app/common/modules/region/repositories'; import { TimeZoneRepository } from '@app/common/modules/timezone/repositories'; -import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; +import { + InviteUserRepository, + InviteUserSpaceRepository, +} from '@app/common/modules/Invite-user/repositiories'; export const CommandHandlers = [DisableSpaceHandler]; @@ -124,6 +127,7 @@ export const CommandHandlers = [DisableSpaceHandler]; RegionRepository, TimeZoneRepository, InviteUserRepository, + InviteUserSpaceRepository, ], exports: [SpaceService], }) diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index 074dcf1..b96ca87 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -18,7 +18,10 @@ import { PermissionType } from '@app/common/constants/permission-type.enum'; import { InviteSpaceEntity } from '@app/common/modules/space/entities/invite-space.entity'; import { UserService } from './user.service'; import { RoleType } from '@app/common/constants/role.type.enum'; -import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; +import { + InviteUserRepository, + InviteUserSpaceRepository, +} from '@app/common/modules/Invite-user/repositiories'; import { UserStatusEnum } from '@app/common/constants/user-status.enum'; @Injectable() @@ -29,6 +32,7 @@ export class UserSpaceService { private readonly inviteSpaceRepository: InviteSpaceRepository, private readonly userService: UserService, private readonly inviteUserRepository: InviteUserRepository, + private readonly inviteUserSpaceRepository: InviteUserSpaceRepository, private readonly userDevicePermissionService: UserDevicePermissionService, ) {} @@ -141,23 +145,36 @@ export class UserSpaceService { ) { try { const space = await this.getProjectBySpaceUuid(spaceUuid); - - const inviteUser = this.inviteUserRepository.create({ - firstName: user.firstName, - lastName: user.lastName, - email: user.email, - jobTitle: null, - phoneNumber: null, - roleType: { uuid: user.role.uuid }, - status: UserStatusEnum.ACTIVE, - invitationCode: inviteCode, - invitedBy: RoleType.SPACE_OWNER, - project: { uuid: space.community.project.uuid }, + const invitedUserData = await this.inviteUserRepository.findOne({ + where: { + email: user.email, + project: { uuid: space.community.project.uuid }, + }, }); - await this.inviteUserRepository.save(inviteUser); + if (!invitedUserData) { + const inviteUser = this.inviteUserRepository.create({ + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + jobTitle: null, + phoneNumber: null, + roleType: { uuid: user.role.uuid }, + status: UserStatusEnum.ACTIVE, + invitationCode: inviteCode, + invitedBy: RoleType.SPACE_OWNER, + project: { uuid: space.community.project.uuid }, + }); + const invitedUser = await this.inviteUserRepository.save(inviteUser); + const inviteUserSpace = this.inviteUserSpaceRepository.create({ + inviteUser: { uuid: invitedUser.uuid }, + space: { uuid: spaceUuid }, + }); + + await this.inviteUserSpaceRepository.save(inviteUserSpace); + } } catch (err) { throw new HttpException( - err.message || 'Internal Server Error', + err.message || 'Failed to add user as an active invitation.', HttpStatus.INTERNAL_SERVER_ERROR, ); } diff --git a/src/users/user.module.ts b/src/users/user.module.ts index bf5164d..004ac32 100644 --- a/src/users/user.module.ts +++ b/src/users/user.module.ts @@ -19,7 +19,10 @@ import { import { UserDevicePermissionService } from 'src/user-device-permission/services'; import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; -import { InviteUserRepository } from '@app/common/modules/Invite-user/repositiories'; +import { + InviteUserRepository, + InviteUserSpaceRepository, +} from '@app/common/modules/Invite-user/repositiories'; @Module({ imports: [ConfigModule, CommunityModule], @@ -38,6 +41,7 @@ import { InviteUserRepository } from '@app/common/modules/Invite-user/repositior UserSpaceService, InviteSpaceRepository, InviteUserRepository, + InviteUserSpaceRepository, ], exports: [UserService], }) From 48d597a066ccc90f55705e75dd2906247ad61ad3 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 11 Jan 2025 02:54:08 -0600 Subject: [PATCH 190/247] Update email template data in `sendEditUserEmailWithTemplate` --- src/invite-user/services/invite-user.service.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 8d63959..a4e9e0a 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -374,10 +374,10 @@ export class InviteUserService { oldFullName, newFullName, ); - await this.emailService.sendEditUserEmailWithTemplate( - userOldData.email, - emailMessage, - ); + await this.emailService.sendEditUserEmailWithTemplate(userOldData.email, { + name: dto.firstName || userOldData.firstName, + ...emailMessage, + }); // Proceed with other updates (e.g., roles, names, etc.) await queryRunner.commitTransaction(); From ef4729521d8939a9d2818243a2aed74005847d4b Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 11 Jan 2025 03:07:28 -0600 Subject: [PATCH 191/247] Add user UUID to inviteUser creation in UserSpaceService --- src/users/services/user-space.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index b96ca87..0f905b0 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -163,6 +163,7 @@ export class UserSpaceService { invitationCode: inviteCode, invitedBy: RoleType.SPACE_OWNER, project: { uuid: space.community.project.uuid }, + user: { uuid: user.uuid }, }); const invitedUser = await this.inviteUserRepository.save(inviteUser); const inviteUserSpace = this.inviteUserSpaceRepository.create({ From 65b7cf1f64017aa4e79217e54c1e7134116b82ba Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 13:40:48 +0400 Subject: [PATCH 192/247] updated listing --- libs/common/src/models/typeOrmCustom.model.ts | 6 +++- .../services/space-model.service.ts | 25 ++++++++++++++--- src/space/services/space.service.ts | 28 ++++++++++++++++--- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/libs/common/src/models/typeOrmCustom.model.ts b/libs/common/src/models/typeOrmCustom.model.ts index 76054d0..043b13c 100644 --- a/libs/common/src/models/typeOrmCustom.model.ts +++ b/libs/common/src/models/typeOrmCustom.model.ts @@ -98,14 +98,17 @@ export function TypeORMCustomModel(repository: Repository) { if (customQueryBuilder) { const qb = customQueryBuilder.skip(skip).take(size); + if (order) { Object.keys(order).forEach((key) => { qb.addOrderBy(key, order[key]); }); } + if (whereClause) { - qb.where(whereClause); + qb.andWhere(whereClause); // Use .andWhere instead of .where to avoid overwriting conditions } + if (select) { const selectColumns = Array.isArray(select) ? select @@ -114,6 +117,7 @@ export function TypeORMCustomModel(repository: Repository) { ); qb.select(selectColumns as string[]); } + [data, count] = await qb.getManyAndCount(); } else { [data, count] = await repository.findAndCount({ diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index c7ad97a..e71d05f 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -121,12 +121,27 @@ export class SpaceModelService { pageable.include = 'subspaceModels,tags,subspaceModels.tags,tags.product,subspaceModels.tags.product'; - const queryBuilder = this.spaceModelRepository + const queryBuilder = await this.spaceModelRepository .createQueryBuilder('spaceModel') - .leftJoinAndSelect('spaceModel.subspaceModels', 'subspaceModels') - .leftJoinAndSelect('spaceModel.tags', 'tags') + .leftJoinAndSelect( + 'spaceModel.subspaceModels', + 'subspaceModels', + 'subspaceModels.disabled = :subspaceDisabled', + { subspaceDisabled: false }, + ) + .leftJoinAndSelect( + 'spaceModel.tags', + 'tags', + 'tags.disabled = :tagsDisabled', + { tagsDisabled: false }, + ) .leftJoinAndSelect('tags.product', 'spaceTagproduct') - .leftJoinAndSelect('subspaceModels.tags', 'subspaceModelTags') + .leftJoinAndSelect( + 'subspaceModels.tags', + 'subspaceModelTags', + 'subspaceModelTags.disabled = :subspaceModelTagsDisabled', + { subspaceModelTagsDisabled: false }, + ) .leftJoinAndSelect('subspaceModelTags.product', 'subspaceTagproduct') .where('spaceModel.disabled = :disabled', { disabled: false }) .andWhere('spaceModel.project = :projectUuid', { @@ -144,6 +159,8 @@ export class SpaceModelService { queryBuilder, ); + console.log(baseResponseDto); + return new PageResponse( baseResponseDto, paginationResponseDto, diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 87700e4..2e64c62 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -184,11 +184,31 @@ export class SpaceService { 'children.disabled = :disabled', { disabled: false }, ) - .leftJoinAndSelect('space.incomingConnections', 'incomingConnections') - .leftJoinAndSelect('space.tags', 'tags') + .leftJoinAndSelect( + 'space.incomingConnections', + 'incomingConnections', + 'incomingConnections.disabled = :incomingConnectionDisabled', + { incomingConnectionDisabled: false }, + ) + .leftJoinAndSelect( + 'space.tags', + 'tags', + 'tags.disabled = :tagDisabled', + { tagDisabled: false }, + ) .leftJoinAndSelect('tags.product', 'tagProduct') - .leftJoinAndSelect('space.subspaces', 'subspaces') - .leftJoinAndSelect('subspaces.tags', 'subspaceTags') + .leftJoinAndSelect( + 'space.subspaces', + 'subspaces', + 'subspaces.disabled = :subspaceDisabled', + { subspaceDisabled: false }, + ) + .leftJoinAndSelect( + 'subspaces.tags', + 'subspaceTags', + 'subspaceTags.disabled = :subspaceTagsDisabled', + { subspaceTagsDisabled: false }, + ) .leftJoinAndSelect('subspaceTags.product', 'subspaceTagProduct') .where('space.community_id = :communityUuid', { communityUuid }) .andWhere('space.spaceName != :orphanSpaceName', { From ab9517e0c199ae1f87aeb6ae73838cee5cca95c7 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 12 Jan 2025 13:47:35 +0400 Subject: [PATCH 193/247] return just message --- src/space/services/space.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 2e64c62..d60abb4 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -366,7 +366,6 @@ export class SpaceService { return new SuccessResponseDto({ message: `Space with ID ${spaceUuid} successfully updated`, - data: space, statusCode: HttpStatus.OK, }); } catch (error) { From ad4ec05d0aa4173999c0d057ed98afe16a26ed07 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:55:54 -0600 Subject: [PATCH 194/247] Add JWT authentication to PermissionController and update subOption titles --- src/permission/controllers/permission.controller.ts | 8 +++++--- src/permission/services/permission.service.ts | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/permission/controllers/permission.controller.ts b/src/permission/controllers/permission.controller.ts index 9b07db2..dfa0c6b 100644 --- a/src/permission/controllers/permission.controller.ts +++ b/src/permission/controllers/permission.controller.ts @@ -1,8 +1,9 @@ -import { Controller, Get, Param } from '@nestjs/common'; -import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import { Controller, Get, Param, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; import { ControllerRoute } from '@app/common/constants/controller-route'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { PermissionService } from '../services'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; @ApiTags('Permission Module') @Controller({ @@ -11,7 +12,8 @@ import { PermissionService } from '../services'; }) export class PermissionController { constructor(private readonly permissionService: PermissionService) {} - + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) @Get(':roleUuid') @ApiOperation({ summary: ControllerRoute.PERMISSION.ACTIONS.GET_PERMISSION_BY_ROLE_SUMMARY, diff --git a/src/permission/services/permission.service.ts b/src/permission/services/permission.service.ts index 33c5f18..bd43da9 100644 --- a/src/permission/services/permission.service.ts +++ b/src/permission/services/permission.service.ts @@ -37,7 +37,7 @@ export class PermissionService { title, subOptions: Object.entries(subOptions).map( ([subTitle, permissions]) => ({ - title: subTitle, + title: `MANAGE_${subTitle}`, // Prepend "MANAGE_" to subTitle subOptions: permissions.map((permission) => ({ title: permission, isChecked: rolePermissions.includes(`${subTitle}_${permission}`), // Check if the role has the permission From d6d3491515071d9cf1beac6e7f94ddea95825802 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:56:00 -0600 Subject: [PATCH 195/247] Ensure inviteUserSpace is saved in both branches --- src/users/services/user-space.service.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index 0f905b0..fc15c3d 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -171,6 +171,13 @@ export class UserSpaceService { space: { uuid: spaceUuid }, }); + await this.inviteUserSpaceRepository.save(inviteUserSpace); + } else { + const inviteUserSpace = this.inviteUserSpaceRepository.create({ + inviteUser: { uuid: invitedUserData.uuid }, + space: { uuid: spaceUuid }, + }); + await this.inviteUserSpaceRepository.save(inviteUserSpace); } } catch (err) { From ef593086bfad3aec1b6e0126b4b1107ec3b52d87 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 15 Jan 2025 23:17:25 -0600 Subject: [PATCH 196/247] Add Terms and Conditions Module --- libs/common/src/constants/controller-route.ts | 9 ++++ .../src/constants/terms-and-conditions.html | 44 ++++++++++++++++ libs/common/src/constants/terms-conditions.ts | 29 +++++++++++ src/app.module.ts | 2 + src/terms-conditions/controllers/index.ts | 1 + .../terms-conditions.controller.ts | 32 ++++++++++++ src/terms-conditions/services/index.ts | 1 + .../services/terms-conditions.service.ts | 51 +++++++++++++++++++ .../terms-conditions.module.ts | 12 +++++ 9 files changed, 181 insertions(+) create mode 100644 libs/common/src/constants/terms-and-conditions.html create mode 100644 libs/common/src/constants/terms-conditions.ts create mode 100644 src/terms-conditions/controllers/index.ts create mode 100644 src/terms-conditions/controllers/terms-conditions.controller.ts create mode 100644 src/terms-conditions/services/index.ts create mode 100644 src/terms-conditions/services/terms-conditions.service.ts create mode 100644 src/terms-conditions/terms-conditions.module.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 4012c1a..f257e2a 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -404,6 +404,15 @@ export class ControllerRoute { 'This endpoint adds a new user role to the system based on the provided role data.'; }; }; + static TERMS_AND_CONDITIONS = class { + public static readonly ROUTE = 'terms'; + + static ACTIONS = class { + public static readonly FETCH_TERMS_SUMMARY = 'Fetch Terms and Conditions'; + public static readonly FETCH_TERMS_DESCRIPTION = + 'This endpoint retrieves the terms and conditions for the application.'; + }; + }; static GROUP = class { public static readonly ROUTE = 'group'; diff --git a/libs/common/src/constants/terms-and-conditions.html b/libs/common/src/constants/terms-and-conditions.html new file mode 100644 index 0000000..80bac71 --- /dev/null +++ b/libs/common/src/constants/terms-and-conditions.html @@ -0,0 +1,44 @@ +
+

User Agreement

+

Terms and Conditions

+

Last updated: {{lastUpdated}}

+

+ Please read these Terms and Conditions ("Terms", "Terms and Conditions") + carefully before using the + {{websiteUrl}} website and the {{mobileApp}} + mobile application (the "Service") operated by {{companyName}}. +

+

+ Your access to and use of the Service is conditioned on your acceptance of + and compliance with these Terms. These Terms apply to all visitors, users, + and others who access or use the Service. +

+

Content

+

+ Our Service allows you to post, link, store, share and otherwise make + available certain information, text, graphics, videos, or other material + ("Content"). You are responsible for the Content you post. +

+

Links To Other Websites

+

+ Our Service may contain links to third-party websites or services that are + not owned or controlled by {{companyName}}. +

+

+ {{companyName}} has no control over, and assumes no responsibility for, the + content, privacy policies, or practices of any third-party websites or + services. +

+

Changes

+

+ We reserve the right, at our sole discretion, to modify or replace these + Terms at any time. If a revision is material, we will try to provide at + least 30 days' notice prior to any new terms taking effect. What constitutes + a material change will be determined at our sole discretion. +

+

Contact Us

+

+ If you have any questions about these Terms, please + contact us. +

+
diff --git a/libs/common/src/constants/terms-conditions.ts b/libs/common/src/constants/terms-conditions.ts new file mode 100644 index 0000000..6b52f99 --- /dev/null +++ b/libs/common/src/constants/terms-conditions.ts @@ -0,0 +1,29 @@ +export const TERMS_AND_CONDITIONS = { + title: 'User Agreement', + content: ` + +
+

User Agreement

+

Terms and Conditions

+

Last updated: 25/01/2025

+

Please read these Terms and Conditions ("Terms", "Terms and Conditions") carefully before using the + http://www.mywebsite.com website and the My Mobile App + (change this) mobile application (the "Service") operated by My Company (change this) ("us", "we", or "our"). +

+

Your access to and use of the Service is conditioned on your acceptance of and compliance with these Terms. + These Terms apply to all visitors, users and others who access or use the Service. +

+

By accessing or using the Service you agree to be bound by these Terms. If you disagree with any part of the terms then you may not access the Service.

+

Content

+

Our Service allows you to post, link, store, share and otherwise make available certain information, text, graphics, videos, or other material ("Content"). You are responsible for the Content you post.

+

Links To Other Websites

+

Our Service may contain links to third-party websites or services that are not owned or controlled by My Company (change this).

+

My Company (change this) has no control over, and assumes no responsibility for, the content, privacy policies, or practices of any third-party websites or services. You further acknowledge and agree that My Company (change this) shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods, or services available on or through any such websites or services.

+

Changes

+

We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a revision is material, we will try to provide at least 30 (change this) days' notice prior to any new terms taking effect. What constitutes a material change will be determined at our sole discretion.

+

Contact Us

+

If you have any questions about these Terms, please contact us.

+
+ +`, +}; diff --git a/src/app.module.ts b/src/app.module.ts index fe656ff..d61ac4c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -26,6 +26,7 @@ import { SpaceModelModule } from './space-model'; import { InviteUserModule } from './invite-user/invite-user.module'; import { PermissionModule } from './permission/permission.module'; import { RoleModule } from './role/role.module'; +import { TermsConditionsModule } from './terms-conditions/terms-conditions.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -55,6 +56,7 @@ import { RoleModule } from './role/role.module'; ProjectModule, PermissionModule, RoleModule, + TermsConditionsModule, ], providers: [ { diff --git a/src/terms-conditions/controllers/index.ts b/src/terms-conditions/controllers/index.ts new file mode 100644 index 0000000..6d48cce --- /dev/null +++ b/src/terms-conditions/controllers/index.ts @@ -0,0 +1 @@ +export * from './terms-conditions.controller'; diff --git a/src/terms-conditions/controllers/terms-conditions.controller.ts b/src/terms-conditions/controllers/terms-conditions.controller.ts new file mode 100644 index 0000000..52849ae --- /dev/null +++ b/src/terms-conditions/controllers/terms-conditions.controller.ts @@ -0,0 +1,32 @@ +import { Controller, Get, HttpStatus } from '@nestjs/common'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; // Assuming this is where the routes are defined +import { TermsAndConditionsService } from '../services'; + +@ApiTags('Terms & Conditions Module') +@Controller({ + version: EnableDisableStatusEnum.ENABLED, + path: ControllerRoute.TERMS_AND_CONDITIONS.ROUTE, // use the static route constant +}) +export class TermsConditionsController { + constructor( + private readonly termsAndConditionsService: TermsAndConditionsService, + ) {} + + @Get() + @ApiOperation({ + summary: ControllerRoute.TERMS_AND_CONDITIONS.ACTIONS.FETCH_TERMS_SUMMARY, + description: + ControllerRoute.TERMS_AND_CONDITIONS.ACTIONS.FETCH_TERMS_DESCRIPTION, + }) + async fetchTermsAndConditions() { + const htmlContent = + await this.termsAndConditionsService.fetchTermsAndConditions(); + return { + statusCode: HttpStatus.OK, + message: 'Terms and conditions fetched successfully', + data: htmlContent, + }; + } +} diff --git a/src/terms-conditions/services/index.ts b/src/terms-conditions/services/index.ts new file mode 100644 index 0000000..09d3783 --- /dev/null +++ b/src/terms-conditions/services/index.ts @@ -0,0 +1 @@ +export * from './terms-conditions.service'; diff --git a/src/terms-conditions/services/terms-conditions.service.ts b/src/terms-conditions/services/terms-conditions.service.ts new file mode 100644 index 0000000..14d98d6 --- /dev/null +++ b/src/terms-conditions/services/terms-conditions.service.ts @@ -0,0 +1,51 @@ +import { Injectable } from '@nestjs/common'; +import * as fs from 'fs'; +import * as path from 'path'; + +@Injectable() +export class TermsAndConditionsService { + async fetchTermsAndConditions(): Promise { + // Use process.cwd() to get the current working directory (project root) + const projectRoot = process.cwd(); + + // Dynamically build the path to the terms-and-conditions.html file from the root + const filePath = path.join( + projectRoot, + 'libs', + 'common', + 'src', + 'constants', + 'terms-and-conditions.html', + ); + + // Ensure the file exists + if (!fs.existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + // Read the HTML content from the file + let htmlContent = fs.readFileSync(filePath, 'utf-8'); + + // Optionally, remove newlines or excess white spaces using a regular expression + htmlContent = htmlContent.replace(/(\r\n|\n|\r)/gm, ''); // Removes newlines + + // Define dynamic values + const dynamicValues = { + lastUpdated: '25/01/2025', + websiteUrl: 'http://www.mywebsite.com', + mobileApp: 'My Mobile App', + companyName: 'My Company', + contactEmail: 'contact@mycompany.com', + }; + + // Replace placeholders in the HTML with dynamic values + htmlContent = htmlContent + .replace(/{{lastUpdated}}/g, dynamicValues.lastUpdated) + .replace(/{{websiteUrl}}/g, dynamicValues.websiteUrl) + .replace(/{{mobileApp}}/g, dynamicValues.mobileApp) + .replace(/{{companyName}}/g, dynamicValues.companyName) + .replace(/{{contactEmail}}/g, dynamicValues.contactEmail); + + return htmlContent; + } +} diff --git a/src/terms-conditions/terms-conditions.module.ts b/src/terms-conditions/terms-conditions.module.ts new file mode 100644 index 0000000..6dbf974 --- /dev/null +++ b/src/terms-conditions/terms-conditions.module.ts @@ -0,0 +1,12 @@ +import { DeviceRepositoryModule } from '@app/common/modules/device'; +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { TermsConditionsController } from './controllers'; +import { TermsAndConditionsService } from './services'; + +@Module({ + imports: [ConfigModule, DeviceRepositoryModule], + controllers: [TermsConditionsController], + providers: [TermsAndConditionsService], +}) +export class TermsConditionsModule {} From c7bcb177e19334629740c068fddb712d6aa86a3a Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 15 Jan 2025 23:19:19 -0600 Subject: [PATCH 197/247] Remove unused terms and conditions constant --- libs/common/src/constants/terms-conditions.ts | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 libs/common/src/constants/terms-conditions.ts diff --git a/libs/common/src/constants/terms-conditions.ts b/libs/common/src/constants/terms-conditions.ts deleted file mode 100644 index 6b52f99..0000000 --- a/libs/common/src/constants/terms-conditions.ts +++ /dev/null @@ -1,29 +0,0 @@ -export const TERMS_AND_CONDITIONS = { - title: 'User Agreement', - content: ` - -
-

User Agreement

-

Terms and Conditions

-

Last updated: 25/01/2025

-

Please read these Terms and Conditions ("Terms", "Terms and Conditions") carefully before using the - http://www.mywebsite.com website and the My Mobile App - (change this) mobile application (the "Service") operated by My Company (change this) ("us", "we", or "our"). -

-

Your access to and use of the Service is conditioned on your acceptance of and compliance with these Terms. - These Terms apply to all visitors, users and others who access or use the Service. -

-

By accessing or using the Service you agree to be bound by these Terms. If you disagree with any part of the terms then you may not access the Service.

-

Content

-

Our Service allows you to post, link, store, share and otherwise make available certain information, text, graphics, videos, or other material ("Content"). You are responsible for the Content you post.

-

Links To Other Websites

-

Our Service may contain links to third-party websites or services that are not owned or controlled by My Company (change this).

-

My Company (change this) has no control over, and assumes no responsibility for, the content, privacy policies, or practices of any third-party websites or services. You further acknowledge and agree that My Company (change this) shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods, or services available on or through any such websites or services.

-

Changes

-

We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a revision is material, we will try to provide at least 30 (change this) days' notice prior to any new terms taking effect. What constitutes a material change will be determined at our sole discretion.

-

Contact Us

-

If you have any questions about these Terms, please contact us.

-
- -`, -}; From 12ddb95f1921347f61735509b4b2f6bbe3fbdad4 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 15 Jan 2025 23:24:37 -0600 Subject: [PATCH 198/247] Add terms and conditions data constants and update service --- libs/common/src/constants/terms-condtions.ts | 7 ++++++ .../services/terms-conditions.service.ts | 25 +++++-------------- 2 files changed, 13 insertions(+), 19 deletions(-) create mode 100644 libs/common/src/constants/terms-condtions.ts diff --git a/libs/common/src/constants/terms-condtions.ts b/libs/common/src/constants/terms-condtions.ts new file mode 100644 index 0000000..b0ba0af --- /dev/null +++ b/libs/common/src/constants/terms-condtions.ts @@ -0,0 +1,7 @@ +export const termsAndConditionsData = { + lastUpdated: '25/01/2025', + websiteUrl: 'https://www.Syncrow.ae', + mobileApp: 'Syncrow Mobile App', + companyName: 'Syncrow', + contactEmail: 'contact@Syncrow.ae', +}; diff --git a/src/terms-conditions/services/terms-conditions.service.ts b/src/terms-conditions/services/terms-conditions.service.ts index 14d98d6..c8adf0f 100644 --- a/src/terms-conditions/services/terms-conditions.service.ts +++ b/src/terms-conditions/services/terms-conditions.service.ts @@ -1,3 +1,4 @@ +import { termsAndConditionsData } from '@app/common/constants/terms-condtions'; import { Injectable } from '@nestjs/common'; import * as fs from 'fs'; import * as path from 'path'; @@ -5,10 +6,8 @@ import * as path from 'path'; @Injectable() export class TermsAndConditionsService { async fetchTermsAndConditions(): Promise { - // Use process.cwd() to get the current working directory (project root) const projectRoot = process.cwd(); - // Dynamically build the path to the terms-and-conditions.html file from the root const filePath = path.join( projectRoot, 'libs', @@ -23,28 +22,16 @@ export class TermsAndConditionsService { throw new Error(`File not found: ${filePath}`); } - // Read the HTML content from the file let htmlContent = fs.readFileSync(filePath, 'utf-8'); - // Optionally, remove newlines or excess white spaces using a regular expression htmlContent = htmlContent.replace(/(\r\n|\n|\r)/gm, ''); // Removes newlines - // Define dynamic values - const dynamicValues = { - lastUpdated: '25/01/2025', - websiteUrl: 'http://www.mywebsite.com', - mobileApp: 'My Mobile App', - companyName: 'My Company', - contactEmail: 'contact@mycompany.com', - }; - - // Replace placeholders in the HTML with dynamic values htmlContent = htmlContent - .replace(/{{lastUpdated}}/g, dynamicValues.lastUpdated) - .replace(/{{websiteUrl}}/g, dynamicValues.websiteUrl) - .replace(/{{mobileApp}}/g, dynamicValues.mobileApp) - .replace(/{{companyName}}/g, dynamicValues.companyName) - .replace(/{{contactEmail}}/g, dynamicValues.contactEmail); + .replace(/{{lastUpdated}}/g, termsAndConditionsData.lastUpdated) + .replace(/{{websiteUrl}}/g, termsAndConditionsData.websiteUrl) + .replace(/{{mobileApp}}/g, termsAndConditionsData.mobileApp) + .replace(/{{companyName}}/g, termsAndConditionsData.companyName) + .replace(/{{contactEmail}}/g, termsAndConditionsData.contactEmail); return htmlContent; } From dc111330579e53454542eed35f4196322f165314 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 16 Jan 2025 23:32:42 -0600 Subject: [PATCH 199/247] Add Privacy Policy Module and Controller --- libs/common/src/constants/controller-route.ts | 10 +++++ libs/common/src/constants/privacy-policy.html | 37 +++++++++++++++++++ src/app.module.ts | 2 + src/privacy-policy/controllers/index.ts | 1 + .../controllers/privacy-policy.controller.ts | 29 +++++++++++++++ src/privacy-policy/privacy-policy.module.ts | 12 ++++++ src/privacy-policy/services/index.ts | 1 + .../services/privacy-policy.service.ts | 30 +++++++++++++++ 8 files changed, 122 insertions(+) create mode 100644 libs/common/src/constants/privacy-policy.html create mode 100644 src/privacy-policy/controllers/index.ts create mode 100644 src/privacy-policy/controllers/privacy-policy.controller.ts create mode 100644 src/privacy-policy/privacy-policy.module.ts create mode 100644 src/privacy-policy/services/index.ts create mode 100644 src/privacy-policy/services/privacy-policy.service.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index f257e2a..d82aebe 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -413,6 +413,16 @@ export class ControllerRoute { 'This endpoint retrieves the terms and conditions for the application.'; }; }; + + static PRIVACY_POLICY = class { + public static readonly ROUTE = 'policy'; + + static ACTIONS = class { + public static readonly FETCH_POLICY_SUMMARY = 'Fetch Privacy Policy'; + public static readonly FETCH_POLICY_DESCRIPTION = + 'This endpoint retrieves the privacy policy for the application.'; + }; + }; static GROUP = class { public static readonly ROUTE = 'group'; diff --git a/libs/common/src/constants/privacy-policy.html b/libs/common/src/constants/privacy-policy.html new file mode 100644 index 0000000..e33fabf --- /dev/null +++ b/libs/common/src/constants/privacy-policy.html @@ -0,0 +1,37 @@ +
+

Syncrow Mobile Privacy Policy

+

+ Effective Date: 26/06/2022
+ Updated: 26/06/2022 +

+

+ Syncrow and subsidiaries (“we”, “us”, “our”, “Syncrow”) are committed to + protecting your privacy. This Privacy Policy (“Policy”) describes our + practices in connection with information privacy on Personal Data we process + through your individual use of the following services, products, and related + mobile applications (collectively, the “Products”): +

+
    +
  • Syncrow Mobile Application
  • +
+

+ Before you use our Products, please carefully read through this Policy and + understand our purposes and practices of collection, processing of your + Personal Data, including how we use, store, share and transfer Personal + Data. In the Policy you will also find ways to execute your rights of + access, update, delete or protect your Personal Data. +

+

+ When you accept this Policy when you register with your Personal Data, or if + you start to use our Products and does not expressly object to the contents + of this Policy, we will consider that you fully understand and agree with + this Policy. If you have any questions regarding this Policy, please do not + hesitate to contact us via: +

+

+ For other branded mobile applications powered by Syncrow, our Clients + control all the Personal Data collected through our Products. We collect the + information under the direction of our Clients and the processing of such + information. +

+
diff --git a/src/app.module.ts b/src/app.module.ts index d61ac4c..f941837 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -27,6 +27,7 @@ import { InviteUserModule } from './invite-user/invite-user.module'; import { PermissionModule } from './permission/permission.module'; import { RoleModule } from './role/role.module'; import { TermsConditionsModule } from './terms-conditions/terms-conditions.module'; +import { PrivacyPolicyModule } from './privacy-policy/privacy-policy.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -57,6 +58,7 @@ import { TermsConditionsModule } from './terms-conditions/terms-conditions.modul PermissionModule, RoleModule, TermsConditionsModule, + PrivacyPolicyModule, ], providers: [ { diff --git a/src/privacy-policy/controllers/index.ts b/src/privacy-policy/controllers/index.ts new file mode 100644 index 0000000..1ef7536 --- /dev/null +++ b/src/privacy-policy/controllers/index.ts @@ -0,0 +1 @@ +export * from './privacy-policy.controller'; diff --git a/src/privacy-policy/controllers/privacy-policy.controller.ts b/src/privacy-policy/controllers/privacy-policy.controller.ts new file mode 100644 index 0000000..621a63c --- /dev/null +++ b/src/privacy-policy/controllers/privacy-policy.controller.ts @@ -0,0 +1,29 @@ +import { Controller, Get, HttpStatus } from '@nestjs/common'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; // Assuming this is where the routes are defined +import { PrivacyPolicyService } from '../services'; + +@ApiTags('Privacy Policy Module') +@Controller({ + version: EnableDisableStatusEnum.ENABLED, + path: ControllerRoute.PRIVACY_POLICY.ROUTE, +}) +export class PrivacyPolicyController { + constructor(private readonly privacyPolicyService: PrivacyPolicyService) {} + + @Get() + @ApiOperation({ + summary: ControllerRoute.PRIVACY_POLICY.ACTIONS.FETCH_POLICY_SUMMARY, + description: + ControllerRoute.PRIVACY_POLICY.ACTIONS.FETCH_POLICY_DESCRIPTION, + }) + async fetchPrivacyPolicy() { + const htmlContent = await this.privacyPolicyService.fetchPrivacyPolicy(); + return { + statusCode: HttpStatus.OK, + message: 'Privacy policy fetched successfully', + data: htmlContent, + }; + } +} diff --git a/src/privacy-policy/privacy-policy.module.ts b/src/privacy-policy/privacy-policy.module.ts new file mode 100644 index 0000000..75da19c --- /dev/null +++ b/src/privacy-policy/privacy-policy.module.ts @@ -0,0 +1,12 @@ +import { DeviceRepositoryModule } from '@app/common/modules/device'; +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { PrivacyPolicyController } from './controllers'; +import { PrivacyPolicyService } from './services'; + +@Module({ + imports: [ConfigModule, DeviceRepositoryModule], + controllers: [PrivacyPolicyController], + providers: [PrivacyPolicyService], +}) +export class PrivacyPolicyModule {} diff --git a/src/privacy-policy/services/index.ts b/src/privacy-policy/services/index.ts new file mode 100644 index 0000000..9866974 --- /dev/null +++ b/src/privacy-policy/services/index.ts @@ -0,0 +1 @@ +export * from './privacy-policy.service'; diff --git a/src/privacy-policy/services/privacy-policy.service.ts b/src/privacy-policy/services/privacy-policy.service.ts new file mode 100644 index 0000000..9c347b8 --- /dev/null +++ b/src/privacy-policy/services/privacy-policy.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@nestjs/common'; +import * as fs from 'fs'; +import * as path from 'path'; + +@Injectable() +export class PrivacyPolicyService { + async fetchPrivacyPolicy(): Promise { + const projectRoot = process.cwd(); + + const filePath = path.join( + projectRoot, + 'libs', + 'common', + 'src', + 'constants', + 'privacy-policy.html', + ); + + // Ensure the file exists + if (!fs.existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + let htmlContent = fs.readFileSync(filePath, 'utf-8'); + + htmlContent = htmlContent.replace(/(\r\n|\n|\r)/gm, ''); // Removes newlines + + return htmlContent; + } +} From 42c3f3c150ee0f2e1f4d428261cb6c72cee42d31 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 21 Jan 2025 00:02:17 -0600 Subject: [PATCH 200/247] Update device status logs to handle multiple properties --- .../services/devices-status.service.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/libs/common/src/firebase/devices-status/services/devices-status.service.ts b/libs/common/src/firebase/devices-status/services/devices-status.service.ts index 162313b..fbbc00c 100644 --- a/libs/common/src/firebase/devices-status/services/devices-status.service.ts +++ b/libs/common/src/firebase/devices-status/services/devices-status.service.ts @@ -187,19 +187,19 @@ export class DeviceStatusFirebaseService { code, value, })); - const newLog = this.deviceStatusLogRepository.create({ - deviceId: addDeviceStatusDto.deviceUuid, - deviceTuyaId: addDeviceStatusDto.deviceTuyaUuid, - productId: addDeviceStatusDto.log.productId, - log: addDeviceStatusDto.log, - code: existingData.status[0].code, - value: existingData.status[0].value, - eventId: addDeviceStatusDto.log.dataId, - eventTime: new Date( - addDeviceStatusDto.log.properties[0].time, - ).toISOString(), + const newLogs = addDeviceStatusDto.log.properties.map((property) => { + return this.deviceStatusLogRepository.create({ + deviceId: addDeviceStatusDto.deviceUuid, + deviceTuyaId: addDeviceStatusDto.deviceTuyaUuid, + productId: addDeviceStatusDto.log.productId, + log: addDeviceStatusDto.log, + code: property.code, + value: property.value, + eventId: addDeviceStatusDto.log.dataId, + eventTime: new Date(property.time).toISOString(), + }); }); - await this.deviceStatusLogRepository.save(newLog); + await this.deviceStatusLogRepository.save(newLogs); // Save the updated data to Firebase await set(dataRef, existingData); From 9542f9334f307a858d9f0d0573a7eb67ccaf35bf Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 21 Jan 2025 00:02:25 -0600 Subject: [PATCH 201/247] Refactor invite user service to validate spaces and improve code structure --- .../services/invite-user.service.ts | 56 ++++++++++++++----- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 4a27387..df74f21 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -9,7 +9,7 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { UserStatusEnum } from '@app/common/constants/user-status.enum'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { generateRandomString } from '@app/common/helper/randomString'; -import { In, IsNull, Not, QueryRunner } from 'typeorm'; +import { EntityManager, In, IsNull, Not, QueryRunner } from 'typeorm'; import { DataSource } from 'typeorm'; import { UserEntity } from '@app/common/modules/user/entities'; import { RoleType } from '@app/common/constants/role.type.enum'; @@ -69,20 +69,27 @@ export class InviteUserService { const userRepo = queryRunner.manager.getRepository(UserEntity); await this.checkEmailAndProject({ email }); - const user = await userRepo.findOne({ + const existingUser = await userRepo.findOne({ where: { email, project: Not(IsNull()), }, }); - if (user) { + if (existingUser) { throw new HttpException( 'User already has a project', HttpStatus.BAD_REQUEST, ); } + // Validate spaces + const validSpaces = await this.validateSpaces( + spaceUuids, + queryRunner.manager, + ); + + // Create invitation const inviteUser = this.inviteUserRepository.create({ firstName, lastName, @@ -97,30 +104,25 @@ export class InviteUserService { }); const invitedUser = await queryRunner.manager.save(inviteUser); - const spaceRepo = queryRunner.manager.getRepository(SpaceEntity); - const spaces = await spaceRepo.find({ - where: { - uuid: In(spaceUuids), - }, - }); - const spaceNames = spaces.map((space) => space.spaceName); - - const spaceNamesString = spaceNames.join(', '); - const spacePromises = spaceUuids.map(async (spaceUuid) => { + // Link user to spaces + const spacePromises = validSpaces.map(async (space) => { const inviteUserSpace = this.inviteUserSpaceRepository.create({ inviteUser: { uuid: invitedUser.uuid }, - space: { uuid: spaceUuid }, + space: { uuid: space.uuid }, }); return queryRunner.manager.save(inviteUserSpace); }); await Promise.all(spacePromises); + + // Send invitation email + const spaceNames = validSpaces.map((space) => space.spaceName).join(', '); await this.emailService.sendEmailWithInvitationTemplate(email, { name: firstName, invitationCode, role: roleType, - spacesList: spaceNamesString, + spacesList: spaceNames, }); await queryRunner.commitTransaction(); @@ -147,6 +149,30 @@ export class InviteUserService { await queryRunner.release(); } } + private async validateSpaces( + spaceUuids: string[], + entityManager: EntityManager, + ): Promise { + const spaceRepo = entityManager.getRepository(SpaceEntity); + + const validSpaces = await spaceRepo.find({ + where: { uuid: In(spaceUuids) }, + }); + + if (validSpaces.length !== spaceUuids.length) { + const validSpaceUuids = validSpaces.map((space) => space.uuid); + const invalidSpaceUuids = spaceUuids.filter( + (uuid) => !validSpaceUuids.includes(uuid), + ); + throw new HttpException( + `Invalid space UUIDs: ${invalidSpaceUuids.join(', ')}`, + HttpStatus.BAD_REQUEST, + ); + } + + return validSpaces; + } + async checkEmailAndProject(dto: CheckEmailDto): Promise { const { email } = dto; From 335ba9f63be44a486ea5d022b035721513ca1ae6 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 21 Jan 2025 07:40:52 -0600 Subject: [PATCH 202/247] Add VIEW_DEVICE_WIZARD permission to VISITOR role --- libs/common/src/constants/role-permissions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/common/src/constants/role-permissions.ts b/libs/common/src/constants/role-permissions.ts index 8d3d0ed..10b6067 100644 --- a/libs/common/src/constants/role-permissions.ts +++ b/libs/common/src/constants/role-permissions.ts @@ -134,5 +134,6 @@ export const RolePermissions = { 'VISITOR_PASSWORD_ADD', 'VISITOR_PASSWORD_UPDATE', 'VISITOR_PASSWORD_DELETE', + 'VIEW_DEVICE_WIZARD', ], }; From 41da5289636f59930c39fdaafd3e97c2bbcb8547 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 22 Jan 2025 00:33:44 -0600 Subject: [PATCH 203/247] Add agreement acceptance fields to UserEntity --- libs/common/src/modules/user/entities/user.entity.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libs/common/src/modules/user/entities/user.entity.ts b/libs/common/src/modules/user/entities/user.entity.ts index 097b154..023864a 100644 --- a/libs/common/src/modules/user/entities/user.entity.ts +++ b/libs/common/src/modules/user/entities/user.entity.ts @@ -82,6 +82,18 @@ export class UserEntity extends AbstractEntity { }) public isActive: boolean; + @Column({ default: false }) + hasAcceptedWebAgreement: boolean; + + @Column({ default: false }) + hasAcceptedAppAgreement: boolean; + + @Column({ type: 'timestamp', nullable: true }) + webAgreementAcceptedAt: Date; + + @Column({ type: 'timestamp', nullable: true }) + appAgreementAcceptedAt: Date; + @OneToMany(() => UserSpaceEntity, (userSpace) => userSpace.user) userSpaces: UserSpaceEntity[]; From 6dd6c79d87ad3e0d831f4da641d7123ba19a6e83 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 22 Jan 2025 00:34:47 -0600 Subject: [PATCH 204/247] Add app agreement acceptance check and validation --- libs/common/src/auth/services/auth.service.ts | 6 +++++- src/auth/dtos/user-auth.dto.ts | 20 ++++++++++++++++++- src/auth/services/user-auth.service.ts | 12 +++++++++-- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index bc25e0e..528db56 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -48,7 +48,9 @@ export class AuthService { if (!user.isActive) { throw new BadRequestException('User is not active'); } - + if (!user.hasAcceptedAppAgreement) { + throw new BadRequestException('User has not accepted app agreement'); + } const passwordMatch = await this.helperHashService.bcryptCompare( pass, user.password, @@ -92,6 +94,8 @@ export class AuthService { sessionId: user.sessionId, role: user?.role, googleCode: user.googleCode, + hasAcceptedWebAgreement: user.hasAcceptedWebAgreement, + hasAcceptedAppAgreement: user.hasAcceptedAppAgreement, }; if (payload.googleCode) { const profile = await this.getProfile(payload.googleCode); diff --git a/src/auth/dtos/user-auth.dto.ts b/src/auth/dtos/user-auth.dto.ts index dad1e07..d2f2e8a 100644 --- a/src/auth/dtos/user-auth.dto.ts +++ b/src/auth/dtos/user-auth.dto.ts @@ -1,5 +1,11 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { + IsBoolean, + IsEmail, + IsNotEmpty, + IsOptional, + IsString, +} from 'class-validator'; import { IsPasswordStrong } from 'src/validators/password.validator'; export class UserSignUpDto { @@ -39,7 +45,19 @@ export class UserSignUpDto { @IsNotEmpty() public lastName: string; + @ApiProperty({ + description: 'regionUuid', + required: false, + }) @IsString() @IsOptional() public regionUuid?: string; + + @ApiProperty({ + description: 'hasAcceptedAppAgreement', + required: true, + }) + @IsBoolean() + @IsNotEmpty() + public hasAcceptedAppAgreement: boolean; } diff --git a/src/auth/services/user-auth.service.ts b/src/auth/services/user-auth.service.ts index afdf6d2..c9c9436 100644 --- a/src/auth/services/user-auth.service.ts +++ b/src/auth/services/user-auth.service.ts @@ -46,12 +46,17 @@ export class UserAuthService { ); try { - const { regionUuid, ...rest } = userSignUpDto; + const { regionUuid, hasAcceptedAppAgreement, ...rest } = userSignUpDto; + if (!hasAcceptedAppAgreement) { + throw new BadRequestException('Please accept the terms and conditions'); + } const spaceMemberRole = await this.roleService.findRoleByType( RoleType.SPACE_MEMBER, ); const user = await this.userRepository.save({ ...rest, + appAgreementAcceptedAt: new Date(), + hasAcceptedAppAgreement, password: hashedPassword, roleType: { uuid: spaceMemberRole.uuid }, region: regionUuid @@ -65,7 +70,7 @@ export class UserAuthService { return user; } catch (error) { - throw new BadRequestException('Failed to register user'); + throw new BadRequestException(error.message || 'Failed to register user'); } } @@ -116,6 +121,7 @@ export class UserAuthService { firstName: googleUserData['given_name'], lastName: googleUserData['family_name'], password: googleUserData['email'], + hasAcceptedAppAgreement: true, }); } data.email = googleUserData['email']; @@ -147,6 +153,8 @@ export class UserAuthService { userId: user.uuid, uuid: user.uuid, role: user.roleType, + hasAcceptedWebAgreement: user.hasAcceptedWebAgreement, + hasAcceptedAppAgreement: user.hasAcceptedAppAgreement, sessionId: session[1].uuid, }); return res; From f675064b68c4bf769ba39c17f4b9fb4a5e82c5ee Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 22 Jan 2025 00:34:54 -0600 Subject: [PATCH 205/247] Add endpoint to update user web agreement --- libs/common/src/constants/controller-route.ts | 4 +++ src/users/controllers/user.controller.ts | 16 +++++++++++ src/users/services/user-space.service.ts | 5 +--- src/users/services/user.service.ts | 27 +++++++++++++++---- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index d82aebe..4cee3fe 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -349,6 +349,10 @@ export class ControllerRoute { public static readonly DELETE_USER_SUMMARY = 'Delete user by UUID'; public static readonly DELETE_USER_DESCRIPTION = 'This endpoint deletes a user identified by their UUID. Accessible only by users with the Super Admin role.'; + public static readonly UPDATE_USER_WEB_AGREEMENT_SUMMARY = + 'Update user web agreement by user UUID'; + public static readonly UPDATE_USER_WEB_AGREEMENT_DESCRIPTION = + 'This endpoint updates the web agreement for a user identified by their UUID.'; }; }; static AUTHENTICATION = class { diff --git a/src/users/controllers/user.controller.ts b/src/users/controllers/user.controller.ts index 6f0eded..ce9991a 100644 --- a/src/users/controllers/user.controller.ts +++ b/src/users/controllers/user.controller.ts @@ -5,6 +5,7 @@ import { Get, HttpStatus, Param, + Patch, Put, UseGuards, } from '@nestjs/common'; @@ -21,6 +22,7 @@ import { CheckProfilePictureGuard } from 'src/guards/profile.picture.guard'; import { SuperAdminRoleGuard } from 'src/guards/super.admin.role.guard'; import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; import { ControllerRoute } from '@app/common/constants/controller-route'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; @ApiTags('User Module') @Controller({ @@ -151,4 +153,18 @@ export class UserController { message: 'User deleted successfully', }; } + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Patch('agreements/web/:userUuid') + @ApiOperation({ + summary: ControllerRoute.USER.ACTIONS.UPDATE_USER_WEB_AGREEMENT_SUMMARY, + description: + ControllerRoute.USER.ACTIONS.UPDATE_USER_WEB_AGREEMENT_DESCRIPTION, + }) + async acceptWebAgreement( + @Param('userUuid') userUuid: string, + ): Promise { + return this.userService.acceptWebAgreement(userUuid); + } } diff --git a/src/users/services/user-space.service.ts b/src/users/services/user-space.service.ts index fc15c3d..e06bbd5 100644 --- a/src/users/services/user-space.service.ts +++ b/src/users/services/user-space.service.ts @@ -59,10 +59,7 @@ export class UserSpaceService { const { inviteCode } = params; try { const inviteSpace = await this.findInviteSpaceByInviteCode(inviteCode); - const user = await this.userService.getUserDetailsByUserUuid( - userUuid, - true, - ); + const user = await this.userService.getUserDetailsByUserUuid(userUuid); await this.checkSpaceMemberRole(user); await this.addUserToSpace(userUuid, inviteSpace.space.uuid); diff --git a/src/users/services/user.service.ts b/src/users/services/user.service.ts index 96a669b..268dc7c 100644 --- a/src/users/services/user.service.ts +++ b/src/users/services/user.service.ts @@ -15,6 +15,7 @@ import { RegionRepository } from '@app/common/modules/region/repositories'; import { TimeZoneRepository } from '@app/common/modules/timezone/repositories'; import { removeBase64Prefix } from '@app/common/helper/removeBase64Prefix'; import { UserEntity } from '@app/common/modules/user/entities'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; @Injectable() export class UserService { @@ -23,15 +24,13 @@ export class UserService { private readonly regionRepository: RegionRepository, private readonly timeZoneRepository: TimeZoneRepository, ) {} - async getUserDetailsByUserUuid(userUuid: string, withRole = false) { + async getUserDetailsByUserUuid(userUuid: string) { try { const user = await this.userRepository.findOne({ where: { uuid: userUuid, }, - ...(withRole - ? { relations: ['roleType'] } - : { relations: ['region', 'timezone'] }), + relations: ['region', 'timezone', 'roleType'], }); if (!user) { throw new BadRequestException('Invalid room UUID'); @@ -48,7 +47,11 @@ export class UserService { profilePicture: cleanedProfilePicture, region: user?.region, timeZone: user?.timezone, - ...(withRole && { role: user?.roleType }), + hasAcceptedWebAgreement: user?.hasAcceptedWebAgreement, + webAgreementAcceptedAt: user?.webAgreementAcceptedAt, + hasAcceptedAppAgreement: user?.hasAcceptedAppAgreement, + appAgreementAcceptedAt: user?.appAgreementAcceptedAt, + role: user?.roleType, }; } catch (err) { if (err instanceof BadRequestException) { @@ -241,6 +244,20 @@ export class UserService { ); } } + async acceptWebAgreement(userUuid: string) { + await this.userRepository.update( + { uuid: userUuid }, + { + hasAcceptedWebAgreement: true, + webAgreementAcceptedAt: new Date(), + }, + ); + return new SuccessResponseDto({ + statusCode: HttpStatus.OK, + success: true, + message: 'Web agreement accepted successfully', + }); + } async findOneById(id: string): Promise { return await this.userRepository.findOne({ where: { uuid: id } }); } From 3ee2e66e3989e92e9dacb41e09a8a69ba78d4624 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 22 Jan 2025 12:03:53 +0400 Subject: [PATCH 206/247] filter disable get space models in get query --- .../services/space-model.service.ts | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index e71d05f..5ef531c 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -159,8 +159,6 @@ export class SpaceModelService { queryBuilder, ); - console.log(baseResponseDto); - return new PageResponse( baseResponseDto, paginationResponseDto, @@ -344,35 +342,51 @@ export class SpaceModelService { try { await this.validateProject(params.projectUuid); const spaceModel = await this.validateSpaceModel(params.spaceModelUuid); + return new SuccessResponseDto({ message: 'SpaceModel retrieved successfully', data: spaceModel, }); } catch (error) { throw new HttpException( - `Failed to retrieve space model ${error}`, + `Failed to retrieve space model: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } } async validateSpaceModel(uuid: string): Promise { - const spaceModel = await this.spaceModelRepository.findOne({ - where: { - uuid, - disabled: false, - }, - relations: [ + const spaceModel = await this.spaceModelRepository + .createQueryBuilder('spaceModel') + .leftJoinAndSelect( + 'spaceModel.subspaceModels', 'subspaceModels', + 'subspaceModels.disabled = :subspaceDisabled', + { subspaceDisabled: false }, + ) + .leftJoinAndSelect( + 'spaceModel.tags', 'tags', - 'tags.product', + 'tags.disabled = :tagsDisabled', + { tagsDisabled: false }, + ) + .leftJoinAndSelect('tags.product', 'spaceTagproduct') + .leftJoinAndSelect( 'subspaceModels.tags', - 'subspaceModels.tags.product', - ], - }); + 'subspaceModelTags', + 'subspaceModelTags.disabled = :subspaceModelTagsDisabled', + { subspaceModelTagsDisabled: false }, + ) + .leftJoinAndSelect('subspaceModelTags.product', 'subspaceTagproduct') + .where('spaceModel.disabled = :disabled', { disabled: false }) + .where('spaceModel.disabled = :disabled', { disabled: false }) + .andWhere('spaceModel.uuid = :uuid', { uuid }) + .getOne(); + if (!spaceModel) { throw new HttpException('space model not found', HttpStatus.NOT_FOUND); } + return spaceModel; } } From 7e15cc65a40ce252f67959ec0b974ea4f436c1a1 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 22 Jan 2025 12:44:54 +0400 Subject: [PATCH 207/247] only update if tag is no there --- src/space-model/services/tag-model.service.ts | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index c364816..14f2cf2 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -67,20 +67,25 @@ export class TagModelService { try { const existingTag = await this.getTagByUuid(tag.uuid); - if (spaceModel) { - await this.checkTagReuse(tag.tag, existingTag.product.uuid, spaceModel); - } else { - await this.checkTagReuse( - tag.tag, - existingTag.product.uuid, - subspaceModel.spaceModel, - ); - } + if (tag.tag !== existingTag.tag) { + if (spaceModel) { + await this.checkTagReuse( + tag.tag, + existingTag.product.uuid, + spaceModel, + ); + } else { + await this.checkTagReuse( + tag.tag, + existingTag.product.uuid, + subspaceModel.spaceModel, + ); + } - if (tag.tag) { - existingTag.tag = tag.tag; + if (tag.tag) { + existingTag.tag = tag.tag; + } } - return await queryRunner.manager.save(existingTag); } catch (error) { if (error instanceof HttpException) { @@ -217,7 +222,10 @@ export class TagModelService { }); if (tagExists) { - throw new HttpException(`Tag can't be reused`, HttpStatus.CONFLICT); + throw new HttpException( + `Tag ${tag} can't be reused`, + HttpStatus.CONFLICT, + ); } } catch (error) { if (error instanceof HttpException) { From 6978ee84fa5eea7be6bc2ec2797b83a19c8da003 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 22 Jan 2025 12:45:10 +0400 Subject: [PATCH 208/247] removed unique subspace name --- .../space-model/entities/subspace-model/subspace-model.entity.ts | 1 - src/space-model/services/subspace/subspace-model.service.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts b/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts index e1d0be0..f5f6377 100644 --- a/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/subspace-model/subspace-model.entity.ts @@ -6,7 +6,6 @@ import { SubspaceEntity } from '@app/common/modules/space/entities'; import { TagModel } from '../tag-model.entity'; @Entity({ name: 'subspace-model' }) -@Unique(['subspaceName', 'spaceModel']) export class SubspaceModelEntity extends AbstractEntity { @Column({ type: 'uuid', diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index e045f36..f309856 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -310,6 +310,7 @@ export class SubSpaceModelService { spaceModel: { uuid: spaceModelUuid, }, + disabled: false, ...(excludeUuid && { uuid: Not(excludeUuid) }), }, }); From d45fe6cb0e0bc523f105cb949a85ef3d221aacae Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 22 Jan 2025 05:33:50 -0600 Subject: [PATCH 209/247] add disabled check --- src/scene/services/scene.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 498d157..42891c1 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -218,6 +218,7 @@ export class SceneService { const scenesData = await this.sceneRepository.find({ where: { space: { uuid: spaceUuid }, + disabled: false, ...(showInHomePage ? { showInHomePage } : {}), }, relations: ['sceneIcon', 'space'], From 19d6669a849830eeea9167bc3ed2dc11bd771dc9 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 22 Jan 2025 06:47:12 -0600 Subject: [PATCH 210/247] Update permission mappings and role permissions --- .../src/constants/permissions-mapping.ts | 10 +++- libs/common/src/constants/role-permissions.ts | 58 +++++++++++-------- src/device/controllers/device.controller.ts | 6 +- src/group/controllers/group.controller.ts | 4 +- .../controllers/space-device.controller.ts | 2 +- .../controllers/space-user.controller.ts | 4 +- .../subspace/subspace-device.controller.ts | 6 +- 7 files changed, 55 insertions(+), 35 deletions(-) diff --git a/libs/common/src/constants/permissions-mapping.ts b/libs/common/src/constants/permissions-mapping.ts index 09a4914..91cc595 100644 --- a/libs/common/src/constants/permissions-mapping.ts +++ b/libs/common/src/constants/permissions-mapping.ts @@ -1,6 +1,14 @@ export const PermissionMapping = { DEVICE_MANAGEMENT: { - DEVICE: ['SINGLE_CONTROL', 'VIEW', 'DELETE', 'UPDATE', 'BATCH_CONTROL'], + DEVICE: [ + 'SINGLE_CONTROL', + 'VIEW', + 'DELETE', + 'UPDATE', + 'BATCH_CONTROL', + 'LOCATION_VIEW', + 'LOCATION_UPDATE', + ], FIRMWARE: ['CONTROL', 'VIEW'], }, COMMUNITY_MANAGEMENT: { diff --git a/libs/common/src/constants/role-permissions.ts b/libs/common/src/constants/role-permissions.ts index 10b6067..906cbd2 100644 --- a/libs/common/src/constants/role-permissions.ts +++ b/libs/common/src/constants/role-permissions.ts @@ -7,11 +7,14 @@ export const RolePermissions = { 'DEVICE_DELETE', 'DEVICE_UPDATE', 'DEVICE_BATCH_CONTROL', + 'DEVICE_LOCATION_VIEW', + 'DEVICE_LOCATION_UPDATE', 'COMMUNITY_VIEW', 'COMMUNITY_ADD', 'COMMUNITY_UPDATE', 'COMMUNITY_DELETE', 'FIRMWARE_CONTROL', + 'FIRMWARE_VIEW', 'SPACE_VIEW', 'SPACE_ADD', 'SPACE_UPDATE', @@ -20,19 +23,19 @@ export const RolePermissions = { 'SPACE_MODEL_VIEW', 'SPACE_MODEL_UPDATE', 'SPACE_MODEL_DELETE', - 'ASSIGN_USER_TO_SPACE', - 'DELETE_USER_FROM_SPACE', + 'SPACE_ASSIGN_USER_TO_SPACE', + 'SPACE_DELETE_USER_FROM_SPACE', 'SUBSPACE_VIEW', 'SUBSPACE_ADD', 'SUBSPACE_UPDATE', 'SUBSPACE_DELETE', - 'ASSIGN_DEVICE_TO_SUBSPACE', - 'DELETE_DEVICE_FROM_SUBSPACE', - 'VIEW_DEVICE_WIZARD', - 'VIEW_DEVICE_IN_SUBSPACE', - 'VIEW_DEVICE_IN_SPACE', - 'UPDATE_DEVICE_IN_SUBSPACE', - 'ASSIGN_DEVICE_TO_SPACE', + 'SUBSPACE_ASSIGN_DEVICE_TO_SUBSPACE', + 'SUBSPACE_DELETE_DEVICE_FROM_SUBSPACE', + 'DEVICE_WIZARD_VIEW_DEVICE_WIZARD', + 'SUBSPACE_DEVICE_VIEW_DEVICE_IN_SUBSPACE', + 'SPACE_DEVICE_VIEW_DEVICE_IN_SPACE', + 'SUBSPACE_DEVICE_UPDATE_DEVICE_IN_SUBSPACE', + 'SPACE_DEVICE_ASSIGN_DEVICE_TO_SPACE', 'AUTOMATION_VIEW', 'AUTOMATION_ADD', 'AUTOMATION_UPDATE', @@ -45,6 +48,8 @@ export const RolePermissions = { 'SCENES_CONTROL', 'VISITOR_PASSWORD_VIEW', 'VISITOR_PASSWORD_ADD', + 'VISITOR_PASSWORD_UPDATE', + 'VISITOR_PASSWORD_DELETE', 'USER_ADD', 'SPACE_MEMBER_ADD', ], @@ -54,6 +59,8 @@ export const RolePermissions = { 'DEVICE_DELETE', 'DEVICE_UPDATE', 'DEVICE_BATCH_CONTROL', + 'DEVICE_LOCATION_VIEW', + 'DEVICE_LOCATION_UPDATE', 'COMMUNITY_VIEW', 'COMMUNITY_ADD', 'COMMUNITY_UPDATE', @@ -67,19 +74,19 @@ export const RolePermissions = { 'SPACE_MODEL_VIEW', 'SPACE_MODEL_UPDATE', 'SPACE_MODEL_DELETE', - 'ASSIGN_USER_TO_SPACE', - 'DELETE_USER_FROM_SPACE', + 'SPACE_ASSIGN_USER_TO_SPACE', + 'SPACE_DELETE_USER_FROM_SPACE', 'SUBSPACE_VIEW', 'SUBSPACE_ADD', 'SUBSPACE_UPDATE', 'SUBSPACE_DELETE', - 'ASSIGN_DEVICE_TO_SUBSPACE', - 'DELETE_DEVICE_FROM_SUBSPACE', - 'VIEW_DEVICE_WIZARD', - 'VIEW_DEVICE_IN_SUBSPACE', - 'VIEW_DEVICE_IN_SPACE', - 'UPDATE_DEVICE_IN_SUBSPACE', - 'ASSIGN_DEVICE_TO_SPACE', + 'SUBSPACE_ASSIGN_DEVICE_TO_SUBSPACE', + 'SUBSPACE_DELETE_DEVICE_FROM_SUBSPACE', + 'DEVICE_WIZARD_VIEW_DEVICE_WIZARD', + 'SUBSPACE_DEVICE_VIEW_DEVICE_IN_SUBSPACE', + 'SPACE_DEVICE_VIEW_DEVICE_IN_SPACE', + 'SUBSPACE_DEVICE_UPDATE_DEVICE_IN_SUBSPACE', + 'SPACE_DEVICE_ASSIGN_DEVICE_TO_SPACE', 'AUTOMATION_VIEW', 'AUTOMATION_ADD', 'AUTOMATION_UPDATE', @@ -92,6 +99,8 @@ export const RolePermissions = { 'SCENES_CONTROL', 'VISITOR_PASSWORD_VIEW', 'VISITOR_PASSWORD_ADD', + 'VISITOR_PASSWORD_UPDATE', + 'VISITOR_PASSWORD_DELETE', 'USER_ADD', 'SPACE_MEMBER_ADD', ], @@ -100,14 +109,13 @@ export const RolePermissions = { 'DEVICE_VIEW', 'SPACE_VIEW', 'SUBSPACE_VIEW', - 'VIEW_DEVICE_WIZARD', - 'VIEW_DEVICE_IN_SUBSPACE', - 'VIEW_DEVICE_IN_SPACE', + 'DEVICE_WIZARD_VIEW_DEVICE_WIZARD', + 'SUBSPACE_DEVICE_VIEW_DEVICE_IN_SUBSPACE', + 'SPACE_DEVICE_VIEW_DEVICE_IN_SPACE', 'AUTOMATION_VIEW', 'AUTOMATION_CONTROL', 'SCENES_VIEW', 'SCENES_CONTROL', - 'VISITOR_PASSWORD_VIEW', ], [RoleType.SPACE_OWNER]: [ 'DEVICE_SINGLE_CONTROL', @@ -115,6 +123,8 @@ export const RolePermissions = { 'FIRMWARE_CONTROL', 'FIRMWARE_VIEW', 'SPACE_VIEW', + 'DEVICE_LOCATION_VIEW', + 'DEVICE_LOCATION_UPDATE', 'SPACE_MEMBER_ADD', 'SUBSPACE_VIEW', 'SUBSPACE_ADD', @@ -134,6 +144,8 @@ export const RolePermissions = { 'VISITOR_PASSWORD_ADD', 'VISITOR_PASSWORD_UPDATE', 'VISITOR_PASSWORD_DELETE', - 'VIEW_DEVICE_WIZARD', + 'DEVICE_WIZARD_VIEW_DEVICE_WIZARD', + 'SPACE_ASSIGN_USER_TO_SPACE', + 'SPACE_DELETE_USER_FROM_SPACE', ], }; diff --git a/src/device/controllers/device.controller.ts b/src/device/controllers/device.controller.ts index 26b49ea..1ec8c6c 100644 --- a/src/device/controllers/device.controller.ts +++ b/src/device/controllers/device.controller.ts @@ -47,7 +47,7 @@ export class DeviceController { constructor(private readonly deviceService: DeviceService) {} @ApiBearerAuth() @UseGuards(PermissionsGuard, CheckDeviceGuard) - @Permissions('ASSIGN_DEVICE_TO_SPACE') + @Permissions('SPACE_DEVICE_ASSIGN_DEVICE_TO_SPACE') @Post() @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.ADD_DEVICE_TO_USER_SUMMARY, @@ -77,7 +77,7 @@ export class DeviceController { @ApiBearerAuth() @UseGuards(PermissionsGuard) - @Permissions('VIEW_DEVICE_IN_SPACE') + @Permissions('SPACE_DEVICE_VIEW_DEVICE_IN_SPACE') @Get('space/:spaceUuid') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.GET_DEVICES_BY_SPACE_UUID_SUMMARY, @@ -89,7 +89,7 @@ export class DeviceController { } @ApiBearerAuth() @UseGuards(PermissionsGuard, CheckRoomGuard) - @Permissions('UPDATE_DEVICE_IN_SUBSPACE') + @Permissions('SUBSPACE_DEVICE_UPDATE_DEVICE_IN_SUBSPACE') @Put('space') @ApiOperation({ summary: ControllerRoute.DEVICE.ACTIONS.UPDATE_DEVICE_IN_ROOM_SUMMARY, diff --git a/src/group/controllers/group.controller.ts b/src/group/controllers/group.controller.ts index 513bbeb..a183cad 100644 --- a/src/group/controllers/group.controller.ts +++ b/src/group/controllers/group.controller.ts @@ -16,7 +16,7 @@ export class GroupController { @ApiBearerAuth() @UseGuards(PermissionsGuard) - @Permissions('VIEW_DEVICE_WIZARD') + @Permissions('DEVICE_WIZARD_VIEW_DEVICE_WIZARD') @Get(':spaceUuid') @ApiOperation({ summary: ControllerRoute.GROUP.ACTIONS.GET_GROUPS_BY_SPACE_UUID_SUMMARY, @@ -29,7 +29,7 @@ export class GroupController { @ApiBearerAuth() @UseGuards(PermissionsGuard) - @Permissions('VIEW_DEVICE_WIZARD') + @Permissions('DEVICE_WIZARD_VIEW_DEVICE_WIZARD') @Get(':spaceUuid/devices/:groupName') @ApiOperation({ summary: diff --git a/src/space/controllers/space-device.controller.ts b/src/space/controllers/space-device.controller.ts index dbb1585..7009531 100644 --- a/src/space/controllers/space-device.controller.ts +++ b/src/space/controllers/space-device.controller.ts @@ -17,7 +17,7 @@ export class SpaceDeviceController { @ApiBearerAuth() @UseGuards(PermissionsGuard) - @Permissions('VIEW_DEVICE_IN_SPACE') + @Permissions('SPACE_DEVICE_VIEW_DEVICE_IN_SPACE') @ApiOperation({ summary: ControllerRoute.SPACE_DEVICES.ACTIONS.LIST_SPACE_DEVICE_SUMMARY, description: diff --git a/src/space/controllers/space-user.controller.ts b/src/space/controllers/space-user.controller.ts index faf887a..d2709ad 100644 --- a/src/space/controllers/space-user.controller.ts +++ b/src/space/controllers/space-user.controller.ts @@ -18,7 +18,7 @@ export class SpaceUserController { @ApiBearerAuth() @Post('/:userUuid') @UseGuards(PermissionsGuard) - @Permissions('ASSIGN_USER_TO_SPACE') + @Permissions('SPACE_ASSIGN_USER_TO_SPACE') @ApiOperation({ summary: ControllerRoute.SPACE_USER.ACTIONS.ASSOCIATE_SPACE_USER_DESCRIPTION, @@ -34,7 +34,7 @@ export class SpaceUserController { @ApiBearerAuth() @Delete('/:userUuid') @UseGuards(PermissionsGuard) - @Permissions('DELETE_USER_FROM_SPACE') + @Permissions('SPACE_ASSIGN_USER_TO_SPACE') @ApiOperation({ summary: ControllerRoute.SPACE_USER.ACTIONS.DISSOCIATE_SPACE_USER_SUMMARY, description: diff --git a/src/space/controllers/subspace/subspace-device.controller.ts b/src/space/controllers/subspace/subspace-device.controller.ts index 1bb3db4..664cf38 100644 --- a/src/space/controllers/subspace/subspace-device.controller.ts +++ b/src/space/controllers/subspace/subspace-device.controller.ts @@ -24,7 +24,7 @@ export class SubSpaceDeviceController { @ApiBearerAuth() @UseGuards(PermissionsGuard) - @Permissions('VIEW_DEVICE_IN_SUBSPACE') + @Permissions('SUBSPACE_DEVICE_VIEW_DEVICE_IN_SUBSPACE') @ApiOperation({ summary: ControllerRoute.SUBSPACE_DEVICE.ACTIONS.LIST_SUBSPACE_DEVICE_SUMMARY, @@ -40,7 +40,7 @@ export class SubSpaceDeviceController { @ApiBearerAuth() @UseGuards(PermissionsGuard) - @Permissions('ASSIGN_DEVICE_TO_SUBSPACE') + @Permissions('SUBSPACE_ASSIGN_DEVICE_TO_SUBSPACE') @ApiOperation({ summary: ControllerRoute.SUBSPACE_DEVICE.ACTIONS.ASSOCIATE_SUBSPACE_DEVICE_SUMMARY, @@ -57,7 +57,7 @@ export class SubSpaceDeviceController { @ApiBearerAuth() @UseGuards(PermissionsGuard) - @Permissions('DELETE_DEVICE_FROM_SUBSPACE') + @Permissions('SUBSPACE_DELETE_DEVICE_FROM_SUBSPACE') @ApiOperation({ summary: ControllerRoute.SUBSPACE_DEVICE.ACTIONS From c1aa5232c75f635842f98fcf4c2324613ddf258c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 24 Jan 2025 20:56:50 +0400 Subject: [PATCH 211/247] fixed delete --- .../tag-model-dtos/create-tag-model.dto.ts | 12 +- .../services/space-model.service.ts | 14 +- .../subspace/subspace-model.service.ts | 4 +- src/space-model/services/tag-model.service.ts | 124 +++++++++++++++++- 4 files changed, 143 insertions(+), 11 deletions(-) diff --git a/src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts b/src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts index 5f4ec66..c271eef 100644 --- a/src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts +++ b/src/space-model/dtos/tag-model-dtos/create-tag-model.dto.ts @@ -1,5 +1,5 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class CreateTagModelDto { @ApiProperty({ @@ -10,6 +10,14 @@ export class CreateTagModelDto { @IsString() tag: string; + @ApiPropertyOptional({ + description: 'UUID of the tag model (required for update/delete)', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @IsOptional() + @IsString() + uuid?: string; + @ApiProperty({ description: 'ID of the product associated with the tag', example: '123e4567-e89b-12d3-a456-426614174000', diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 5ef531c..98d6c78 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -89,6 +89,8 @@ export class SpaceModelService { statusCode: HttpStatus.CREATED, }); } catch (error) { + console.log(JSON.stringify(createSpaceModelDto)); + await queryRunner.rollbackTransaction(); const errorMessage = @@ -177,7 +179,6 @@ export class SpaceModelService { async update(dto: UpdateSpaceModelDto, param: SpaceModelParam) { const queryRunner = this.dataSource.createQueryRunner(); - await this.validateProject(param.projectUuid); const spaceModel = await this.validateSpaceModel(param.spaceModelUuid); await queryRunner.connect(); @@ -201,6 +202,12 @@ export class SpaceModelService { ); } + const spaceTagsAfterMove = this.tagModelService.getSubspaceTagsToBeAdded( + dto.tags, + dto.subspaceModels, + ); + console.log(spaceTagsAfterMove); + if (dto.subspaceModels) { modifiedSubspaceModels = await this.subSpaceModelService.modifySubSpaceModels( @@ -212,9 +219,10 @@ export class SpaceModelService { if (dto.tags) { modifiedTagsModelPayload = await this.tagModelService.modifyTags( - dto.tags, + spaceTagsAfterMove, queryRunner, spaceModel, + null, ); } @@ -235,8 +243,8 @@ export class SpaceModelService { message: 'SpaceModel updated successfully', }); } catch (error) { + console.log(JSON.stringify(dto)); await queryRunner.rollbackTransaction(); - if (error instanceof HttpException) { throw error; } diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index f309856..dd3fe91 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -50,8 +50,7 @@ export class SubSpaceModelService { const otherDtoTags = subSpaceModelDtos .filter((_, i) => i !== index) .flatMap((otherDto) => otherDto.tags || []); - - if (dto.tags?.length) { + if (dto.tags && dto.tags.length > 0) { subspace.tags = await this.tagModelService.createTags( dto.tags, queryRunner, @@ -174,6 +173,7 @@ export class SubSpaceModelService { const createTagDtos: CreateTagModelDto[] = subspace.tags?.map((tag) => ({ tag: tag.tag, + uuid: tag.uuid, productUuid: tag.productUuid, })) || []; diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index 14f2cf2..abb0d3a 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -6,7 +6,11 @@ import { } from '@app/common/modules/space-model/entities'; import { SubspaceModelEntity } from '@app/common/modules/space-model/entities'; import { TagModelRepository } from '@app/common/modules/space-model'; -import { CreateTagModelDto, ModifyTagModelDto } from '../dtos'; +import { + CreateTagModelDto, + ModifySubspaceModelDto, + ModifyTagModelDto, +} from '../dtos'; import { ProductService } from 'src/product/services'; import { ModifyAction } from '@app/common/constants/modify-action.enum'; import { ModifiedTagsModelPayload } from '../interfaces'; @@ -39,11 +43,55 @@ export class TagModelService { ); } + const tagEntitiesToCreate = tags.filter((tagDto) => tagDto.uuid === null); + const tagEntitiesToUpdate = tags.filter((tagDto) => tagDto.uuid !== null); + + try { + const createdTags = await this.bulkSaveTags( + tagEntitiesToCreate, + queryRunner, + spaceModel, + subspaceModel, + ); + + // Update existing tags + const updatedTags = await this.moveTags( + tagEntitiesToUpdate, + queryRunner, + spaceModel, + subspaceModel, + ); + + // Combine created and updated tags + return [...createdTags, ...updatedTags]; + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + `Failed to create tag models due to an unexpected error.: ${error}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async bulkSaveTags( + tags: CreateTagModelDto[], + queryRunner: QueryRunner, + spaceModel?: SpaceModelEntity, + subspaceModel?: SubspaceModelEntity, + ): Promise { + if (!tags.length) { + return []; + } + const tagEntities = await Promise.all( - tags.map(async (tagDto) => + tags.map((tagDto) => this.prepareTagEntity(tagDto, queryRunner, spaceModel, subspaceModel), ), ); + try { return await queryRunner.manager.save(tagEntities); } catch (error) { @@ -52,12 +100,46 @@ export class TagModelService { } throw new HttpException( - 'Failed to save tag models due to an unexpected error.', + `Failed to save tag models due to an unexpected error: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } } + async moveTags( + tags: CreateTagModelDto[], + queryRunner: QueryRunner, + spaceModel?: SpaceModelEntity, + subspaceModel?: SubspaceModelEntity, + ): Promise { + if (!tags.length) { + return []; + } + + return await Promise.all( + tags.map(async (tagDto) => { + const tag = await this.getTagByUuid(tagDto.uuid); + if (!tag) { + throw new HttpException( + `Tag with UUID ${tagDto.uuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + + if (subspaceModel && subspaceModel.spaceModel) { + await queryRunner.manager.update( + this.tagModelRepository.target, + { uuid: tag.uuid }, + { subspaceModel }, + ); + tag.subspaceModel = subspaceModel; + } + + return tag; + }), + ); + } + async updateTag( tag: ModifyTagModelDto, queryRunner: QueryRunner, @@ -153,6 +235,7 @@ export class TagModelService { if (tag.action === ModifyAction.ADD) { const createTagDto: CreateTagModelDto = { tag: tag.tag as string, + uuid: tag.uuid, productUuid: tag.productUuid as string, }; @@ -268,7 +351,6 @@ export class TagModelService { HttpStatus.BAD_REQUEST, ); } - return queryRunner.manager.create(TagModel, { tag: tagDto.tag, product: product.data, @@ -333,4 +415,38 @@ export class TagModelService { return existingTag; } + + getSubspaceTagsToBeAdded( + spaceTags: ModifyTagModelDto[], + subspaceModels: ModifySubspaceModelDto[], + ): ModifyTagModelDto[] { + if (!subspaceModels || subspaceModels.length === 0) { + return spaceTags; + } + + const spaceTagsToDelete = spaceTags.filter( + (tag) => tag.action === 'delete', + ); + + const tagsToAdd = subspaceModels.flatMap( + (subspace) => subspace.tags?.filter((tag) => tag.action === 'add') || [], + ); + + const commonTagUuids = new Set( + tagsToAdd + .filter((tagToAdd) => + spaceTagsToDelete.some( + (tagToDelete) => tagToAdd.uuid === tagToDelete.uuid, + ), + ) + .map((tag) => tag.uuid), + ); + + const remainingTags = spaceTags.filter( + (tag) => + !tag.uuid || commonTagUuids.size === 0 || commonTagUuids.has(tag.uuid), + ); + + return remainingTags; + } } From 65e585c8f2d04f618da0f3625f91e1354b2e2eee Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 26 Jan 2025 11:59:15 -0600 Subject: [PATCH 212/247] Add query parameter to filter spaces with devices --- src/space/controllers/space.controller.ts | 8 +++++++- src/space/dtos/get.space.dto.ts | 19 +++++++++++++++++++ src/space/services/space.service.ts | 19 ++++++++++++++----- 3 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 src/space/dtos/get.space.dto.ts diff --git a/src/space/controllers/space.controller.ts b/src/space/controllers/space.controller.ts index bb18516..ca47488 100644 --- a/src/space/controllers/space.controller.ts +++ b/src/space/controllers/space.controller.ts @@ -9,6 +9,7 @@ import { Param, Post, Put, + Query, UseGuards, } from '@nestjs/common'; import { AddSpaceDto, CommunitySpaceParam, UpdateSpaceDto } from '../dtos'; @@ -16,6 +17,7 @@ import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { GetSpaceParam } from '../dtos/get.space.param'; import { PermissionsGuard } from 'src/guards/permissions.guard'; import { Permissions } from 'src/decorators/permissions.decorator'; +import { GetSpaceDto } from '../dtos/get.space.dto'; @ApiTags('Space Module') @Controller({ @@ -55,8 +57,12 @@ export class SpaceController { @Get() async getHierarchy( @Param() params: CommunitySpaceParam, + @Query() getSpaceDto: GetSpaceDto, ): Promise { - return this.spaceService.getSpacesHierarchyForCommunity(params); + return this.spaceService.getSpacesHierarchyForCommunity( + params, + getSpaceDto, + ); } @ApiBearerAuth() diff --git a/src/space/dtos/get.space.dto.ts b/src/space/dtos/get.space.dto.ts new file mode 100644 index 0000000..d0b6475 --- /dev/null +++ b/src/space/dtos/get.space.dto.ts @@ -0,0 +1,19 @@ +import { BooleanValues } from '@app/common/constants/boolean-values.enum'; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { IsBoolean, IsOptional } from 'class-validator'; + +export class GetSpaceDto { + @ApiProperty({ + example: true, + description: 'Only return spaces with devices', + required: false, + default: false, + }) + @IsOptional() + @IsBoolean() + @Transform((value) => { + return value.obj.onlyWithDevices === BooleanValues.TRUE; + }) + public onlyWithDevices?: boolean = false; +} diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index d60abb4..267d1d1 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -32,6 +32,7 @@ import { CommandBus } from '@nestjs/cqrs'; import { TagService } from './tag'; import { SpaceModelService } from 'src/space-model/services'; import { DisableSpaceCommand } from '../commands'; +import { GetSpaceDto } from '../dtos/get.space.dto'; @Injectable() export class SpaceService { constructor( @@ -167,15 +168,18 @@ export class SpaceService { async getSpacesHierarchyForCommunity( params: CommunitySpaceParam, + getSpaceDto?: GetSpaceDto, ): Promise { const { communityUuid, projectUuid } = params; + const { onlyWithDevices } = getSpaceDto; + console.log('onlyWithDevices', onlyWithDevices); + await this.validationService.validateCommunityAndProject( communityUuid, projectUuid, ); try { - // Get all spaces related to the community, including the parent-child relations - const spaces = await this.spaceRepository + const queryBuilder = this.spaceRepository .createQueryBuilder('space') .leftJoinAndSelect('space.parent', 'parent') .leftJoinAndSelect( @@ -214,14 +218,19 @@ export class SpaceService { .andWhere('space.spaceName != :orphanSpaceName', { orphanSpaceName: ORPHAN_SPACE_NAME, }) - .andWhere('space.disabled = :disabled', { disabled: false }) - .getMany(); + .andWhere('space.disabled = :disabled', { disabled: false }); + + if (onlyWithDevices) { + queryBuilder.innerJoin('space.devices', 'devices'); + } + + const spaces = await queryBuilder.getMany(); const spaceHierarchy = this.buildSpaceHierarchy(spaces); return new SuccessResponseDto({ message: `Spaces in community ${communityUuid} successfully fetched in hierarchy`, - data: spaceHierarchy, + data: onlyWithDevices ? spaces : spaceHierarchy, statusCode: HttpStatus.OK, }); } catch (error) { From 91f831f817951fc8d4095bf45339d52b352b8749 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sun, 26 Jan 2025 13:46:33 -0600 Subject: [PATCH 213/247] Change deviceId column type to uuid --- .../device-status-log/entities/device-status-log.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/modules/device-status-log/entities/device-status-log.entity.ts b/libs/common/src/modules/device-status-log/entities/device-status-log.entity.ts index a7a998a..b40c393 100644 --- a/libs/common/src/modules/device-status-log/entities/device-status-log.entity.ts +++ b/libs/common/src/modules/device-status-log/entities/device-status-log.entity.ts @@ -20,7 +20,7 @@ export class DeviceStatusLogEntity { }) eventFrom: SourceType; - @Column({ type: 'text' }) + @Column({ type: 'uuid' }) deviceId: string; @Column({ type: 'text' }) From ec06434191e65567e877a491024b1babd500c3b2 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 27 Jan 2025 02:01:40 -0600 Subject: [PATCH 214/247] Add tag and subspace fields to device validation --- src/space/services/space-validation.service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts index d9a9f04..4a3e32a 100644 --- a/src/space/services/space-validation.service.ts +++ b/src/space/services/space-validation.service.ts @@ -52,6 +52,8 @@ export class ValidationService { 'subspaces.devices', 'devices', 'devices.productDevice', + 'devices.tag', + 'devices.subspace', ], }); From 666fa67784a8c82f21a4eff4d0ae77c542dbc84a Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 27 Jan 2025 02:03:35 -0600 Subject: [PATCH 215/247] return device status for each device --- .../tuya/services/tuya.service.ts | 21 +++++++++++++++++++ src/space/services/space-device.service.ts | 8 ++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/libs/common/src/integrations/tuya/services/tuya.service.ts b/libs/common/src/integrations/tuya/services/tuya.service.ts index e9c937f..67a4eff 100644 --- a/libs/common/src/integrations/tuya/services/tuya.service.ts +++ b/libs/common/src/integrations/tuya/services/tuya.service.ts @@ -6,6 +6,7 @@ import { ConvertedAction, TuyaResponseInterface, } from '../interfaces'; +import { GetDeviceDetailsFunctionsStatusInterface } from 'src/device/interfaces/get.device.interface'; @Injectable() export class TuyaService { @@ -284,4 +285,24 @@ export class TuyaService { ); } } + async getDevicesInstructionStatusTuya( + deviceUuid: string, + ): Promise { + try { + const path = `/v1.0/iot-03/devices/status`; + const response = await this.tuya.request({ + method: 'GET', + path, + query: { + device_ids: deviceUuid, + }, + }); + return response as GetDeviceDetailsFunctionsStatusInterface; + } catch (error) { + throw new HttpException( + 'Error fetching device functions status from Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index 75746ca..505e49f 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -37,7 +37,10 @@ export class SpaceDeviceService { const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya( device.deviceTuyaUuid, ); - + const tuyaDeviceStatus = + await this.tuyaService.getDevicesInstructionStatusTuya( + device.deviceTuyaUuid, + ); return { uuid: device.uuid, deviceTuyaUuid: device.deviceTuyaUuid, @@ -45,7 +48,10 @@ export class SpaceDeviceService { productType: device.productDevice.prodType, isActive: device.isActive, updatedAt: device.updatedAt, + deviceTag: device.tag, + subspace: device.subspace, ...tuyaDetails, + status: tuyaDeviceStatus.result[0].status, }; } catch (error) { console.warn( From be60e02b9950e8ac4cdbb162a6e879055ddcc687 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 27 Jan 2025 12:54:38 +0400 Subject: [PATCH 216/247] fixed moving tags --- .../space-model/entities/tag-model.entity.ts | 10 +- .../services/space-model.service.ts | 11 +- src/space-model/services/tag-model.service.ts | 161 +++++++++++++++--- 3 files changed, 140 insertions(+), 42 deletions(-) diff --git a/libs/common/src/modules/space-model/entities/tag-model.entity.ts b/libs/common/src/modules/space-model/entities/tag-model.entity.ts index 3f7805c..e2a70ee 100644 --- a/libs/common/src/modules/space-model/entities/tag-model.entity.ts +++ b/libs/common/src/modules/space-model/entities/tag-model.entity.ts @@ -1,11 +1,4 @@ -import { - Column, - Entity, - JoinColumn, - ManyToOne, - OneToMany, - Unique, -} from 'typeorm'; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { TagModelDto } from '../dtos/tag-model.dto'; import { SpaceModelEntity } from './space-model.entity'; @@ -14,7 +7,6 @@ import { ProductEntity } from '../../product/entities'; import { TagEntity } from '../../space/entities/tag.entity'; @Entity({ name: 'tag_model' }) -@Unique(['tag', 'product', 'spaceModel', 'subspaceModel']) export class TagModel extends AbstractEntity { @Column({ type: 'varchar', length: 255 }) tag: string; diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 98d6c78..05906ca 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -89,8 +89,6 @@ export class SpaceModelService { statusCode: HttpStatus.CREATED, }); } catch (error) { - console.log(JSON.stringify(createSpaceModelDto)); - await queryRunner.rollbackTransaction(); const errorMessage = @@ -206,12 +204,16 @@ export class SpaceModelService { dto.tags, dto.subspaceModels, ); - console.log(spaceTagsAfterMove); + + const modifiedSubspaces = this.tagModelService.getModifiedSubspaces( + dto.tags, + dto.subspaceModels, + ); if (dto.subspaceModels) { modifiedSubspaceModels = await this.subSpaceModelService.modifySubSpaceModels( - dto.subspaceModels, + modifiedSubspaces, spaceModel, queryRunner, ); @@ -243,7 +245,6 @@ export class SpaceModelService { message: 'SpaceModel updated successfully', }); } catch (error) { - console.log(JSON.stringify(dto)); await queryRunner.rollbackTransaction(); if (error instanceof HttpException) { throw error; diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index abb0d3a..0fb1942 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -28,6 +28,7 @@ export class TagModelService { spaceModel?: SpaceModelEntity, subspaceModel?: SubspaceModelEntity, additionalTags?: CreateTagModelDto[], + tagsToDelete?: ModifyTagModelDto[], ): Promise { if (!tags.length) { throw new HttpException('Tags cannot be empty.', HttpStatus.BAD_REQUEST); @@ -46,12 +47,14 @@ export class TagModelService { const tagEntitiesToCreate = tags.filter((tagDto) => tagDto.uuid === null); const tagEntitiesToUpdate = tags.filter((tagDto) => tagDto.uuid !== null); + try { const createdTags = await this.bulkSaveTags( tagEntitiesToCreate, queryRunner, spaceModel, subspaceModel, + tagsToDelete, ); // Update existing tags @@ -81,6 +84,7 @@ export class TagModelService { queryRunner: QueryRunner, spaceModel?: SpaceModelEntity, subspaceModel?: SubspaceModelEntity, + tagsToDelete?: ModifyTagModelDto[], ): Promise { if (!tags.length) { return []; @@ -88,7 +92,13 @@ export class TagModelService { const tagEntities = await Promise.all( tags.map((tagDto) => - this.prepareTagEntity(tagDto, queryRunner, spaceModel, subspaceModel), + this.prepareTagEntity( + tagDto, + queryRunner, + spaceModel, + subspaceModel, + tagsToDelete, + ), ), ); @@ -116,28 +126,53 @@ export class TagModelService { return []; } - return await Promise.all( - tags.map(async (tagDto) => { - const tag = await this.getTagByUuid(tagDto.uuid); - if (!tag) { - throw new HttpException( - `Tag with UUID ${tagDto.uuid} not found.`, - HttpStatus.NOT_FOUND, - ); - } + try { + return await Promise.all( + tags.map(async (tagDto) => { + try { + const tag = await this.getTagByUuid(tagDto.uuid); + if (!tag) { + throw new HttpException( + `Tag with UUID ${tagDto.uuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + + if (subspaceModel && subspaceModel.spaceModel) { + await queryRunner.manager.update( + this.tagModelRepository.target, + { uuid: tag.uuid }, + { subspaceModel, spaceModel: null }, + ); + tag.subspaceModel = subspaceModel; + } - if (subspaceModel && subspaceModel.spaceModel) { - await queryRunner.manager.update( - this.tagModelRepository.target, - { uuid: tag.uuid }, - { subspaceModel }, - ); - tag.subspaceModel = subspaceModel; - } + if (subspaceModel === null && spaceModel) { + await queryRunner.manager.update( + this.tagModelRepository.target, + { uuid: tag.uuid }, + { subspaceModel: null, spaceModel: spaceModel }, + ); + tag.subspaceModel = null; + tag.spaceModel = spaceModel; + } - return tag; - }), - ); + return tag; + } catch (error) { + console.error( + `Error moving tag with UUID ${tagDto.uuid}: ${error.message}`, + ); + throw error; // Re-throw the error to propagate it to the parent Promise.all + } + }), + ); + } catch (error) { + console.error(`Error in moveTags: ${error.message}`); + throw new HttpException( + `Failed to move tags due to an unexpected error: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } } async updateTag( @@ -231,6 +266,10 @@ export class TagModelService { deleted: [], }; try { + const tagsToDelete = tags.filter( + (tag) => tag.action === ModifyAction.DELETE, + ); + for (const tag of tags) { if (tag.action === ModifyAction.ADD) { const createTagDto: CreateTagModelDto = { @@ -239,11 +278,14 @@ export class TagModelService { productUuid: tag.productUuid as string, }; + const newModel = await this.createTags( [createTagDto], queryRunner, spaceModel, subspaceModel, + null, + tagsToDelete, ); modifiedTagModels.added.push(...newModel); } else if (tag.action === ModifyAction.UPDATE) { @@ -285,9 +327,11 @@ export class TagModelService { tag: string, productUuid: string, spaceModel: SpaceModelEntity, + tagsToDelete?: ModifyTagModelDto[], ): Promise { try { - const tagExists = await this.tagModelRepository.exists({ + // Query to find existing tags + const tagExists = await this.tagModelRepository.find({ where: [ { tag, @@ -304,16 +348,29 @@ export class TagModelService { ], }); - if (tagExists) { + + // Remove tags that are marked for deletion + const filteredTagExists = tagExists.filter( + (existingTag) => + !tagsToDelete?.some( + (deleteTag) => deleteTag.uuid === existingTag.uuid, + ), + ); + + + // If any tags remain, throw an exception + if (filteredTagExists.length > 0) { throw new HttpException( `Tag ${tag} can't be reused`, HttpStatus.CONFLICT, ); } + } catch (error) { if (error instanceof HttpException) { throw error; } + console.error(`Error while checking tag reuse: ${error.message}`); throw new HttpException( `An error occurred while checking tag reuse: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR, @@ -326,6 +383,7 @@ export class TagModelService { queryRunner: QueryRunner, spaceModel?: SpaceModelEntity, subspaceModel?: SubspaceModelEntity, + tagsToDelete?: ModifyTagModelDto[], ): Promise { try { const product = await this.productService.findOne(tagDto.productUuid); @@ -338,7 +396,12 @@ export class TagModelService { } if (spaceModel) { - await this.checkTagReuse(tagDto.tag, tagDto.productUuid, spaceModel); + await this.checkTagReuse( + tagDto.tag, + tagDto.productUuid, + spaceModel, + tagsToDelete, + ); } else if (subspaceModel && subspaceModel.spaceModel) { await this.checkTagReuse( tagDto.tag, @@ -351,11 +414,14 @@ export class TagModelService { HttpStatus.BAD_REQUEST, ); } + const tagSpace = + spaceModel !== null ? spaceModel : subspaceModel.spaceModel; + return queryRunner.manager.create(TagModel, { tag: tagDto.tag, product: product.data, - spaceModel, - subspaceModel, + spaceModel: tagSpace, + subspaceModel: subspaceModel, }); } catch (error) { if (error instanceof HttpException) { @@ -421,16 +487,20 @@ export class TagModelService { subspaceModels: ModifySubspaceModelDto[], ): ModifyTagModelDto[] { if (!subspaceModels || subspaceModels.length === 0) { + return spaceTags; } + const spaceTagsToDelete = spaceTags.filter( (tag) => tag.action === 'delete', ); + const tagsToAdd = subspaceModels.flatMap( (subspace) => subspace.tags?.filter((tag) => tag.action === 'add') || [], ); + const commonTagUuids = new Set( tagsToAdd @@ -443,10 +513,45 @@ export class TagModelService { ); const remainingTags = spaceTags.filter( - (tag) => - !tag.uuid || commonTagUuids.size === 0 || commonTagUuids.has(tag.uuid), + (tag) => !commonTagUuids.has(tag.uuid), // Exclude tags in commonTagUuids ); return remainingTags; } + + getModifiedSubspaces( + spaceTags: ModifyTagModelDto[], + subspaceModels: ModifySubspaceModelDto[], + ): ModifySubspaceModelDto[] { + if (!subspaceModels || subspaceModels.length === 0) { + return []; + } + + // Extract tags marked for addition in spaceTags + const spaceTagsToAdd = spaceTags.filter((tag) => tag.action === 'add'); + + // Find UUIDs of tags that are common between spaceTagsToAdd and subspace tags marked for deletion + const commonTagUuids = new Set( + spaceTagsToAdd + .flatMap((tagToAdd) => + subspaceModels.flatMap( + (subspace) => + subspace.tags?.filter( + (tagToDelete) => + tagToDelete.action === 'delete' && + tagToAdd.uuid === tagToDelete.uuid, + ) || [], + ), + ) + .map((tag) => tag.uuid), + ); + + // Modify subspaceModels by removing tags with UUIDs present in commonTagUuids + const modifiedSubspaces = subspaceModels.map((subspace) => ({ + ...subspace, + tags: subspace.tags?.filter((tag) => !commonTagUuids.has(tag.uuid)) || [], + })); + + return modifiedSubspaces; + } } From 96bf8ed7f8fa3041a5e30fe2b64940608e53af35 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 27 Jan 2025 12:54:46 +0400 Subject: [PATCH 217/247] prettier --- src/space-model/services/tag-model.service.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index 0fb1942..0f77b3d 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -47,7 +47,6 @@ export class TagModelService { const tagEntitiesToCreate = tags.filter((tagDto) => tagDto.uuid === null); const tagEntitiesToUpdate = tags.filter((tagDto) => tagDto.uuid !== null); - try { const createdTags = await this.bulkSaveTags( tagEntitiesToCreate, @@ -137,7 +136,7 @@ export class TagModelService { HttpStatus.NOT_FOUND, ); } - + if (subspaceModel && subspaceModel.spaceModel) { await queryRunner.manager.update( this.tagModelRepository.target, @@ -278,7 +277,6 @@ export class TagModelService { productUuid: tag.productUuid as string, }; - const newModel = await this.createTags( [createTagDto], queryRunner, @@ -348,7 +346,6 @@ export class TagModelService { ], }); - // Remove tags that are marked for deletion const filteredTagExists = tagExists.filter( (existingTag) => @@ -357,7 +354,6 @@ export class TagModelService { ), ); - // If any tags remain, throw an exception if (filteredTagExists.length > 0) { throw new HttpException( @@ -365,7 +361,6 @@ export class TagModelService { HttpStatus.CONFLICT, ); } - } catch (error) { if (error instanceof HttpException) { throw error; @@ -487,20 +482,16 @@ export class TagModelService { subspaceModels: ModifySubspaceModelDto[], ): ModifyTagModelDto[] { if (!subspaceModels || subspaceModels.length === 0) { - return spaceTags; } - const spaceTagsToDelete = spaceTags.filter( (tag) => tag.action === 'delete', ); - const tagsToAdd = subspaceModels.flatMap( (subspace) => subspace.tags?.filter((tag) => tag.action === 'add') || [], ); - const commonTagUuids = new Set( tagsToAdd From 903533cd86753df6f0368082ce19745c7264cf54 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 27 Jan 2025 15:19:35 +0400 Subject: [PATCH 218/247] fix create subspace --- src/space/dtos/subspace/add.subspace.dto.ts | 9 ++++++++- src/space/services/subspace/subspace.service.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/space/dtos/subspace/add.subspace.dto.ts b/src/space/dtos/subspace/add.subspace.dto.ts index 98b381a..95ad0fb 100644 --- a/src/space/dtos/subspace/add.subspace.dto.ts +++ b/src/space/dtos/subspace/add.subspace.dto.ts @@ -1,5 +1,11 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsArray, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; +import { + IsArray, + IsNotEmpty, + IsOptional, + IsString, + ValidateNested, +} from 'class-validator'; import { CreateTagDto } from '../tag'; import { Type } from 'class-transformer'; @@ -19,5 +25,6 @@ export class AddSubspaceDto { @IsArray() @ValidateNested({ each: true }) @Type(() => CreateTagDto) + @IsOptional() tags?: CreateTagDto[]; } diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 7c5cf6d..599a282 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -146,7 +146,7 @@ export class SubSpaceService { ): Promise { const space = await this.validationService.validateSpaceWithinCommunityAndProject( - params.projectUuid, + params.communityUuid, params.projectUuid, params.spaceUuid, ); From 9a163b0e8595ce3b5e34ab938aaf039f87ade737 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 28 Jan 2025 03:42:52 -0600 Subject: [PATCH 219/247] Add Automation Module and Entity --- libs/common/src/database/database.module.ts | 2 + .../automation.repository.module.ts | 11 ++++++ .../modules/automation/dtos/automation.dto.ts | 14 +++++++ .../src/modules/automation/dtos/index.ts | 1 + .../automation/entities/automation.entity.ts | 37 +++++++++++++++++++ .../src/modules/automation/entities/index.ts | 1 + .../repositories/automation.repository.ts | 10 +++++ 7 files changed, 76 insertions(+) create mode 100644 libs/common/src/modules/automation/automation.repository.module.ts create mode 100644 libs/common/src/modules/automation/dtos/automation.dto.ts create mode 100644 libs/common/src/modules/automation/dtos/index.ts create mode 100644 libs/common/src/modules/automation/entities/automation.entity.ts create mode 100644 libs/common/src/modules/automation/entities/index.ts create mode 100644 libs/common/src/modules/automation/repositories/automation.repository.ts diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index cd83fa0..fae80e0 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -37,6 +37,7 @@ import { InviteUserSpaceEntity, } from '../modules/Invite-user/entities'; import { InviteSpaceEntity } from '../modules/space/entities/invite-space.entity'; +import { AutomationEntity } from '../modules/automation/entities'; @Module({ imports: [ TypeOrmModule.forRootAsync({ @@ -82,6 +83,7 @@ import { InviteSpaceEntity } from '../modules/space/entities/invite-space.entity InviteUserEntity, InviteUserSpaceEntity, InviteSpaceEntity, + AutomationEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), diff --git a/libs/common/src/modules/automation/automation.repository.module.ts b/libs/common/src/modules/automation/automation.repository.module.ts new file mode 100644 index 0000000..50c998e --- /dev/null +++ b/libs/common/src/modules/automation/automation.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { AutomationEntity } from './entities'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [TypeOrmModule.forFeature([AutomationEntity])], +}) +export class AutomationRepositoryModule {} diff --git a/libs/common/src/modules/automation/dtos/automation.dto.ts b/libs/common/src/modules/automation/dtos/automation.dto.ts new file mode 100644 index 0000000..8a0c495 --- /dev/null +++ b/libs/common/src/modules/automation/dtos/automation.dto.ts @@ -0,0 +1,14 @@ +import { IsNotEmpty, IsString } from 'class-validator'; + +export class AutomationDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public automationTuyaUuid: string; + @IsString() + @IsNotEmpty() + public spaceUuid: string; +} diff --git a/libs/common/src/modules/automation/dtos/index.ts b/libs/common/src/modules/automation/dtos/index.ts new file mode 100644 index 0000000..4cdad58 --- /dev/null +++ b/libs/common/src/modules/automation/dtos/index.ts @@ -0,0 +1 @@ +export * from './automation.dto'; diff --git a/libs/common/src/modules/automation/entities/automation.entity.ts b/libs/common/src/modules/automation/entities/automation.entity.ts new file mode 100644 index 0000000..34f77cd --- /dev/null +++ b/libs/common/src/modules/automation/entities/automation.entity.ts @@ -0,0 +1,37 @@ +import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import { AutomationDto } from '../dtos'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { SpaceEntity } from '../../space/entities'; + +@Entity({ name: 'automation' }) +export class AutomationEntity extends AbstractEntity { + @Column({ + type: 'uuid', + default: () => 'gen_random_uuid()', + nullable: false, + }) + public uuid: string; + + @Column({ + nullable: false, + }) + automationTuyaUuid: string; + + @ManyToOne(() => SpaceEntity, (space) => space.scenes, { + nullable: false, + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'space_uuid' }) + space: SpaceEntity; + + @Column({ + nullable: false, + default: false, + }) + public disabled: boolean; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/automation/entities/index.ts b/libs/common/src/modules/automation/entities/index.ts new file mode 100644 index 0000000..6c6c641 --- /dev/null +++ b/libs/common/src/modules/automation/entities/index.ts @@ -0,0 +1 @@ +export * from './automation.entity'; diff --git a/libs/common/src/modules/automation/repositories/automation.repository.ts b/libs/common/src/modules/automation/repositories/automation.repository.ts new file mode 100644 index 0000000..d75dd75 --- /dev/null +++ b/libs/common/src/modules/automation/repositories/automation.repository.ts @@ -0,0 +1,10 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { AutomationEntity } from '../entities'; + +@Injectable() +export class AutomationRepository extends Repository { + constructor(private dataSource: DataSource) { + super(AutomationEntity, dataSource.createEntityManager()); + } +} From 8161e8469bb460e0feb1d975ce21e55ccacdaa82 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 28 Jan 2025 03:43:11 -0600 Subject: [PATCH 220/247] Add new automation actions and repository --- libs/common/src/constants/automation.enum.ts | 2 + .../modules/automation/repositories/index.ts | 1 + src/automation/automation.module.ts | 2 + .../interface/automation.interface.ts | 1 + src/automation/services/automation.service.ts | 346 +++++++++++++----- src/device/device.module.ts | 2 + src/door-lock/door.lock.module.ts | 2 + src/scene/scene.module.ts | 2 + src/scene/services/scene.service.ts | 24 +- src/space/space.module.ts | 2 + .../visitor-password.module.ts | 2 + 11 files changed, 288 insertions(+), 98 deletions(-) create mode 100644 libs/common/src/modules/automation/repositories/index.ts diff --git a/libs/common/src/constants/automation.enum.ts b/libs/common/src/constants/automation.enum.ts index d1503cb..1713c9a 100644 --- a/libs/common/src/constants/automation.enum.ts +++ b/libs/common/src/constants/automation.enum.ts @@ -2,6 +2,8 @@ export enum ActionExecutorEnum { DEVICE_ISSUE = 'device_issue', DELAY = 'delay', RULE_TRIGGER = 'rule_trigger', + RULE_DISABLE = 'rule_disable', + RULE_ENABLE = 'rule_enable', } export enum EntityTypeEnum { diff --git a/libs/common/src/modules/automation/repositories/index.ts b/libs/common/src/modules/automation/repositories/index.ts new file mode 100644 index 0000000..de055c3 --- /dev/null +++ b/libs/common/src/modules/automation/repositories/index.ts @@ -0,0 +1 @@ +export * from './automation.repository'; diff --git a/src/automation/automation.module.ts b/src/automation/automation.module.ts index 0a05f0e..810c8ec 100644 --- a/src/automation/automation.module.ts +++ b/src/automation/automation.module.ts @@ -15,6 +15,7 @@ import { SceneRepository, } from '@app/common/modules/scene/repositories'; import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; +import { AutomationRepository } from '@app/common/modules/automation/repositories'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule], @@ -30,6 +31,7 @@ import { SceneDeviceRepository } from '@app/common/modules/scene-device/reposito SceneIconRepository, SceneRepository, SceneDeviceRepository, + AutomationRepository, ], exports: [AutomationService], }) diff --git a/src/automation/interface/automation.interface.ts b/src/automation/interface/automation.interface.ts index 01523a0..146ec56 100644 --- a/src/automation/interface/automation.interface.ts +++ b/src/automation/interface/automation.interface.ts @@ -60,4 +60,5 @@ export interface AddAutomationParams { effectiveTime: EffectiveTime; decisionExpr: string; spaceTuyaId: string; + spaceUuid: string; } diff --git a/src/automation/services/automation.service.ts b/src/automation/services/automation.service.ts index d28b944..166e2d3 100644 --- a/src/automation/services/automation.service.ts +++ b/src/automation/services/automation.service.ts @@ -21,7 +21,6 @@ import { AutomationDetailsResult, AutomationResponseData, Condition, - GetAutomationBySpaceInterface, } from '../interface/automation.interface'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { @@ -35,6 +34,11 @@ import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service import { ConvertedAction } from '@app/common/integrations/tuya/interfaces'; import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; import { SceneRepository } from '@app/common/modules/scene/repositories'; +import { AutomationRepository } from '@app/common/modules/automation/repositories'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; +import { AutomationEntity } from '@app/common/modules/automation/entities'; +import { DeleteTapToRunSceneInterface } from 'src/scene/interface/scene.interface'; @Injectable() export class AutomationService { @@ -46,6 +50,7 @@ export class AutomationService { private readonly tuyaService: TuyaService, private readonly sceneDeviceRepository: SceneDeviceRepository, private readonly sceneRepository: SceneRepository, + private readonly automationRepository: AutomationRepository, ) { const accessKey = this.configService.get('auth-config.ACCESS_KEY'); const secretKey = this.configService.get('auth-config.SECRET_KEY'); @@ -57,7 +62,9 @@ export class AutomationService { }); } - async addAutomation(addAutomationDto: AddAutomationDto) { + async addAutomation( + addAutomationDto: AddAutomationDto, + ): Promise { try { const { automationName, @@ -65,30 +72,37 @@ export class AutomationService { decisionExpr, actions, conditions, + spaceUuid, } = addAutomationDto; - const space = await this.getSpaceByUuid(addAutomationDto.spaceUuid); - const response = await this.add({ + const space = await this.getSpaceByUuid(spaceUuid); + const automation = await this.add({ automationName, effectiveTime, decisionExpr, actions, conditions, spaceTuyaId: space.spaceTuyaUuid, + spaceUuid, + }); + return new SuccessResponseDto({ + message: `Successfully created new automation with uuid ${automation.uuid}`, + data: automation, + statusCode: HttpStatus.CREATED, }); - return response; } catch (err) { - if (err instanceof BadRequestException) { - throw err; - } else { - throw new HttpException( - err.message || 'Automation not found', - err.status || HttpStatus.NOT_FOUND, - ); - } + console.error( + `Error in creating automation for space UUID ${addAutomationDto.spaceUuid}:`, + err.message, + ); + throw err instanceof HttpException + ? err + : new HttpException( + 'Failed to create automation', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } - - async add(params: AddAutomationParams) { + async createAutomationExternalService(params: AddAutomationParams) { try { const formattedActions = await this.prepareActions(params.actions); const formattedCondition = await this.prepareConditions( @@ -104,14 +118,54 @@ export class AutomationService { formattedActions, ); - return { - id: response?.result.id, - }; - } catch (error) { - throw new HttpException( - error.message || 'Failed to add automation', - error.status || HttpStatus.INTERNAL_SERVER_ERROR, - ); + if (!response.result?.id) { + throw new HttpException( + 'Failed to create automation in Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + return response; + } catch (err) { + if (err instanceof HttpException) { + throw err; + } else if (err.message?.includes('tuya')) { + throw new HttpException( + 'API error: Failed to create automation', + HttpStatus.BAD_GATEWAY, + ); + } else { + throw new HttpException( + `An Internal error has been occured ${err}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + async add(params: AddAutomationParams) { + try { + const response = await this.createAutomationExternalService(params); + + const automation = await this.automationRepository.save({ + automationTuyaUuid: response.result.id, + space: { uuid: params.spaceUuid }, + }); + + return automation; + } catch (err) { + if (err instanceof HttpException) { + throw err; + } else if (err.message?.includes('tuya')) { + throw new HttpException( + 'API error: Failed to create automation', + HttpStatus.BAD_GATEWAY, + ); + } else { + throw new HttpException( + 'Database error: Failed to save automation', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } } } @@ -145,27 +199,42 @@ export class AutomationService { async getAutomationBySpace(spaceUuid: string) { try { const space = await this.getSpaceByUuid(spaceUuid); + + const automationData = await this.automationRepository.find({ + where: { + space: { uuid: spaceUuid }, + disabled: false, + }, + relations: ['space'], + }); + if (!space.spaceTuyaUuid) { throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); } + const automations = await Promise.all( + automationData.map(async (automation) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const automationDetails = await this.tuyaService.getSceneRule( + automation.automationTuyaUuid, + ); - const path = `/v2.0/cloud/scene/rule?space_id=${space.spaceTuyaUuid}&type=automation`; - const response: GetAutomationBySpaceInterface = await this.tuya.request({ - method: 'GET', - path, - }); - - if (!response.success) { - throw new HttpException(response.msg, HttpStatus.BAD_REQUEST); - } - - return response.result.list - .filter((item) => item.name && !item.name.startsWith(AUTO_PREFIX)) - .map((item) => { return { - id: item.id, - name: item.name, - status: item.status, + uuid: automation.uuid, + ...automationDetails, + }; + }), + ); + + return automations + .filter( + (item: any) => + item.result.name && !item.result.name.startsWith(AUTO_PREFIX), + ) + .map((item: any) => { + return { + uuid: item.uuid, + name: item.result.name, + status: item.result.status, type: AUTOMATION_TYPE, }; }); @@ -180,7 +249,45 @@ export class AutomationService { } } } + async findAutomationBySpace(spaceUuid: string) { + try { + await this.getSpaceByUuid(spaceUuid); + const automationData = await this.automationRepository.find({ + where: { + space: { uuid: spaceUuid }, + disabled: false, + }, + relations: ['space'], + }); + + const automations = await Promise.all( + automationData.map(async (automation) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { actions, ...automationDetails } = + await this.getAutomation(automation); + + return automationDetails; + }), + ); + + return automations; + } catch (err) { + console.error( + `Error fetching Tap-to-Run scenes for space UUID ${spaceUuid}:`, + err.message, + ); + + if (err instanceof HttpException) { + throw err; + } else { + throw new HttpException( + 'An error occurred while retrieving scenes', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } async getTapToRunSceneDetailsTuya( sceneUuid: string, ): Promise { @@ -213,13 +320,34 @@ export class AutomationService { } } } - async getAutomationDetails(automationUuid: string, withSpaceId = false) { + async getAutomationDetails(automationUuid: string) { try { - const path = `/v2.0/cloud/scene/rule/${automationUuid}`; - const response = await this.tuya.request({ - method: 'GET', - path, - }); + const automation = await this.findAutomation(automationUuid); + + const automationDetails = await this.getAutomation(automation); + + return automationDetails; + } catch (error) { + console.error( + `Error fetching automation details for automationUuid ${automationUuid}:`, + error, + ); + + if (error instanceof HttpException) { + throw error; + } else { + throw new HttpException( + 'An error occurred while retrieving automation details', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + } + async getAutomation(automation: AutomationEntity) { + try { + const response = await this.tuyaService.getSceneRule( + automation.automationTuyaUuid, + ); if (!response.success) { throw new HttpException(response.msg, HttpStatus.BAD_REQUEST); @@ -261,6 +389,11 @@ export class AutomationService { action.entityId = scene.uuid; action.iconUuid = scene.sceneIcon.uuid; action.icon = scene.sceneIcon.icon; + } else if (sceneDetails.type === ActionTypeEnum.AUTOMATION) { + const automation = await this.automationRepository.findOne({ + where: { automationTuyaUuid: action.entityId }, + }); + action.entityId = automation.uuid; } action.name = sceneDetails.name; action.type = sceneDetails.type; @@ -291,91 +424,95 @@ export class AutomationService { responseData.effectiveTime || {}; return { - id: responseData.id, + uuid: automation.uuid, name: responseData.name, status: responseData.status, type: 'automation', ...(() => { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { spaceId, runningMode, ...rest } = responseData; + const { spaceId, id, runningMode, ...rest } = responseData; return rest; })(), actions, conditions, effectiveTime: effectiveTimeWithoutTimeZoneId, // Use modified effectiveTime - ...(withSpaceId && { spaceId: responseData.spaceId }), }; } catch (err) { if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException + throw err; } else { - throw new HttpException('Automation not found', HttpStatus.NOT_FOUND); + throw new HttpException( + `An error occurred while retrieving automation details for ${automation.uuid}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } } + async findAutomation(sceneUuid: string): Promise { + const automation = await this.automationRepository.findOne({ + where: { uuid: sceneUuid }, + relations: ['space'], + }); + + if (!automation) { + throw new HttpException( + `Invalid automation with id ${sceneUuid}`, + HttpStatus.NOT_FOUND, + ); + } + return automation; + } async deleteAutomation(param: AutomationParamDto) { + const { automationUuid } = param; + try { - const { automationUuid } = param; - - const automation = await this.getAutomationDetails(automationUuid, true); - - if (!automation && !automation.spaceId) { - throw new HttpException( - `Invalid automationid ${automationUuid}`, - HttpStatus.BAD_REQUEST, - ); - } + const automationData = await this.findAutomation(automationUuid); + const space = await this.getSpaceByUuid(automationData.space.uuid); + await this.delete(automationData.automationTuyaUuid, space.spaceTuyaUuid); const existingSceneDevice = await this.sceneDeviceRepository.findOne({ - where: { automationTuyaUuid: automationUuid }, + where: { automationTuyaUuid: automationData.automationTuyaUuid }, }); if (existingSceneDevice) { await this.sceneDeviceRepository.delete({ - automationTuyaUuid: automationUuid, + automationTuyaUuid: automationData.automationTuyaUuid, }); } - - const response = this.tuyaService.deleteAutomation( - automation.spaceId, - automationUuid, + await this.automationRepository.update( + { + uuid: automationUuid, + }, + { disabled: true }, ); - return response; + return new SuccessResponseDto({ + message: `Automation with ID ${automationUuid} deleted successfully`, + }); } catch (err) { if (err instanceof HttpException) { throw err; } else { throw new HttpException( - err.message || 'Automation not found', + err.message || `Automation not found for id ${param.automationUuid}`, err.status || HttpStatus.NOT_FOUND, ); } } } - - async delete(tuyaSpaceId: string, automationUuid: string) { + async delete(tuyaAutomationId: string, tuyaSpaceId: string) { try { - const existingSceneDevice = await this.sceneDeviceRepository.findOne({ - where: { automationTuyaUuid: automationUuid }, - }); - - if (existingSceneDevice) { - await this.sceneDeviceRepository.delete({ - automationTuyaUuid: automationUuid, - }); - } - const response = await this.tuyaService.deleteAutomation( + const response = (await this.tuyaService.deleteSceneRule( + tuyaAutomationId, tuyaSpaceId, - automationUuid, - ); + )) as DeleteTapToRunSceneInterface; return response; - } catch (err) { - if (err instanceof HttpException) { - throw err; + } catch (error) { + if (error instanceof HttpException) { + throw error; } else { throw new HttpException( - err.message || 'Automation not found', - err.status || HttpStatus.NOT_FOUND, + 'Failed to delete automation rule in Tuya', + HttpStatus.INTERNAL_SERVER_ERROR, ); } } @@ -429,17 +566,13 @@ export class AutomationService { automationUuid: string, ) { try { - const automation = await this.getAutomationDetails(automationUuid, true); - if (!automation.spaceId) { - throw new HttpException( - "Automation doesn't exist", - HttpStatus.NOT_FOUND, - ); - } + const automation = await this.findAutomation(automationUuid); + const space = await this.getSpaceByUuid(automation.space.uuid); + const updateTuyaAutomationResponse = await this.updateAutomationExternalService( - automation.spaceId, - automation.id, + space.spaceTuyaUuid, + automation.automationTuyaUuid, updateAutomationDto, ); @@ -449,6 +582,16 @@ export class AutomationService { HttpStatus.BAD_GATEWAY, ); } + const updatedScene = await this.automationRepository.update( + { uuid: automationUuid }, + { + space: { uuid: automation.space.uuid }, + }, + ); + return new SuccessResponseDto({ + data: updatedScene, + message: `Automation with ID ${automationUuid} updated successfully`, + }); } catch (err) { if (err instanceof BadRequestException) { throw err; // Re-throw BadRequestException @@ -466,6 +609,7 @@ export class AutomationService { ) { const { isEnable, spaceUuid } = updateAutomationStatusDto; try { + const automation = await this.findAutomation(automationUuid); const space = await this.getSpaceByUuid(spaceUuid); if (!space.spaceTuyaUuid) { throw new HttpException( @@ -476,7 +620,7 @@ export class AutomationService { const response = await this.tuyaService.updateAutomationState( space.spaceTuyaUuid, - automationUuid, + automation.automationTuyaUuid, isEnable, ); @@ -521,6 +665,14 @@ export class AutomationService { action.entity_id = scene.sceneTuyaUuid; } } + } else if ( + action.action_executor === ActionExecutorEnum.RULE_DISABLE || + action.action_executor === ActionExecutorEnum.RULE_ENABLE + ) { + if (action.action_type === ActionTypeEnum.AUTOMATION) { + const automation = await this.findAutomation(action.entity_id); + action.entity_id = automation.automationTuyaUuid; + } } }), ); diff --git a/src/device/device.module.ts b/src/device/device.module.ts index 39b1be6..2a372cc 100644 --- a/src/device/device.module.ts +++ b/src/device/device.module.ts @@ -19,6 +19,7 @@ import { SceneRepository, } from '@app/common/modules/scene/repositories'; import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; +import { AutomationRepository } from '@app/common/modules/automation/repositories'; @Module({ imports: [ ConfigModule, @@ -41,6 +42,7 @@ import { SceneDeviceRepository } from '@app/common/modules/scene-device/reposito SceneIconRepository, SceneRepository, SceneDeviceRepository, + AutomationRepository, ], exports: [DeviceService], }) diff --git a/src/door-lock/door.lock.module.ts b/src/door-lock/door.lock.module.ts index 9bbc0d5..3cf563b 100644 --- a/src/door-lock/door.lock.module.ts +++ b/src/door-lock/door.lock.module.ts @@ -18,6 +18,7 @@ import { SceneRepository, } from '@app/common/modules/scene/repositories'; import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; +import { AutomationRepository } from '@app/common/modules/automation/repositories'; @Module({ imports: [ConfigModule, DeviceRepositoryModule], controllers: [DoorLockController], @@ -36,6 +37,7 @@ import { SceneDeviceRepository } from '@app/common/modules/scene-device/reposito SceneIconRepository, SceneRepository, SceneDeviceRepository, + AutomationRepository, ], exports: [DoorLockService], }) diff --git a/src/scene/scene.module.ts b/src/scene/scene.module.ts index 9da0156..a6a1435 100644 --- a/src/scene/scene.module.ts +++ b/src/scene/scene.module.ts @@ -14,6 +14,7 @@ import { } from '@app/common/modules/scene/repositories'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; +import { AutomationRepository } from '@app/common/modules/automation/repositories'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, DeviceStatusFirebaseModule], @@ -28,6 +29,7 @@ import { SceneDeviceRepository } from '@app/common/modules/scene-device/reposito SceneIconRepository, SceneRepository, SceneDeviceRepository, + AutomationRepository, ], exports: [SceneService], }) diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 42891c1..6ff986d 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -23,7 +23,10 @@ import { SceneDetailsResult, } from '../interface/scene.interface'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; -import { ActionExecutorEnum } from '@app/common/constants/automation.enum'; +import { + ActionExecutorEnum, + ActionTypeEnum, +} from '@app/common/constants/automation.enum'; import { SceneIconRepository, SceneRepository, @@ -40,6 +43,7 @@ import { HttpStatusCode } from 'axios'; import { ConvertedAction } from '@app/common/integrations/tuya/interfaces'; import { DeviceService } from 'src/device/services'; import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; +import { AutomationRepository } from '@app/common/modules/automation/repositories'; @Injectable() export class SceneService { @@ -48,6 +52,7 @@ export class SceneService { private readonly sceneIconRepository: SceneIconRepository, private readonly sceneRepository: SceneRepository, private readonly sceneDeviceRepository: SceneDeviceRepository, + private readonly automationRepository: AutomationRepository, private readonly tuyaService: TuyaService, @Inject(forwardRef(() => DeviceService)) private readonly deviceService: DeviceService, @@ -460,6 +465,12 @@ export class SceneService { ); if (sceneDetails.id) { + if (sceneDetails.type === ActionTypeEnum.AUTOMATION) { + const automation = await this.automationRepository.findOne({ + where: { automationTuyaUuid: action.entityId }, + }); + action.entityId = automation.uuid; + } action.name = sceneDetails.name; action.type = sceneDetails.type; } @@ -568,6 +579,17 @@ export class SceneService { if (device) { action.entity_id = device.deviceTuyaUuid; } + } else if ( + action.action_executor === ActionExecutorEnum.RULE_DISABLE || + action.action_executor === ActionExecutorEnum.RULE_ENABLE + ) { + const automation = await this.automationRepository.findOne({ + where: { uuid: action.entity_id }, + }); + + if (automation) { + action.entity_id = automation.automationTuyaUuid; + } } }), ); diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 9cc0519..8289fd2 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -70,6 +70,7 @@ import { InviteUserRepository, InviteUserSpaceRepository, } from '@app/common/modules/Invite-user/repositiories'; +import { AutomationRepository } from '@app/common/modules/automation/repositories'; export const CommandHandlers = [DisableSpaceHandler]; @@ -128,6 +129,7 @@ export const CommandHandlers = [DisableSpaceHandler]; TimeZoneRepository, InviteUserRepository, InviteUserSpaceRepository, + AutomationRepository, ], exports: [SpaceService], }) diff --git a/src/vistor-password/visitor-password.module.ts b/src/vistor-password/visitor-password.module.ts index e768ad5..3aaf3a5 100644 --- a/src/vistor-password/visitor-password.module.ts +++ b/src/vistor-password/visitor-password.module.ts @@ -20,6 +20,7 @@ import { SceneRepository, } from '@app/common/modules/scene/repositories'; import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; +import { AutomationRepository } from '@app/common/modules/automation/repositories'; @Module({ imports: [ConfigModule, DeviceRepositoryModule, DoorLockModule], controllers: [VisitorPasswordController], @@ -39,6 +40,7 @@ import { SceneDeviceRepository } from '@app/common/modules/scene-device/reposito SceneIconRepository, SceneRepository, SceneDeviceRepository, + AutomationRepository, ], exports: [VisitorPasswordService], }) From 2477f4825e4ca13a125541ba9e3fe2068c9906d8 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 28 Jan 2025 19:12:31 -0600 Subject: [PATCH 221/247] Refactor automation and scene fetching to handle errors and improve readability --- src/automation/services/automation.service.ts | 65 ++++++++++--------- src/scene/services/scene.service.ts | 36 +++++----- 2 files changed, 53 insertions(+), 48 deletions(-) diff --git a/src/automation/services/automation.service.ts b/src/automation/services/automation.service.ts index 166e2d3..8999533 100644 --- a/src/automation/services/automation.service.ts +++ b/src/automation/services/automation.service.ts @@ -198,8 +198,7 @@ export class AutomationService { async getAutomationBySpace(spaceUuid: string) { try { - const space = await this.getSpaceByUuid(spaceUuid); - + // Fetch automation data from the repository const automationData = await this.automationRepository.find({ where: { space: { uuid: spaceUuid }, @@ -208,45 +207,47 @@ export class AutomationService { relations: ['space'], }); - if (!space.spaceTuyaUuid) { - throw new BadRequestException(`Invalid space UUID ${spaceUuid}`); - } - const automations = await Promise.all( - automationData.map(async (automation) => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const automationDetails = await this.tuyaService.getSceneRule( + // Safe fetch function to handle individual automation fetching + const safeFetch = async (automation: any) => { + try { + const automationDetails = (await this.tuyaService.getSceneRule( automation.automationTuyaUuid, - ); + )) as { result?: { name?: string; status?: string } }; // Explicit type assertion + + if ( + !automationDetails?.result?.name || + automationDetails.result.name.startsWith(AUTO_PREFIX) + ) { + return null; // Skip invalid or auto-generated automations + } return { uuid: automation.uuid, - ...automationDetails, - }; - }), - ); - - return automations - .filter( - (item: any) => - item.result.name && !item.result.name.startsWith(AUTO_PREFIX), - ) - .map((item: any) => { - return { - uuid: item.uuid, - name: item.result.name, - status: item.result.status, + name: automationDetails.result.name, + status: automationDetails.result.status, type: AUTOMATION_TYPE, }; - }); + } catch (error) { + console.warn( + `Skipping automation with UUID: ${automation.uuid} due to error. ${error.message}`, + ); + return null; + } + }; + + // Process automations using safeFetch + const automations = await Promise.all(automationData.map(safeFetch)); + + return automations.filter(Boolean); // Remove null values } catch (err) { + console.error('Error retrieving automations:', err); if (err instanceof BadRequestException) { - throw err; // Re-throw BadRequestException - } else { - throw new HttpException( - err.message || 'Automation not found', - err.status || HttpStatus.NOT_FOUND, - ); + throw err; } + throw new HttpException( + err.message || 'Automation not found', + err.status || HttpStatus.NOT_FOUND, + ); } } async findAutomationBySpace(spaceUuid: string) { diff --git a/src/scene/services/scene.service.ts b/src/scene/services/scene.service.ts index 6ff986d..1a9196e 100644 --- a/src/scene/services/scene.service.ts +++ b/src/scene/services/scene.service.ts @@ -229,33 +229,37 @@ export class SceneService { relations: ['sceneIcon', 'space'], }); - const scenes = await Promise.all( - scenesData.map(async (scene) => { + const safeFetch = async (scene: any) => { + try { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { actions, ...sceneDetails } = await this.getScene( scene, spaceUuid, ); - return sceneDetails; - }), - ); + } catch (error) { + console.warn( + `Skipping scene UUID: ${scene.uuid} due to error: ${error.message}`, + ); + return null; + } + }; - return scenes; - } catch (err) { + const scenes = await Promise.all(scenesData.map(safeFetch)); + + return scenes.filter(Boolean); // Remove null values + } catch (error) { console.error( `Error fetching Tap-to-Run scenes for space UUID ${spaceUuid}:`, - err.message, + error.message, ); - if (err instanceof HttpException) { - throw err; - } else { - throw new HttpException( - 'An error occurred while retrieving scenes', - HttpStatus.INTERNAL_SERVER_ERROR, - ); - } + throw error instanceof HttpException + ? error + : new HttpException( + 'An error occurred while retrieving scenes', + HttpStatus.INTERNAL_SERVER_ERROR, + ); } } From dc23cce89a76e55d9c7cd57e536046d26660137e Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 28 Jan 2025 19:41:03 -0600 Subject: [PATCH 222/247] Add platform type to user login and enforce access restrictions --- libs/common/src/auth/services/auth.service.ts | 11 ++++++++++- libs/common/src/constants/platform-type.enum.ts | 4 ++++ src/auth/dtos/user-login.dto.ts | 8 +++++++- src/auth/services/user-auth.service.ts | 1 + 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 libs/common/src/constants/platform-type.enum.ts diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index 528db56..fdba2f0 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -11,6 +11,8 @@ import { UserSessionRepository } from '../../../../common/src/modules/session/re import { UserSessionEntity } from '../../../../common/src/modules/session/entities'; import { ConfigService } from '@nestjs/config'; import { OAuth2Client } from 'google-auth-library'; +import { PlatformType } from '@app/common/constants/platform-type.enum'; +import { RoleType } from '@app/common/constants/role.type.enum'; @Injectable() export class AuthService { @@ -29,6 +31,7 @@ export class AuthService { email: string, pass: string, regionUuid?: string, + platform?: PlatformType, ): Promise { const user = await this.userRepository.findOne({ where: { @@ -37,7 +40,13 @@ export class AuthService { }, relations: ['roleType'], }); - + if ( + platform === PlatformType.WEB && + (user.roleType.type === RoleType.SPACE_OWNER || + user.roleType.type === RoleType.SPACE_MEMBER) + ) { + throw new UnauthorizedException('Access denied for web platform'); + } if (!user) { throw new BadRequestException('Invalid credentials'); } diff --git a/libs/common/src/constants/platform-type.enum.ts b/libs/common/src/constants/platform-type.enum.ts new file mode 100644 index 0000000..e8216c0 --- /dev/null +++ b/libs/common/src/constants/platform-type.enum.ts @@ -0,0 +1,4 @@ +export enum PlatformType { + WEB = 'web', + MOBILE = 'mobile', +} diff --git a/src/auth/dtos/user-login.dto.ts b/src/auth/dtos/user-login.dto.ts index 198ae12..6af12aa 100644 --- a/src/auth/dtos/user-login.dto.ts +++ b/src/auth/dtos/user-login.dto.ts @@ -1,5 +1,6 @@ +import { PlatformType } from '@app/common/constants/platform-type.enum'; import { ApiProperty } from '@nestjs/swagger'; -import { IsEmail, IsOptional, IsString } from 'class-validator'; +import { IsEmail, IsEnum, IsOptional, IsString } from 'class-validator'; export class UserLoginDto { @ApiProperty() @@ -20,4 +21,9 @@ export class UserLoginDto { @IsOptional() @IsString() googleCode?: string; + + @ApiProperty() + @IsOptional() + @IsEnum(PlatformType) + platform?: PlatformType; } diff --git a/src/auth/services/user-auth.service.ts b/src/auth/services/user-auth.service.ts index c9c9436..aaa2b34 100644 --- a/src/auth/services/user-auth.service.ts +++ b/src/auth/services/user-auth.service.ts @@ -132,6 +132,7 @@ export class UserAuthService { data.email, data.password, data.regionUuid, + data.platform, ); } const session = await Promise.all([ From 3366f525ef0289614a0be9139e7252ac9e0a63c5 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 29 Jan 2025 11:53:28 +0400 Subject: [PATCH 223/247] fixed moving device --- src/space-model/services/tag-model.service.ts | 6 +- src/space/dtos/tag/create-tag-dto.ts | 12 +- src/space/services/space.service.ts | 14 +- src/space/services/tag/tag.service.ts | 276 ++++++++++++++++-- 4 files changed, 274 insertions(+), 34 deletions(-) diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index 0f77b3d..67ce6a5 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -478,14 +478,14 @@ export class TagModelService { } getSubspaceTagsToBeAdded( - spaceTags: ModifyTagModelDto[], - subspaceModels: ModifySubspaceModelDto[], + spaceTags?: ModifyTagModelDto[], + subspaceModels?: ModifySubspaceModelDto[], ): ModifyTagModelDto[] { if (!subspaceModels || subspaceModels.length === 0) { return spaceTags; } - const spaceTagsToDelete = spaceTags.filter( + const spaceTagsToDelete = spaceTags?.filter( (tag) => tag.action === 'delete', ); diff --git a/src/space/dtos/tag/create-tag-dto.ts b/src/space/dtos/tag/create-tag-dto.ts index 8e89155..3e61f39 100644 --- a/src/space/dtos/tag/create-tag-dto.ts +++ b/src/space/dtos/tag/create-tag-dto.ts @@ -1,5 +1,5 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class CreateTagDto { @ApiProperty({ @@ -10,6 +10,14 @@ export class CreateTagDto { @IsString() tag: string; + @ApiPropertyOptional({ + description: 'UUID of the tag (required for update/delete)', + example: '123e4567-e89b-12d3-a456-426614174000', + }) + @IsOptional() + @IsString() + uuid?: string; + @ApiProperty({ description: 'ID of the product associated with the tag', example: '123e4567-e89b-12d3-a456-426614174000', diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 267d1d1..394fd9f 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -356,16 +356,26 @@ export class SpaceService { } if (hasSubspace) { - await this.subSpaceService.modifySubSpace( + const modifiedSubspaces = this.tagService.getModifiedSubspaces( + updateSpaceDto.tags, updateSpaceDto.subspace, + ); + + await this.subSpaceService.modifySubSpace( + modifiedSubspaces, queryRunner, space, ); } if (hasTags) { - await this.tagService.modifyTags( + const spaceTagsAfterMove = this.tagService.getSubspaceTagsToBeAdded( updateSpaceDto.tags, + updateSpaceDto.subspace, + ); + + await this.tagService.modifyTags( + spaceTagsAfterMove, queryRunner, space, ); diff --git a/src/space/services/tag/tag.service.ts b/src/space/services/tag/tag.service.ts index be32a2e..94c157d 100644 --- a/src/space/services/tag/tag.service.ts +++ b/src/space/services/tag/tag.service.ts @@ -8,7 +8,8 @@ import { import { TagModel } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { ProductService } from 'src/product/services'; -import { CreateTagDto } from 'src/space/dtos'; +import { ModifyTagModelDto } from 'src/space-model/dtos'; +import { CreateTagDto, ModifySubspaceDto } from 'src/space/dtos'; import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto'; import { QueryRunner } from 'typeorm'; @@ -25,25 +26,133 @@ export class TagService { space?: SpaceEntity, subspace?: SubspaceEntity, additionalTags?: CreateTagDto[], + tagsToDelete?: ModifyTagDto[], ): Promise { this.validateTagsInput(tags); const combinedTags = this.combineTags(tags, additionalTags); this.ensureNoDuplicateTags(combinedTags); + const tagEntitiesToCreate = tags.filter((tagDto) => tagDto.uuid === null); + const tagEntitiesToUpdate = tags.filter((tagDto) => tagDto.uuid !== null); + try { - const tagEntities = await Promise.all( - tags.map(async (tagDto) => - this.prepareTagEntity(tagDto, queryRunner, space, subspace), - ), + const createdTags = await this.bulkSaveTags( + tagEntitiesToCreate, + queryRunner, + space, + subspace, + tagsToDelete, + ); + const updatedTags = await this.moveTags( + tagEntitiesToUpdate, + queryRunner, + space, + subspace, ); - return await queryRunner.manager.save(tagEntities); + return [...createdTags, ...updatedTags]; } catch (error) { throw this.handleUnexpectedError('Failed to save tags', error); } } + async bulkSaveTags( + tags: CreateTagDto[], + queryRunner: QueryRunner, + space?: SpaceEntity, + subspace?: SubspaceEntity, + tagsToDelete?: ModifyTagDto[], + ): Promise { + if (!tags.length) { + return []; + } + + const tagEntities = await Promise.all( + tags.map((tagDto) => + this.prepareTagEntity( + tagDto, + queryRunner, + space, + subspace, + tagsToDelete, + ), + ), + ); + + try { + return await queryRunner.manager.save(tagEntities); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } + + throw new HttpException( + `Failed to save tag models due to an unexpected error: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async moveTags( + tags: CreateTagDto[], + queryRunner: QueryRunner, + space?: SpaceEntity, + subspace?: SubspaceEntity, + ): Promise { + if (!tags.length) { + return []; + } + + try { + return await Promise.all( + tags.map(async (tagDto) => { + try { + const tag = await this.getTagByUuid(tagDto.uuid); + if (!tag) { + throw new HttpException( + `Tag with UUID ${tagDto.uuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + + if (subspace && subspace.space) { + await queryRunner.manager.update( + this.tagRepository.target, + { uuid: tag.uuid }, + { subspace, space: null }, + ); + tag.subspace = subspace; + } + + if (subspace === null && space) { + await queryRunner.manager.update( + this.tagRepository.target, + { uuid: tag.uuid }, + { subspace: null, space: space }, + ); + tag.subspace = null; + tag.space = space; + } + + return tag; + } catch (error) { + console.error( + `Error moving tag with UUID ${tagDto.uuid}: ${error.message}`, + ); + throw error; // Re-throw the error to propagate it to the parent Promise.all + } + }), + ); + } catch (error) { + console.error(`Error in moveTags: ${error.message}`); + throw new HttpException( + `Failed to move tags due to an unexpected error: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async createTagsFromModel( queryRunner: QueryRunner, tagModels: TagModel[], @@ -170,6 +279,7 @@ export class TagService { ); } } + async modifyTags( tags: ModifyTagDto[], queryRunner: QueryRunner, @@ -179,15 +289,27 @@ export class TagService { if (!tags?.length) return; try { + const tagsToDelete = tags.filter( + (tag) => tag.action === ModifyAction.DELETE, + ); + await Promise.all( tags.map(async (tag) => { switch (tag.action) { case ModifyAction.ADD: await this.createTags( - [{ tag: tag.tag, productUuid: tag.productUuid }], + [ + { + tag: tag.tag, + productUuid: tag.productUuid, + uuid: tag.uuid, + }, + ], queryRunner, space, subspace, + null, + tagsToDelete, ); break; case ModifyAction.UPDATE: @@ -195,7 +317,6 @@ export class TagService { break; case ModifyAction.DELETE: await this.deleteTags([tag.uuid], queryRunner); - break; default: throw new HttpException( @@ -244,10 +365,11 @@ export class TagService { tag: string, productUuid: string, space: SpaceEntity, + tagsToDelete?: ModifyTagDto[], ): Promise { const { uuid: spaceUuid } = space; - const tagExists = await this.tagRepository.exists({ + const tagExists = await this.tagRepository.find({ where: [ { tag, @@ -264,7 +386,12 @@ export class TagService { ], }); - if (tagExists) { + const filteredTagExists = tagExists.filter( + (existingTag) => + !tagsToDelete?.some((deleteTag) => deleteTag.uuid === existingTag.uuid), + ); + + if (filteredTagExists.length > 0) { throw new HttpException(`Tag can't be reused`, HttpStatus.CONFLICT); } } @@ -274,28 +401,54 @@ export class TagService { queryRunner: QueryRunner, space?: SpaceEntity, subspace?: SubspaceEntity, + tagsToDelete?: ModifyTagDto[], ): Promise { - const product = await this.productService.findOne(tagDto.productUuid); + try { + const product = await this.productService.findOne(tagDto.productUuid); - if (!product) { + if (!product) { + throw new HttpException( + `Product with UUID ${tagDto.productUuid} not found.`, + HttpStatus.NOT_FOUND, + ); + } + + if (space) { + await this.checkTagReuse( + tagDto.tag, + tagDto.productUuid, + space, + tagsToDelete, + ); + } else if (subspace && subspace.space) { + await this.checkTagReuse( + tagDto.tag, + tagDto.productUuid, + subspace.space, + ); + } else { + throw new HttpException( + `Invalid subspace or space provided.`, + HttpStatus.BAD_REQUEST, + ); + } + const tagSpace = space !== null ? space : subspace.space; + + return queryRunner.manager.create(TagEntity, { + tag: tagDto.tag, + product: product.data, + space: tagSpace, + subspace: subspace, + }); + } catch (error) { + if (error instanceof HttpException) { + throw error; + } throw new HttpException( - `Product with UUID ${tagDto.productUuid} not found.`, - HttpStatus.NOT_FOUND, + `An error occurred while preparing the tag entity: ${error.message}`, + HttpStatus.INTERNAL_SERVER_ERROR, ); } - - await this.checkTagReuse( - tagDto.tag, - tagDto.productUuid, - space ?? subspace.space, - ); - - return queryRunner.manager.create(TagEntity, { - tag: tagDto.tag, - product: product.data, - space, - subspace, - }); } private async getTagByUuid(uuid: string): Promise { @@ -347,4 +500,73 @@ export class TagService { return; } } + + getSubspaceTagsToBeAdded( + spaceTags?: ModifyTagDto[], + subspaceModels?: ModifySubspaceDto[], + ): ModifyTagDto[] { + if (!subspaceModels || subspaceModels.length === 0) { + return spaceTags; + } + + const spaceTagsToDelete = spaceTags?.filter( + (tag) => tag.action === 'delete', + ); + + const tagsToAdd = subspaceModels.flatMap( + (subspace) => subspace.tags?.filter((tag) => tag.action === 'add') || [], + ); + + const commonTagUuids = new Set( + tagsToAdd + .filter((tagToAdd) => + spaceTagsToDelete.some( + (tagToDelete) => tagToAdd.uuid === tagToDelete.uuid, + ), + ) + .map((tag) => tag.uuid), + ); + + const remainingTags = spaceTags.filter( + (tag) => !commonTagUuids.has(tag.uuid), // Exclude tags in commonTagUuids + ); + + return remainingTags; + } + + getModifiedSubspaces( + spaceTags?: ModifyTagDto[], + subspaceModels?: ModifySubspaceDto[], + ): ModifySubspaceDto[] { + if (!subspaceModels || subspaceModels.length === 0) { + return []; + } + + // Extract tags marked for addition in spaceTags + const spaceTagsToAdd = spaceTags?.filter((tag) => tag.action === 'add'); + + // Find UUIDs of tags that are common between spaceTagsToAdd and subspace tags marked for deletion + const commonTagUuids = new Set( + spaceTagsToAdd + .flatMap((tagToAdd) => + subspaceModels.flatMap( + (subspace) => + subspace.tags?.filter( + (tagToDelete) => + tagToDelete.action === 'delete' && + tagToAdd.uuid === tagToDelete.uuid, + ) || [], + ), + ) + .map((tag) => tag.uuid), + ); + + // Modify subspaceModels by removing tags with UUIDs present in commonTagUuids + const modifiedSubspaces = subspaceModels.map((subspace) => ({ + ...subspace, + tags: subspace.tags?.filter((tag) => !commonTagUuids.has(tag.uuid)) || [], + })); + + return modifiedSubspaces; + } } From 64a056cf95fe5a9c0e75178859b2e2be91cc61f1 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 29 Jan 2025 14:02:26 +0400 Subject: [PATCH 224/247] tag fix --- src/space-model/services/space-model.service.ts | 2 +- src/space-model/services/tag-model.service.ts | 4 +--- src/space/services/tag/tag.service.ts | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 05906ca..82054d5 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -395,7 +395,7 @@ export class SpaceModelService { if (!spaceModel) { throw new HttpException('space model not found', HttpStatus.NOT_FOUND); } - + console.log(JSON.stringify(spaceModel)); return spaceModel; } } diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index 67ce6a5..3f6a493 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -409,13 +409,11 @@ export class TagModelService { HttpStatus.BAD_REQUEST, ); } - const tagSpace = - spaceModel !== null ? spaceModel : subspaceModel.spaceModel; return queryRunner.manager.create(TagModel, { tag: tagDto.tag, product: product.data, - spaceModel: tagSpace, + spaceModel: spaceModel, subspaceModel: subspaceModel, }); } catch (error) { diff --git a/src/space/services/tag/tag.service.ts b/src/space/services/tag/tag.service.ts index 94c157d..03bc42d 100644 --- a/src/space/services/tag/tag.service.ts +++ b/src/space/services/tag/tag.service.ts @@ -432,12 +432,11 @@ export class TagService { HttpStatus.BAD_REQUEST, ); } - const tagSpace = space !== null ? space : subspace.space; return queryRunner.manager.create(TagEntity, { tag: tagDto.tag, product: product.data, - space: tagSpace, + space: space, subspace: subspace, }); } catch (error) { From 1b02f1c86affa3403496140cf69439c0855e0c66 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 29 Jan 2025 14:55:05 +0400 Subject: [PATCH 225/247] add tag_id --- libs/common/src/modules/device/entities/device.entity.ts | 1 + src/space-model/services/space-model.service.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index bdd1ce5..f2a5a1e 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -78,6 +78,7 @@ export class DeviceEntity extends AbstractEntity { @OneToOne(() => TagEntity, (tag) => tag.device, { nullable: true, }) + @JoinColumn({ name: 'tag_id' }) tag: TagEntity; constructor(partial: Partial) { diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 82054d5..2162c38 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -395,7 +395,6 @@ export class SpaceModelService { if (!spaceModel) { throw new HttpException('space model not found', HttpStatus.NOT_FOUND); } - console.log(JSON.stringify(spaceModel)); return spaceModel; } } From 289e615b7a97269e2815a1cd610945bf33917b0b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 30 Jan 2025 10:09:41 +0400 Subject: [PATCH 226/247] added subspace information --- src/device/services/device.service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 4846cd3..2cd5639 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -960,6 +960,7 @@ export class DeviceService { 'productDevice', 'permission', 'permission.permissionType', + 'subspace', ], }); @@ -1024,6 +1025,7 @@ export class DeviceService { uuid: device.spaceDevice.community.uuid, name: device.spaceDevice.community.name, }, + subspace: device.subspace, // permissionType: device.permission[0].permissionType.type, ...(await this.getDeviceDetailsByDeviceIdTuya( device.deviceTuyaUuid, From 6a857aac65e0a4bebea441ab7e00cb0fee6bc463 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 30 Jan 2025 04:26:39 -0600 Subject: [PATCH 227/247] Order users by creation date in ascending order --- src/project/services/project-user.service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/project/services/project-user.service.ts b/src/project/services/project-user.service.ts index 5564f13..8a2250b 100644 --- a/src/project/services/project-user.service.ts +++ b/src/project/services/project-user.service.ts @@ -37,6 +37,9 @@ export class ProjectUserService { 'isEnabled', ], relations: ['roleType'], + order: { + createdAt: 'ASC', + }, }); const normalizedUsers = allUsers.map((user) => { From 11faa52283efc500ad158a2be0c469f3f9abc2b5 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 30 Jan 2025 21:43:30 +0400 Subject: [PATCH 228/247] added moving tags --- src/space/services/space.service.ts | 2 + .../subspace/subspace-device.service.ts | 51 ++++++++++++++++++- src/space/services/tag/tag.service.ts | 9 ++-- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 394fd9f..2072758 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -323,6 +323,8 @@ export class SpaceService { updateSpaceDto: UpdateSpaceDto, ): Promise { const { communityUuid, spaceUuid, projectUuid } = params; + console.log(communityUuid, spaceUuid, projectUuid); + console.log(updateSpaceDto); const queryRunner = this.dataSource.createQueryRunner(); try { diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index 130da1d..97b6080 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -11,6 +11,7 @@ import { ValidationService } from '../space-validation.service'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; import { In, QueryRunner } from 'typeorm'; import { DeviceEntity } from '@app/common/modules/device/entities'; +import { TagRepository } from '@app/common/modules/space'; @Injectable() export class SubspaceDeviceService { @@ -20,6 +21,7 @@ export class SubspaceDeviceService { private readonly tuyaService: TuyaService, private readonly productRepository: ProductRepository, private readonly validationService: ValidationService, + private readonly tagRepository: TagRepository, ) {} async listDevicesInSubspace( @@ -80,6 +82,35 @@ export class SubspaceDeviceService { const subspace = await this.findSubspace(subSpaceUuid); const device = await this.findDevice(deviceUuid); + console.log(device); + + if (device.tag) { + console.log(device.tag); + const tag = device.tag; + if (tag.subspace !== null && tag.subspace.uuid === subspace.uuid) { + //do nothing + } + if (tag.subspace !== null && tag.subspace.uuid !== subspace.uuid) { + await this.tagRepository.update( + { + uuid: tag.uuid, + }, + { + subspace: subspace, + }, + ); + } + if (tag.subspace === null) { + await this.tagRepository.update( + { + uuid: tag.uuid, + }, + { + subspace: subspace, + }, + ); + } + } device.subspace = subspace; @@ -123,6 +154,24 @@ export class SubspaceDeviceService { ); } + if (device.tag) { + console.log(device.tag); + const tag = device.tag; + if (tag.subspace === null) { + //do nothing + } + if (tag.subspace !== null) { + await this.tagRepository.update( + { + uuid: tag.uuid, + }, + { + subspace: null, + space: device.subspace, + }, + ); + } + } device.subspace = null; const updatedDevice = await this.deviceRepository.save(device); @@ -167,7 +216,7 @@ export class SubspaceDeviceService { private async findDevice(deviceUuid: string) { const device = await this.deviceRepository.findOne({ where: { uuid: deviceUuid }, - relations: ['subspace'], + relations: ['subspace', 'tag', 'tag.space', 'tag.subspace'], }); if (!device) { this.throwNotFound('Device', deviceUuid); diff --git a/src/space/services/tag/tag.service.ts b/src/space/services/tag/tag.service.ts index 03bc42d..c4bc34c 100644 --- a/src/space/services/tag/tag.service.ts +++ b/src/space/services/tag/tag.service.ts @@ -8,7 +8,6 @@ import { import { TagModel } from '@app/common/modules/space-model'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { ProductService } from 'src/product/services'; -import { ModifyTagModelDto } from 'src/space-model/dtos'; import { CreateTagDto, ModifySubspaceDto } from 'src/space/dtos'; import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto'; import { QueryRunner } from 'typeorm'; @@ -32,10 +31,13 @@ export class TagService { const combinedTags = this.combineTags(tags, additionalTags); this.ensureNoDuplicateTags(combinedTags); + console.log(tags); - const tagEntitiesToCreate = tags.filter((tagDto) => tagDto.uuid === null); + const tagEntitiesToCreate = tags.filter((tagDto) => !tagDto.uuid); const tagEntitiesToUpdate = tags.filter((tagDto) => tagDto.uuid !== null); + console.log(tagEntitiesToCreate); + try { const createdTags = await this.bulkSaveTags( tagEntitiesToCreate, @@ -79,7 +81,8 @@ export class TagService { ), ), ); - + console.log('here'); + console.log(JSON.stringify(tags)); try { return await queryRunner.manager.save(tagEntities); } catch (error) { From f77d52b6be0647f9e4cb09c0f27aa46bd726d925 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 30 Jan 2025 21:52:59 +0400 Subject: [PATCH 229/247] fixed deleting subspace --- .../services/space-model.service.ts | 1 + .../subspace/subspace-model.service.ts | 18 +++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 2162c38..3071b16 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -211,6 +211,7 @@ export class SpaceModelService { ); if (dto.subspaceModels) { + console.log(JSON.stringify(modifiedSubspaces)); modifiedSubspaceModels = await this.subSpaceModelService.modifySubSpaceModels( modifiedSubspaces, diff --git a/src/space-model/services/subspace/subspace-model.service.ts b/src/space-model/services/subspace/subspace-model.service.ts index dd3fe91..bd76a9c 100644 --- a/src/space-model/services/subspace/subspace-model.service.ts +++ b/src/space-model/services/subspace/subspace-model.service.ts @@ -243,15 +243,19 @@ export class SubSpaceModelService { ); if (subspaceModel.tags?.length) { - const modifyTagDtos = subspaceModel.tags.map((tag) => ({ - uuid: tag.uuid, - action: ModifyAction.DELETE, - })); - await this.tagModelService.modifyTags( + const modifyTagDtos: CreateTagModelDto[] = subspaceModel.tags.map( + (tag) => ({ + uuid: tag.uuid, + action: ModifyAction.ADD, + tag: tag.tag, + productUuid: tag.product.uuid, + }), + ); + await this.tagModelService.moveTags( modifyTagDtos, queryRunner, + subspaceModel.spaceModel, null, - subspaceModel, ); } } @@ -259,7 +263,7 @@ export class SubSpaceModelService { private async findOne(subspaceUuid: string): Promise { const subspace = await this.subspaceModelRepository.findOne({ where: { uuid: subspaceUuid, disabled: false }, - relations: ['tags', 'spaceModel'], + relations: ['tags', 'spaceModel', 'tags.product'], }); if (!subspace) { throw new HttpException( From a8f966a221d4675ddd9ea1f4e32fcc3f6496ba6b Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 31 Jan 2025 11:07:06 +0400 Subject: [PATCH 230/247] removed logs --- .../services/space-model.service.ts | 1 - src/space-model/services/tag-model.service.ts | 2 +- src/space/services/space.service.ts | 58 +++++++++++++++---- .../subspace/subspace-device.service.ts | 55 ++++-------------- src/space/services/tag/tag.service.ts | 10 +--- 5 files changed, 63 insertions(+), 63 deletions(-) diff --git a/src/space-model/services/space-model.service.ts b/src/space-model/services/space-model.service.ts index 3071b16..2162c38 100644 --- a/src/space-model/services/space-model.service.ts +++ b/src/space-model/services/space-model.service.ts @@ -211,7 +211,6 @@ export class SpaceModelService { ); if (dto.subspaceModels) { - console.log(JSON.stringify(modifiedSubspaces)); modifiedSubspaceModels = await this.subSpaceModelService.modifySubSpaceModels( modifiedSubspaces, diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index 3f6a493..7394ed8 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -146,7 +146,7 @@ export class TagModelService { tag.subspaceModel = subspaceModel; } - if (subspaceModel === null && spaceModel) { + if (!subspaceModel && spaceModel) { await queryRunner.manager.update( this.tagModelRepository.target, { uuid: tag.uuid }, diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 2072758..c1e21c6 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -172,8 +172,6 @@ export class SpaceService { ): Promise { const { communityUuid, projectUuid } = params; const { onlyWithDevices } = getSpaceDto; - console.log('onlyWithDevices', onlyWithDevices); - await this.validationService.validateCommunityAndProject( communityUuid, projectUuid, @@ -244,12 +242,53 @@ export class SpaceService { async findOne(params: GetSpaceParam): Promise { const { communityUuid, spaceUuid, projectUuid } = params; try { - const space = - await this.validationService.validateSpaceWithinCommunityAndProject( - communityUuid, - projectUuid, - spaceUuid, - ); + await this.validationService.validateCommunityAndProject( + communityUuid, + projectUuid, + ); + + const queryBuilder = this.spaceRepository + .createQueryBuilder('space') + .leftJoinAndSelect('space.parent', 'parent') + .leftJoinAndSelect( + 'space.children', + 'children', + 'children.disabled = :disabled', + { disabled: false }, + ) + .leftJoinAndSelect( + 'space.incomingConnections', + 'incomingConnections', + 'incomingConnections.disabled = :incomingConnectionDisabled', + { incomingConnectionDisabled: false }, + ) + .leftJoinAndSelect( + 'space.tags', + 'tags', + 'tags.disabled = :tagDisabled', + { tagDisabled: false }, + ) + .leftJoinAndSelect('tags.product', 'tagProduct') + .leftJoinAndSelect( + 'space.subspaces', + 'subspaces', + 'subspaces.disabled = :subspaceDisabled', + { subspaceDisabled: false }, + ) + .leftJoinAndSelect( + 'subspaces.tags', + 'subspaceTags', + 'subspaceTags.disabled = :subspaceTagsDisabled', + { subspaceTagsDisabled: false }, + ) + .leftJoinAndSelect('subspaceTags.product', 'subspaceTagProduct') + .where('space.community_id = :communityUuid', { communityUuid }) + .andWhere('space.spaceName != :orphanSpaceName', { + orphanSpaceName: ORPHAN_SPACE_NAME, + }) + .andWhere('space.disabled = :disabled', { disabled: false }); + + const space = await queryBuilder.getOne(); return new SuccessResponseDto({ message: `Space with ID ${spaceUuid} successfully fetched`, @@ -323,8 +362,7 @@ export class SpaceService { updateSpaceDto: UpdateSpaceDto, ): Promise { const { communityUuid, spaceUuid, projectUuid } = params; - console.log(communityUuid, spaceUuid, projectUuid); - console.log(updateSpaceDto); + const queryRunner = this.dataSource.createQueryRunner(); try { diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index 97b6080..6ad6ef3 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -82,34 +82,12 @@ export class SubspaceDeviceService { const subspace = await this.findSubspace(subSpaceUuid); const device = await this.findDevice(deviceUuid); - console.log(device); - if (device.tag) { - console.log(device.tag); - const tag = device.tag; - if (tag.subspace !== null && tag.subspace.uuid === subspace.uuid) { - //do nothing - } - if (tag.subspace !== null && tag.subspace.uuid !== subspace.uuid) { - await this.tagRepository.update( - { - uuid: tag.uuid, - }, - { - subspace: subspace, - }, - ); - } - if (tag.subspace === null) { - await this.tagRepository.update( - { - uuid: tag.uuid, - }, - { - subspace: subspace, - }, - ); - } + if (device.tag?.subspace?.uuid !== subspace.uuid) { + await this.tagRepository.update( + { uuid: device.tag.uuid }, + { subspace }, + ); } device.subspace = subspace; @@ -154,24 +132,13 @@ export class SubspaceDeviceService { ); } - if (device.tag) { - console.log(device.tag); - const tag = device.tag; - if (tag.subspace === null) { - //do nothing - } - if (tag.subspace !== null) { - await this.tagRepository.update( - { - uuid: tag.uuid, - }, - { - subspace: null, - space: device.subspace, - }, - ); - } + if (device.tag?.subspace !== null) { + await this.tagRepository.update( + { uuid: device.tag.uuid }, + { subspace: null, space: device.subspace }, + ); } + device.subspace = null; const updatedDevice = await this.deviceRepository.save(device); diff --git a/src/space/services/tag/tag.service.ts b/src/space/services/tag/tag.service.ts index c4bc34c..f5059e6 100644 --- a/src/space/services/tag/tag.service.ts +++ b/src/space/services/tag/tag.service.ts @@ -31,13 +31,10 @@ export class TagService { const combinedTags = this.combineTags(tags, additionalTags); this.ensureNoDuplicateTags(combinedTags); - console.log(tags); const tagEntitiesToCreate = tags.filter((tagDto) => !tagDto.uuid); const tagEntitiesToUpdate = tags.filter((tagDto) => tagDto.uuid !== null); - console.log(tagEntitiesToCreate); - try { const createdTags = await this.bulkSaveTags( tagEntitiesToCreate, @@ -81,8 +78,7 @@ export class TagService { ), ), ); - console.log('here'); - console.log(JSON.stringify(tags)); + try { return await queryRunner.manager.save(tagEntities); } catch (error) { @@ -128,7 +124,7 @@ export class TagService { tag.subspace = subspace; } - if (subspace === null && space) { + if (!subspace && space) { await queryRunner.manager.update( this.tagRepository.target, { uuid: tag.uuid }, @@ -187,7 +183,7 @@ export class TagService { const contextSpace = space ?? subspace?.space; - if (contextSpace) { + if (contextSpace && tag.tag !== existingTag.tag) { await this.checkTagReuse( tag.tag, existingTag.product.uuid, From 96852b3d918cac8ddbf2637255eead360525fee7 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 31 Jan 2025 12:06:16 +0400 Subject: [PATCH 231/247] auto generate tag if not there --- .../subspace/subspace-device.service.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index 6ad6ef3..e993716 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -90,6 +90,17 @@ export class SubspaceDeviceService { ); } + if (!device.tag) { + const tag = this.tagRepository.create({ + tag: `Tag ${this.findNextTag()}`, + product: device.productDevice, + subspace: subspace, + device: device, + }); + await this.tagRepository.save(tag); + device.tag = tag; + } + device.subspace = subspace; const newDevice = await this.deviceRepository.save(device); @@ -139,6 +150,19 @@ export class SubspaceDeviceService { ); } + if (!device.tag) { + const tag = this.tagRepository.create({ + tag: `Tag ${this.findNextTag()}`, + product: device.productDevice, + subspace: null, + space: device.spaceDevice, + device: device, + }); + + await this.tagRepository.save(tag); + device.tag = tag; + } + device.subspace = null; const updatedDevice = await this.deviceRepository.save(device); @@ -250,4 +274,19 @@ export class SubspaceDeviceService { ); } } + + async findNextTag(): Promise { + const tags = await this.tagRepository.find({ select: ['tag'] }); + + const tagNumbers = tags + .map((t) => t.tag.match(/^Tag (\d+)$/)) + .filter((match) => match) + .map((match) => parseInt(match[1])) + .sort((a, b) => a - b); + + const nextTagNumber = tagNumbers.length + ? tagNumbers[tagNumbers.length - 1] + 1 + : 1; + return nextTagNumber; + } } From 87c2722313025739434dfc43cf6341df082a1357 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sun, 2 Feb 2025 23:15:00 +0400 Subject: [PATCH 232/247] fix space --- src/space/services/space.service.ts | 10 ++++++++-- src/space/services/subspace/subspace.service.ts | 11 +++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index c1e21c6..fa9c30c 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -212,6 +212,7 @@ export class SpaceService { { subspaceTagsDisabled: false }, ) .leftJoinAndSelect('subspaceTags.product', 'subspaceTagProduct') + .leftJoinAndSelect('space.spaceModel', 'spaceModel') .where('space.community_id = :communityUuid', { communityUuid }) .andWhere('space.spaceName != :orphanSpaceName', { orphanSpaceName: ORPHAN_SPACE_NAME, @@ -286,6 +287,7 @@ export class SpaceService { .andWhere('space.spaceName != :orphanSpaceName', { orphanSpaceName: ORPHAN_SPACE_NAME, }) + .andWhere('space.uuid = :spaceUuid', { spaceUuid }) .andWhere('space.disabled = :disabled', { disabled: false }); const space = await queryBuilder.getOne(); @@ -362,7 +364,7 @@ export class SpaceService { updateSpaceDto: UpdateSpaceDto, ): Promise { const { communityUuid, spaceUuid, projectUuid } = params; - + const queryRunner = this.dataSource.createQueryRunner(); try { @@ -584,7 +586,11 @@ export class SpaceService { addSpaceDto: AddSpaceDto, spaceModelUuid?: string, ) { - if (spaceModelUuid && (addSpaceDto.tags || addSpaceDto.subspaces)) { + const hasTagsOrSubspaces = + (addSpaceDto.tags && addSpaceDto.tags.length > 0) || + (addSpaceDto.subspaces && addSpaceDto.subspaces.length > 0); + + if (spaceModelUuid && hasTagsOrSubspaces) { throw new HttpException( 'For space creation choose either space model or products and subspace', HttpStatus.CONFLICT, diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 599a282..3a27aee 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -387,6 +387,7 @@ export class SubSpaceService { const createTagDtos: CreateTagDto[] = subspace.tags?.map((tag) => ({ tag: tag.tag as string, + uuid: tag.uuid, productUuid: tag.productUuid as string, })) || []; const subSpace = await this.createSubspacesFromDto( @@ -441,15 +442,17 @@ export class SubSpaceService { ); if (subspace.tags?.length) { - const modifyTagDtos = subspace.tags.map((tag) => ({ + const modifyTagDtos: CreateTagDto[] = subspace.tags.map((tag) => ({ uuid: tag.uuid, - action: ModifyAction.DELETE, + action: ModifyAction.ADD, + tag: tag.tag, + productUuid: tag.product.uuid, })); - await this.tagService.modifyTags( + await this.tagService.moveTags( modifyTagDtos, queryRunner, + subspace.space, null, - subspace, ); } From 7f69867e8a611d735ce6b39e77393d1c75320d46 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 3 Feb 2025 00:13:34 +0400 Subject: [PATCH 233/247] optimizing space call --- .../modules/device/entities/device.entity.ts | 1 - .../services/space-validation.service.ts | 37 +++++++++++++++++-- .../subspace/subspace-device.service.ts | 25 +++++++++---- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index f2a5a1e..bdd1ce5 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -78,7 +78,6 @@ export class DeviceEntity extends AbstractEntity { @OneToOne(() => TagEntity, (tag) => tag.device, { nullable: true, }) - @JoinColumn({ name: 'tag_id' }) tag: TagEntity; constructor(partial: Partial) { diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts index 4a3e32a..304586b 100644 --- a/src/space/services/space-validation.service.ts +++ b/src/space/services/space-validation.service.ts @@ -7,6 +7,8 @@ import { SpaceModelEntity, SpaceModelRepository, } from '@app/common/modules/space-model'; +import { ProjectRepository } from '@app/common/modules/project/repositiories'; +import { CommunityRepository } from '@app/common/modules/community/repositories'; @Injectable() export class ValidationService { @@ -14,6 +16,8 @@ export class ValidationService { private readonly projectService: ProjectService, private readonly communityService: CommunityService, private readonly spaceRepository: SpaceRepository, + private readonly projectRepository: ProjectRepository, + private readonly communityRepository: CommunityRepository, private readonly spaceModelRepository: SpaceModelRepository, ) {} @@ -30,6 +34,31 @@ export class ValidationService { return { community: community.data, project: project }; } + async checkCommunityAndProjectSpaceExistence( + communityUuid: string, + projectUuid: string, + spaceUuid?: string, + ): Promise { + const [projectExists, communityExists, spaceExists] = await Promise.all([ + this.projectRepository.exists({ where: { uuid: projectUuid } }), + this.communityRepository.exists({ + where: { uuid: communityUuid, project: { uuid: projectUuid } }, + }), + spaceUuid + ? this.spaceRepository.exists({ + where: { uuid: spaceUuid }, + }) + : Promise.resolve(true), + ]); + + if (!projectExists) + throw new HttpException(`Project not found`, HttpStatus.NOT_FOUND); + if (!communityExists) + throw new HttpException(`Community not found`, HttpStatus.NOT_FOUND); + if (spaceUuid && !spaceExists) + throw new HttpException(`Space not found`, HttpStatus.NOT_FOUND); + } + async validateSpaceWithinCommunityAndProject( communityUuid: string, projectUuid: string, @@ -50,10 +79,10 @@ export class ValidationService { 'tags', 'subspaces.tags', 'subspaces.devices', - 'devices', - 'devices.productDevice', - 'devices.tag', - 'devices.subspace', + //'devices', + //'devices.productDevice', + //'devices.tag', + // 'devices.subspace', ], }); diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index e993716..9ee9b9b 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -29,14 +29,22 @@ export class SubspaceDeviceService { ): Promise { const { subSpaceUuid, spaceUuid, communityUuid, projectUuid } = params; - await this.validationService.validateSpaceWithinCommunityAndProject( + console.time('Total Execution Time'); // ⏳ Start total execution time + console.time('Validation Time'); + + await this.validationService.checkCommunityAndProjectSpaceExistence( communityUuid, projectUuid, spaceUuid, ); - const subspace = await this.findSubspaceWithDevices(subSpaceUuid); + console.timeEnd('Validation Time'); // ⏳ Log validation time + console.time('Subspace Query Time'); + const subspace = await this.findSubspaceWithDevices(subSpaceUuid); + console.timeEnd('Subspace Query Time'); // ⏳ Log subspace fetching time + + console.time('Fetching Tuya Details Time'); const safeFetch = async (device: any) => { try { const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya( @@ -55,12 +63,16 @@ export class SubspaceDeviceService { }; } catch (error) { console.warn( - `Skipping device with deviceTuyaUuid: ${device.deviceTuyaUuid} due to error.`, + `⚠️ Skipping device with deviceTuyaUuid: ${device.deviceTuyaUuid} due to error.`, ); return null; } }; + const detailedDevices = await Promise.all(subspace.devices.map(safeFetch)); + console.timeEnd('Fetching Tuya Details Time'); // ⏳ Log total Tuya fetching time + + console.timeEnd('Total Execution Time'); // ⏳ End total execution time return new SuccessResponseDto({ data: detailedDevices.filter(Boolean), // Remove nulls @@ -74,7 +86,7 @@ export class SubspaceDeviceService { const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid, projectUuid } = params; try { - await this.validationService.validateSpaceWithinCommunityAndProject( + await this.validationService.checkCommunityAndProjectSpaceExistence( communityUuid, projectUuid, spaceUuid, @@ -127,12 +139,11 @@ export class SubspaceDeviceService { const { subSpaceUuid, deviceUuid, spaceUuid, communityUuid, projectUuid } = params; try { - await this.validationService.validateSpaceWithinCommunityAndProject( + await this.validationService.checkCommunityAndProjectSpaceExistence( communityUuid, projectUuid, spaceUuid, ); - const subspace = await this.findSubspace(subSpaceUuid); const device = await this.findDevice(deviceUuid); @@ -207,7 +218,7 @@ export class SubspaceDeviceService { private async findDevice(deviceUuid: string) { const device = await this.deviceRepository.findOne({ where: { uuid: deviceUuid }, - relations: ['subspace', 'tag', 'tag.space', 'tag.subspace'], + relations: ['subspace', , 'tag', 'tag.space', 'tag.subspace'], }); if (!device) { this.throwNotFound('Device', deviceUuid); From 427e764539918d8c30984f74bbffb556c8d88fc6 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 3 Feb 2025 00:43:38 +0400 Subject: [PATCH 234/247] faster querying --- src/invite-user/invite-user.module.ts | 6 +++++- src/space/services/space-device.service.ts | 2 +- src/space/services/space-validation.service.ts | 16 ++++++++++++---- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index 6b77d1d..7efbe2f 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -25,7 +25,10 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; import { UserService, UserSpaceService } from 'src/users/services'; import { UserDevicePermissionService } from 'src/user-device-permission/services'; -import { DeviceUserPermissionRepository } from '@app/common/modules/device/repositories'; +import { + DeviceRepository, + DeviceUserPermissionRepository, +} from '@app/common/modules/device/repositories'; import { PermissionTypeRepository } from '@app/common/modules/permission/repositories'; import { ProjectUserService } from 'src/project/services/project-user.service'; import { RoleTypeRepository } from '@app/common/modules/role-type/repositories'; @@ -44,6 +47,7 @@ import { TimeZoneRepository } from '@app/common/modules/timezone/repositories'; SpaceUserService, ValidationService, UserSpaceRepository, + DeviceRepository, CommunityService, SpaceRepository, SpaceModelRepository, diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index 505e49f..aaa7d12 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -26,7 +26,7 @@ export class SpaceDeviceService { spaceUuid, ); - if (space.devices.length === 0) { + if (!space.devices || space.devices.length === 0) { throw new HttpException( 'The space does not contain any devices.', HttpStatus.BAD_REQUEST, diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts index 304586b..76750dd 100644 --- a/src/space/services/space-validation.service.ts +++ b/src/space/services/space-validation.service.ts @@ -9,6 +9,7 @@ import { } from '@app/common/modules/space-model'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { CommunityRepository } from '@app/common/modules/community/repositories'; +import { DeviceRepository } from '@app/common/modules/device/repositories'; @Injectable() export class ValidationService { @@ -19,6 +20,7 @@ export class ValidationService { private readonly projectRepository: ProjectRepository, private readonly communityRepository: CommunityRepository, private readonly spaceModelRepository: SpaceModelRepository, + private readonly deviceRepository: DeviceRepository, ) {} async validateCommunityAndProject( @@ -65,7 +67,9 @@ export class ValidationService { spaceUuid?: string, ): Promise { await this.validateCommunityAndProject(communityUuid, projectUuid); + const space = await this.validateSpace(spaceUuid); + return space; } @@ -79,10 +83,6 @@ export class ValidationService { 'tags', 'subspaces.tags', 'subspaces.devices', - //'devices', - //'devices.productDevice', - //'devices.tag', - // 'devices.subspace', ], }); @@ -93,6 +93,14 @@ export class ValidationService { ); } + const devices = await this.deviceRepository.find({ + where: { spaceDevice: { uuid: spaceUuid } }, + select: ['uuid', 'deviceTuyaUuid', 'isActive', 'createdAt', 'updatedAt'], + relations: ['productDevice', 'tag', 'subspace'], + }); + + space.devices = devices; + return space; } From 3bb88a6e97e3ac2cba15b96e51b0a04685d44711 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 3 Feb 2025 11:54:49 +0400 Subject: [PATCH 235/247] fixed issue in association and disassociation --- .../src/modules/space/entities/tag.entity.ts | 10 +---- src/space/services/space-scene.service.ts | 2 +- src/space/services/space-user.service.ts | 2 +- src/space/services/space.service.ts | 2 +- .../subspace/subspace-device.service.ts | 40 +++++++++---------- .../services/subspace/subspace.service.ts | 8 ++-- 6 files changed, 28 insertions(+), 36 deletions(-) diff --git a/libs/common/src/modules/space/entities/tag.entity.ts b/libs/common/src/modules/space/entities/tag.entity.ts index 6ba1289..7340caf 100644 --- a/libs/common/src/modules/space/entities/tag.entity.ts +++ b/libs/common/src/modules/space/entities/tag.entity.ts @@ -1,11 +1,4 @@ -import { - Entity, - Column, - ManyToOne, - JoinColumn, - Unique, - OneToOne, -} from 'typeorm'; +import { Entity, Column, ManyToOne, JoinColumn, OneToOne } from 'typeorm'; import { AbstractEntity } from '../../abstract/entities/abstract.entity'; import { ProductEntity } from '../../product/entities'; import { TagDto } from '../dtos'; @@ -15,7 +8,6 @@ import { SubspaceEntity } from './subspace'; import { DeviceEntity } from '../../device/entities'; @Entity({ name: 'tag' }) -@Unique(['tag', 'product', 'space', 'subspace']) export class TagEntity extends AbstractEntity { @Column({ type: 'varchar', length: 255 }) tag: string; diff --git a/src/space/services/space-scene.service.ts b/src/space/services/space-scene.service.ts index 6c6b528..f8d2c42 100644 --- a/src/space/services/space-scene.service.ts +++ b/src/space/services/space-scene.service.ts @@ -23,7 +23,7 @@ export class SpaceSceneService { try { const { spaceUuid, communityUuid, projectUuid } = params; - await this.validationService.validateSpaceWithinCommunityAndProject( + await this.validationService.checkCommunityAndProjectSpaceExistence( communityUuid, projectUuid, spaceUuid, diff --git a/src/space/services/space-user.service.ts b/src/space/services/space-user.service.ts index 0912132..fe94983 100644 --- a/src/space/services/space-user.service.ts +++ b/src/space/services/space-user.service.ts @@ -72,7 +72,7 @@ export class SpaceUserService { } // Find the space by ID - await this.validationService.validateSpaceWithinCommunityAndProject( + await this.validationService.checkCommunityAndProjectSpaceExistence( communityUuid, projectUuid, spaceUuid, diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index fa9c30c..43266e5 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -492,7 +492,7 @@ export class SpaceService { params: GetSpaceParam, ): Promise { const { spaceUuid, communityUuid, projectUuid } = params; - await this.validationService.validateSpaceWithinCommunityAndProject( + await this.validationService.checkCommunityAndProjectSpaceExistence( communityUuid, projectUuid, spaceUuid, diff --git a/src/space/services/subspace/subspace-device.service.ts b/src/space/services/subspace/subspace-device.service.ts index 9ee9b9b..cdd80db 100644 --- a/src/space/services/subspace/subspace-device.service.ts +++ b/src/space/services/subspace/subspace-device.service.ts @@ -29,22 +29,14 @@ export class SubspaceDeviceService { ): Promise { const { subSpaceUuid, spaceUuid, communityUuid, projectUuid } = params; - console.time('Total Execution Time'); // ⏳ Start total execution time - console.time('Validation Time'); - await this.validationService.checkCommunityAndProjectSpaceExistence( communityUuid, projectUuid, spaceUuid, ); - console.timeEnd('Validation Time'); // ⏳ Log validation time - - console.time('Subspace Query Time'); const subspace = await this.findSubspaceWithDevices(subSpaceUuid); - console.timeEnd('Subspace Query Time'); // ⏳ Log subspace fetching time - console.time('Fetching Tuya Details Time'); const safeFetch = async (device: any) => { try { const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya( @@ -62,17 +54,11 @@ export class SubspaceDeviceService { ...tuyaDetails, }; } catch (error) { - console.warn( - `⚠️ Skipping device with deviceTuyaUuid: ${device.deviceTuyaUuid} due to error.`, - ); return null; } }; const detailedDevices = await Promise.all(subspace.devices.map(safeFetch)); - console.timeEnd('Fetching Tuya Details Time'); // ⏳ Log total Tuya fetching time - - console.timeEnd('Total Execution Time'); // ⏳ End total execution time return new SuccessResponseDto({ data: detailedDevices.filter(Boolean), // Remove nulls @@ -119,14 +105,14 @@ export class SubspaceDeviceService { return new SuccessResponseDto({ data: newDevice, - message: 'Successfully associated device to subspace', + message: `Successfully associated device to subspace`, }); } catch (error) { if (error instanceof HttpException) { throw error; } else { throw new HttpException( - 'Failed to associate device to subspace', + `Failed to associate device to subspace with error = ${error}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -145,7 +131,7 @@ export class SubspaceDeviceService { spaceUuid, ); const subspace = await this.findSubspace(subSpaceUuid); - const device = await this.findDevice(deviceUuid); + const device = await this.findDeviceWithSubspaceAndTag(deviceUuid); if (!device.subspace || device.subspace.uuid !== subspace.uuid) { throw new HttpException( @@ -157,7 +143,7 @@ export class SubspaceDeviceService { if (device.tag?.subspace !== null) { await this.tagRepository.update( { uuid: device.tag.uuid }, - { subspace: null, space: device.subspace }, + { subspace: null, space: device.spaceDevice }, ); } @@ -186,7 +172,7 @@ export class SubspaceDeviceService { throw error; } else { throw new HttpException( - 'Failed to dissociate device from subspace', + `Failed to dissociate device from subspace error = ${error}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -218,7 +204,13 @@ export class SubspaceDeviceService { private async findDevice(deviceUuid: string) { const device = await this.deviceRepository.findOne({ where: { uuid: deviceUuid }, - relations: ['subspace', , 'tag', 'tag.space', 'tag.subspace'], + relations: [ + 'subspace', + 'tag', + 'tag.space', + 'tag.subspace', + 'spaceDevice', + ], }); if (!device) { this.throwNotFound('Device', deviceUuid); @@ -300,4 +292,12 @@ export class SubspaceDeviceService { : 1; return nextTagNumber; } + + private async findDeviceWithSubspaceAndTag(deviceUuid: string) { + return await this.deviceRepository.findOne({ + where: { uuid: deviceUuid }, + relations: ['subspace', 'tag', 'spaceDevice'], + select: ['uuid', 'subspace', 'spaceDevice', 'productDevice', 'tag'], + }); + } } diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 3a27aee..78f9445 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -178,7 +178,7 @@ export class SubSpaceService { pageable: Partial, ): Promise { const { communityUuid, spaceUuid, projectUuid } = params; - await this.validationService.validateSpaceWithinCommunityAndProject( + await this.validationService.checkCommunityAndProjectSpaceExistence( communityUuid, projectUuid, spaceUuid, @@ -205,7 +205,7 @@ export class SubSpaceService { updateSubSpaceDto: AddSubspaceDto, ): Promise { const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params; - await this.validationService.validateSpaceWithinCommunityAndProject( + await this.validationService.checkCommunityAndProjectSpaceExistence( communityUuid, projectUuid, spaceUuid, @@ -244,7 +244,7 @@ export class SubSpaceService { async delete(params: GetSubSpaceParam): Promise { const { spaceUuid, communityUuid, subSpaceUuid, projectUuid } = params; - await this.validationService.validateSpaceWithinCommunityAndProject( + await this.validationService.checkCommunityAndProjectSpaceExistence( communityUuid, projectUuid, spaceUuid, @@ -367,7 +367,7 @@ export class SubSpaceService { } async getOne(params: GetSubSpaceParam): Promise { - await this.validationService.validateSpaceWithinCommunityAndProject( + await this.validationService.checkCommunityAndProjectSpaceExistence( params.communityUuid, params.projectUuid, params.spaceUuid, From 9dcb68da2dd3fc2725e8da99e9d56131686e2639 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 3 Feb 2025 07:38:19 -0600 Subject: [PATCH 236/247] Add function to remove circular references and use it in SpaceService --- libs/common/src/helper/removeCircularReferences.ts | 12 ++++++++++++ src/space/services/space.service.ts | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 libs/common/src/helper/removeCircularReferences.ts diff --git a/libs/common/src/helper/removeCircularReferences.ts b/libs/common/src/helper/removeCircularReferences.ts new file mode 100644 index 0000000..87eae74 --- /dev/null +++ b/libs/common/src/helper/removeCircularReferences.ts @@ -0,0 +1,12 @@ +export function removeCircularReferences() { + const seen = new WeakSet(); + return (key: string, value: any) => { + if (typeof value === 'object' && value !== null) { + if (seen.has(value)) { + return undefined; // Skip circular reference + } + seen.add(value); + } + return value; + }; +} diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index 43266e5..b575ab6 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -33,6 +33,7 @@ import { TagService } from './tag'; import { SpaceModelService } from 'src/space-model/services'; import { DisableSpaceCommand } from '../commands'; import { GetSpaceDto } from '../dtos/get.space.dto'; +import { removeCircularReferences } from '@app/common/helper/removeCircularReferences'; @Injectable() export class SpaceService { constructor( @@ -116,7 +117,7 @@ export class SpaceService { return new SuccessResponseDto({ statusCode: HttpStatus.CREATED, - data: newSpace, + data: JSON.parse(JSON.stringify(newSpace, removeCircularReferences())), message: 'Space created successfully', }); } catch (error) { From ed6662a08af6a3b9f6f5abd3cd5755ce204b89cb Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 4 Feb 2025 00:52:43 -0600 Subject: [PATCH 237/247] Update permission requirement for automation status update --- src/automation/controllers/automation.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/automation/controllers/automation.controller.ts b/src/automation/controllers/automation.controller.ts index 265638c..864d551 100644 --- a/src/automation/controllers/automation.controller.ts +++ b/src/automation/controllers/automation.controller.ts @@ -125,7 +125,7 @@ export class AutomationController { @ApiBearerAuth() @UseGuards(PermissionsGuard) - @Permissions('AUTOMATION_UPDATE') + @Permissions('AUTOMATION_CONTROL') @Put('status/:automationUuid') @ApiOperation({ summary: From 6e942109fae6890315f498df8e9dc2410d7d6758 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 4 Feb 2025 01:11:10 -0600 Subject: [PATCH 238/247] Update user role in repository when inviting user --- src/invite-user/services/invite-user.service.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index df74f21..940b3e9 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -509,7 +509,12 @@ export class InviteUserService { roleType: { uuid: roleUuid }, }, ); - + await this.userRepository.update( + { uuid: userData.user.uuid }, + { + roleType: { uuid: roleUuid }, + }, + ); // Disassociate the user from all current spaces const disassociatePromises = userData.user.userSpaces.map((userSpace) => this.spaceUserService From a92497603f4ec60cc50d5d2ce279ec710d2c0750 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 4 Feb 2025 01:56:56 -0600 Subject: [PATCH 239/247] Check user active status before sign-up and invite --- src/auth/services/user-auth.service.ts | 3 +++ src/invite-user/services/invite-user.service.ts | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/auth/services/user-auth.service.ts b/src/auth/services/user-auth.service.ts index aaa2b34..6e7780c 100644 --- a/src/auth/services/user-auth.service.ts +++ b/src/auth/services/user-auth.service.ts @@ -36,6 +36,9 @@ export class UserAuthService { async signUp(userSignUpDto: UserSignUpDto): Promise { const findUser = await this.findUser(userSignUpDto.email); + if (!findUser.isActive) { + throw new BadRequestException('User is not active'); + } if (findUser) { throw new BadRequestException('User already registered with given email'); } diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index df74f21..d5205d6 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -181,7 +181,9 @@ export class InviteUserService { where: { email }, relations: ['project'], }); - + if (!user.isActive) { + throw new HttpException('This user is deleted', HttpStatus.BAD_REQUEST); + } if (user?.project) { throw new HttpException( 'This email already has a project', @@ -193,7 +195,9 @@ export class InviteUserService { where: { email }, relations: ['project'], }); - + if (!invitedUser.isActive) { + throw new HttpException('This user is deleted', HttpStatus.BAD_REQUEST); + } if (invitedUser?.project) { throw new HttpException( 'This email already has a project', From c9f2da1b57ff73424a8461f317e6f1318dc0a65d Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 4 Feb 2025 02:14:27 -0600 Subject: [PATCH 240/247] Add missing permissions for SPACE_OWNER role --- libs/common/src/constants/role-permissions.ts | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/libs/common/src/constants/role-permissions.ts b/libs/common/src/constants/role-permissions.ts index 906cbd2..96655f7 100644 --- a/libs/common/src/constants/role-permissions.ts +++ b/libs/common/src/constants/role-permissions.ts @@ -66,6 +66,7 @@ export const RolePermissions = { 'COMMUNITY_UPDATE', 'COMMUNITY_DELETE', 'FIRMWARE_CONTROL', + 'FIRMWARE_VIEW', 'SPACE_VIEW', 'SPACE_ADD', 'SPACE_UPDATE', @@ -120,16 +121,30 @@ export const RolePermissions = { [RoleType.SPACE_OWNER]: [ 'DEVICE_SINGLE_CONTROL', 'DEVICE_VIEW', + 'DEVICE_DELETE', + 'DEVICE_UPDATE', + 'DEVICE_BATCH_CONTROL', + 'DEVICE_LOCATION_VIEW', + 'DEVICE_LOCATION_UPDATE', 'FIRMWARE_CONTROL', 'FIRMWARE_VIEW', 'SPACE_VIEW', - 'DEVICE_LOCATION_VIEW', - 'DEVICE_LOCATION_UPDATE', - 'SPACE_MEMBER_ADD', + 'SPACE_ADD', + 'SPACE_UPDATE', + 'SPACE_DELETE', + 'SPACE_ASSIGN_USER_TO_SPACE', + 'SPACE_DELETE_USER_FROM_SPACE', 'SUBSPACE_VIEW', 'SUBSPACE_ADD', 'SUBSPACE_UPDATE', 'SUBSPACE_DELETE', + 'SUBSPACE_ASSIGN_DEVICE_TO_SUBSPACE', + 'SUBSPACE_DELETE_DEVICE_FROM_SUBSPACE', + 'DEVICE_WIZARD_VIEW_DEVICE_WIZARD', + 'SUBSPACE_DEVICE_VIEW_DEVICE_IN_SUBSPACE', + 'SPACE_DEVICE_VIEW_DEVICE_IN_SPACE', + 'SUBSPACE_DEVICE_UPDATE_DEVICE_IN_SUBSPACE', + 'SPACE_DEVICE_ASSIGN_DEVICE_TO_SPACE', 'AUTOMATION_VIEW', 'AUTOMATION_ADD', 'AUTOMATION_UPDATE', @@ -144,8 +159,7 @@ export const RolePermissions = { 'VISITOR_PASSWORD_ADD', 'VISITOR_PASSWORD_UPDATE', 'VISITOR_PASSWORD_DELETE', - 'DEVICE_WIZARD_VIEW_DEVICE_WIZARD', - 'SPACE_ASSIGN_USER_TO_SPACE', - 'SPACE_DELETE_USER_FROM_SPACE', + 'USER_ADD', + 'SPACE_MEMBER_ADD', ], }; From b04d013d955ba39deb61dc4c8e7ec2f3ac7b0583 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 4 Feb 2025 15:03:47 +0400 Subject: [PATCH 241/247] reduce code duplication --- src/space/services/subspace/subspace.service.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/space/services/subspace/subspace.service.ts b/src/space/services/subspace/subspace.service.ts index 78f9445..acd2fd3 100644 --- a/src/space/services/subspace/subspace.service.ts +++ b/src/space/services/subspace/subspace.service.ts @@ -58,7 +58,7 @@ export class SubSpaceService { return await queryRunner.manager.save(subspaces); } catch (error) { throw new HttpException( - 'An unexpected error occurred while creating subspaces.', + `An unexpected error occurred while creating subspaces. ${error}`, HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -98,9 +98,8 @@ export class SubSpaceService { otherTags?: CreateTagDto[], ): Promise { try { - await this.validateName( - addSubspaceDtos.map((dto) => dto.subspaceName), - space, + this.checkForDuplicateNames( + addSubspaceDtos.map(({ subspaceName }) => subspaceName), ); const subspaceData = addSubspaceDtos.map((dto) => ({ From a890d75d5ad62d8b436b3488b4e1e16b502f0378 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 4 Feb 2025 15:04:58 +0400 Subject: [PATCH 242/247] SP-1046 --- src/device/services/device.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/device/services/device.service.ts b/src/device/services/device.service.ts index 2cd5639..45cdafd 100644 --- a/src/device/services/device.service.ts +++ b/src/device/services/device.service.ts @@ -1019,6 +1019,7 @@ export class DeviceService { uuid: space.uuid, spaceName: space.spaceName, })), + productUuid: device.productDevice.uuid, productType: device.productDevice.prodType, community: { From b02b4c049c51d847a7159cc6a2176419ac4b5e58 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 4 Feb 2025 07:24:47 -0600 Subject: [PATCH 243/247] Add optional chaining to user and invitedUser checks --- src/invite-user/services/invite-user.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 6b90321..0cc6568 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -181,7 +181,7 @@ export class InviteUserService { where: { email }, relations: ['project'], }); - if (!user.isActive) { + if (!user?.isActive) { throw new HttpException('This user is deleted', HttpStatus.BAD_REQUEST); } if (user?.project) { @@ -195,7 +195,7 @@ export class InviteUserService { where: { email }, relations: ['project'], }); - if (!invitedUser.isActive) { + if (!invitedUser?.isActive) { throw new HttpException('This user is deleted', HttpStatus.BAD_REQUEST); } if (invitedUser?.project) { From 5ccac95ee152815a06241e10942faa88890ad891 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Tue, 4 Feb 2025 07:38:00 -0600 Subject: [PATCH 244/247] Refactor user and invited user validation in InviteUserService --- .../services/invite-user.service.ts | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/invite-user/services/invite-user.service.ts b/src/invite-user/services/invite-user.service.ts index 6b90321..e466248 100644 --- a/src/invite-user/services/invite-user.service.ts +++ b/src/invite-user/services/invite-user.service.ts @@ -181,29 +181,12 @@ export class InviteUserService { where: { email }, relations: ['project'], }); - if (!user.isActive) { - throw new HttpException('This user is deleted', HttpStatus.BAD_REQUEST); - } - if (user?.project) { - throw new HttpException( - 'This email already has a project', - HttpStatus.BAD_REQUEST, - ); - } - + this.validateUserOrInvite(user, 'User'); const invitedUser = await this.inviteUserRepository.findOne({ where: { email }, relations: ['project'], }); - if (!invitedUser.isActive) { - throw new HttpException('This user is deleted', HttpStatus.BAD_REQUEST); - } - if (invitedUser?.project) { - throw new HttpException( - 'This email already has a project', - HttpStatus.BAD_REQUEST, - ); - } + this.validateUserOrInvite(invitedUser, 'Invited User'); return new SuccessResponseDto({ statusCode: HttpStatus.OK, @@ -219,6 +202,24 @@ export class InviteUserService { ); } } + + private validateUserOrInvite(user: any, userType: string): void { + if (user) { + if (!user.isActive) { + throw new HttpException( + `${userType} is deleted`, + HttpStatus.BAD_REQUEST, + ); + } + if (user.project) { + throw new HttpException( + `${userType} already has a project`, + HttpStatus.BAD_REQUEST, + ); + } + } + } + async activationCode(dto: ActivateCodeDto): Promise { const { activationCode, userUuid } = dto; From 18b419f1dde7ec528712c1654ced07ce4627bc57 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 4 Feb 2025 18:57:15 +0400 Subject: [PATCH 245/247] - Replaced explicit null/undefined/empty string checks with truthy/falsy conditions - Improved code readability and efficiency --- src/space-model/services/tag-model.service.ts | 4 ++-- src/space/services/tag/tag.service.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/space-model/services/tag-model.service.ts b/src/space-model/services/tag-model.service.ts index 7394ed8..b663590 100644 --- a/src/space-model/services/tag-model.service.ts +++ b/src/space-model/services/tag-model.service.ts @@ -44,8 +44,8 @@ export class TagModelService { ); } - const tagEntitiesToCreate = tags.filter((tagDto) => tagDto.uuid === null); - const tagEntitiesToUpdate = tags.filter((tagDto) => tagDto.uuid !== null); + const tagEntitiesToCreate = tags.filter((tagDto) => !tagDto.uuid); + const tagEntitiesToUpdate = tags.filter((tagDto) => !!tagDto.uuid); try { const createdTags = await this.bulkSaveTags( diff --git a/src/space/services/tag/tag.service.ts b/src/space/services/tag/tag.service.ts index f5059e6..2dee0c7 100644 --- a/src/space/services/tag/tag.service.ts +++ b/src/space/services/tag/tag.service.ts @@ -33,7 +33,7 @@ export class TagService { this.ensureNoDuplicateTags(combinedTags); const tagEntitiesToCreate = tags.filter((tagDto) => !tagDto.uuid); - const tagEntitiesToUpdate = tags.filter((tagDto) => tagDto.uuid !== null); + const tagEntitiesToUpdate = tags.filter((tagDto) => !!tagDto.uuid); try { const createdTags = await this.bulkSaveTags( From 2a4926f94e08276901f54bcc5950bc6e9550bbb5 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 4 Feb 2025 22:58:03 +0400 Subject: [PATCH 246/247] added battery and space details --- .../common/src/constants/product-type.enum.ts | 1 + src/space/services/space-device.service.ts | 125 ++++++++++++------ .../services/space-validation.service.ts | 97 ++++++++++++++ 3 files changed, 184 insertions(+), 39 deletions(-) diff --git a/libs/common/src/constants/product-type.enum.ts b/libs/common/src/constants/product-type.enum.ts index d368865..fd0cc6f 100644 --- a/libs/common/src/constants/product-type.enum.ts +++ b/libs/common/src/constants/product-type.enum.ts @@ -18,4 +18,5 @@ export enum ProductType { PC = 'PC', FOUR_S = '4S', SIX_S = '6S', + SOS = 'SOS', } diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index aaa7d12..490cca7 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -8,6 +8,8 @@ import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; import { convertKeysToCamelCase } from '@app/common/helper/camelCaseConverter'; import { ValidationService } from './space-validation.service'; +import { ProductType } from '@app/common/constants/product-type.enum'; +import { BatteryStatus } from '@app/common/constants/battery-status.enum'; @Injectable() export class SpaceDeviceService { @@ -18,54 +20,35 @@ export class SpaceDeviceService { async listDevicesInSpace(params: GetSpaceParam): Promise { const { spaceUuid, communityUuid, projectUuid } = params; - try { - const space = - await this.validationService.validateSpaceWithinCommunityAndProject( - communityUuid, - projectUuid, - spaceUuid, - ); - if (!space.devices || space.devices.length === 0) { + try { + // Validate community, project, and fetch space including devices in a single query + const space = await this.validationService.fetchSpaceDevices(spaceUuid); + + if (!space || !space.devices?.length) { throw new HttpException( 'The space does not contain any devices.', HttpStatus.BAD_REQUEST, ); } - const safeFetch = async (device: any) => { - try { - const tuyaDetails = await this.getDeviceDetailsByDeviceIdTuya( - device.deviceTuyaUuid, - ); - const tuyaDeviceStatus = - await this.tuyaService.getDevicesInstructionStatusTuya( - device.deviceTuyaUuid, - ); - return { - uuid: device.uuid, - deviceTuyaUuid: device.deviceTuyaUuid, - productUuid: device.productDevice.uuid, - productType: device.productDevice.prodType, - isActive: device.isActive, - updatedAt: device.updatedAt, - deviceTag: device.tag, - subspace: device.subspace, - ...tuyaDetails, - status: tuyaDeviceStatus.result[0].status, - }; - } catch (error) { - console.warn( - `Skipping device with deviceTuyaUuid: ${device.deviceTuyaUuid} due to error. ${error}`, - ); - return null; - } - }; - const detailedDevices = await Promise.all(space.devices.map(safeFetch)); + // Fetch space hierarchy **once** and reverse it to get an ordered hierarchy + const spaceHierarchy = + await this.validationService.getFullSpaceHierarchy(space); + const orderedHierarchy = spaceHierarchy.reverse(); + + // Fetch Tuya details for each device in parallel using Promise.allSettled + const deviceDetailsPromises = space.devices.map((device) => + this.fetchDeviceDetails(device, orderedHierarchy), + ); + + const detailedDevices = (await Promise.allSettled(deviceDetailsPromises)) + .filter((result) => result.status === 'fulfilled' && result.value) + .map((result) => (result as PromiseFulfilledResult).value); return new SuccessResponseDto({ - data: detailedDevices.filter(Boolean), - message: 'Successfully retrieved list of devices', + data: detailedDevices, + message: 'Successfully retrieved list of devices.', }); } catch (error) { console.error('Error listing devices in space:', error); @@ -76,6 +59,70 @@ export class SpaceDeviceService { } } + private async fetchDeviceDetails(device: any, orderedHierarchy: any[]) { + try { + // Fetch Tuya details in parallel + const [tuyaDetails, tuyaDeviceStatusResponse] = await Promise.all([ + this.getDeviceDetailsByDeviceIdTuya(device.deviceTuyaUuid), + this.tuyaService.getDevicesInstructionStatusTuya(device.deviceTuyaUuid), + ]); + + const tuyaStatusList = + tuyaDeviceStatusResponse?.result?.[0]?.status || []; + + return { + spaces: orderedHierarchy.map((space) => ({ + uuid: space.uuid, + spaceName: space.spaceName, + })), + uuid: device.uuid, + deviceTuyaUuid: device.deviceTuyaUuid, + productUuid: device.productDevice?.uuid || null, + productType: device.productDevice?.prodType || null, + isActive: device.isActive, + updatedAt: device.updatedAt, + deviceTag: device.tag, + subspace: device.subspace, + ...tuyaDetails, + ...(this.extractBatteryStatus( + device.productDevice?.prodType, + tuyaStatusList, + ) !== null && { + battery: this.extractBatteryStatus( + device.productDevice?.prodType, + tuyaStatusList, + ), + }), + status: tuyaStatusList, + }; + } catch (error) { + console.warn( + `Skipping device ${device.deviceTuyaUuid} due to error: ${error.message}`, + ); + return null; + } + } + + private extractBatteryStatus( + deviceType: string, + tuyaStatus: any[], + ): number | null { + const batteryCodes = { + [ProductType.DL]: BatteryStatus.RESIDUAL_ELECTRICITY, + [ProductType.DS]: BatteryStatus.BATTERY_PERCENTAGE, + [ProductType.WL]: BatteryStatus.BATTERY_PERCENTAGE, + [ProductType.SOS]: BatteryStatus.BATTERY_PERCENTAGE, + }; + + const batteryCode = batteryCodes[deviceType]; + if (!batteryCode) return null; + + const batteryStatus = tuyaStatus.find( + (status) => status.code === batteryCode, + ); + return batteryStatus ? batteryStatus.value : null; + } + private async getDeviceDetailsByDeviceIdTuya( deviceId: string, ): Promise { diff --git a/src/space/services/space-validation.service.ts b/src/space/services/space-validation.service.ts index 76750dd..9dc614b 100644 --- a/src/space/services/space-validation.service.ts +++ b/src/space/services/space-validation.service.ts @@ -104,6 +104,26 @@ export class ValidationService { return space; } + async fetchSpaceDevices(spaceUuid: string): Promise { + const space = await this.spaceRepository.findOne({ + where: { uuid: spaceUuid, disabled: false }, + relations: [ + 'devices', + 'devices.productDevice', + 'devices.tag', + 'devices.subspace', + ], + }); + + if (!space) { + throw new HttpException( + `Space with UUID ${spaceUuid} not found`, + HttpStatus.NOT_FOUND, + ); + } + return space; + } + async validateSpaceModel(spaceModelUuid: string): Promise { const spaceModel = await this.spaceModelRepository.findOne({ where: { uuid: spaceModelUuid }, @@ -125,4 +145,81 @@ export class ValidationService { return spaceModel; } + + async getFullSpaceHierarchy( + space: SpaceEntity, + ): Promise<{ uuid: string; spaceName: string }[]> { + try { + // Fetch only the relevant spaces, starting with the target space + const targetSpace = await this.spaceRepository.findOne({ + where: { uuid: space.uuid }, + relations: ['parent', 'children'], + }); + + // Fetch only the ancestors of the target space + const ancestors = await this.fetchAncestors(targetSpace); + + // Optionally, fetch descendants if required + const descendants = await this.fetchDescendants(targetSpace); + + const fullHierarchy = [...ancestors, targetSpace, ...descendants].map( + (space) => ({ + uuid: space.uuid, + spaceName: space.spaceName, + }), + ); + + return fullHierarchy; + } catch (error) { + console.error('Error fetching space hierarchy:', error.message); + throw new HttpException( + 'Error fetching space hierarchy', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + private async fetchAncestors(space: SpaceEntity): Promise { + const ancestors: SpaceEntity[] = []; + + let currentSpace = space; + while (currentSpace && currentSpace.parent) { + // Fetch the parent space + const parent = await this.spaceRepository.findOne({ + where: { uuid: currentSpace.parent.uuid }, + relations: ['parent'], // To continue fetching upwards + }); + + if (parent) { + ancestors.push(parent); + currentSpace = parent; + } else { + currentSpace = null; + } + } + + // Return the ancestors in reverse order to have the root at the start + return ancestors.reverse(); + } + + private async fetchDescendants(space: SpaceEntity): Promise { + const descendants: SpaceEntity[] = []; + + // Fetch the immediate children of the current space + const children = await this.spaceRepository.find({ + where: { parent: { uuid: space.uuid } }, + relations: ['children'], // To continue fetching downwards + }); + + for (const child of children) { + // Add the child to the descendants list + descendants.push(child); + + // Recursively fetch the child's descendants + const childDescendants = await this.fetchDescendants(child); + descendants.push(...childDescendants); + } + + return descendants; + } } From 993ecf4cf8917e171b768733a8c1076fe03264cc Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Wed, 5 Feb 2025 11:30:40 +0400 Subject: [PATCH 247/247] fixed the unlinking --- src/space/services/space.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/space/services/space.service.ts b/src/space/services/space.service.ts index b575ab6..0815525 100644 --- a/src/space/services/space.service.ts +++ b/src/space/services/space.service.ts @@ -388,15 +388,15 @@ export class SpaceService { this.updateSpaceProperties(space, updateSpaceDto); - await queryRunner.manager.save(space); - const hasSubspace = updateSpaceDto.subspace?.length > 0; const hasTags = updateSpaceDto.tags?.length > 0; if (hasSubspace || hasTags) { + space.spaceModel = null; await this.tagService.unlinkModels(space.tags, queryRunner); await this.subSpaceService.unlinkModels(space.subspaces, queryRunner); } + await queryRunner.manager.save(space); if (hasSubspace) { const modifiedSubspaces = this.tagService.getModifiedSubspaces(