disable space

This commit is contained in:
hannathkadher
2024-12-30 10:06:33 +04:00
parent 0d539883f1
commit 6a89a17ce9
17 changed files with 604 additions and 177 deletions

View File

@ -1 +1,2 @@
export * from './propogate-subspace-update-command'; export * from './propogate-subspace-update-command';
export * from './propagate-space-model-deletion.command';

View File

@ -0,0 +1,9 @@
import { SpaceModelEntity } from '@app/common/modules/space-model';
export class PropogateDeleteSpaceModelCommand {
constructor(
public readonly param: {
spaceModel: SpaceModelEntity;
},
) {}
}

View File

@ -1,6 +1,12 @@
import { ICommand } from '@nestjs/cqrs'; 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 { export class PropogateUpdateSpaceModelCommand implements ICommand {
constructor(public readonly param: IModifySubspaceModelInterface) {} constructor(
public readonly param: {
spaceModel: SpaceModelEntity;
modifiedSpaceModels: ModifyspaceModelPayload;
},
) {}
} }

View File

@ -1 +1,2 @@
export * from './propate-subspace-handler'; export * from './propate-subspace-handler';
export * from './propogate-space-model-deletion.handler';

View File

@ -1,170 +1,250 @@
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { PropogateSubspaceCommand } from '../commands'; import { PropogateUpdateSpaceModelCommand } from '../commands';
import { Logger } from '@nestjs/common';
import { SpaceEntity, SpaceRepository } from '@app/common/modules/space'; import { SpaceEntity, SpaceRepository } from '@app/common/modules/space';
import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository';
import { import {
IDeletedSubsaceModelInterface, SpaceModelEntity,
IUpdateSubspaceModelInterface, SubspaceModelEntity,
} from '../interfaces'; 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) @CommandHandler(PropogateUpdateSpaceModelCommand)
export class PropogateSubspaceHandler export class PropogateUpdateSpaceModelHandler
implements ICommandHandler<PropogateSubspaceCommand> implements ICommandHandler<PropogateUpdateSpaceModelCommand>
{ {
private readonly logger = new Logger(PropogateSubspaceHandler.name);
constructor( constructor(
private readonly spaceRepository: SpaceRepository, private readonly spaceRepository: SpaceRepository,
private readonly subspaceRepository: SubspaceRepository, 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<void> { async execute(command: PropogateUpdateSpaceModelCommand): Promise<void> {
const { spaceModel, modifiedSpaceModels } = command.param;
const queryRunner = this.dataSource.createQueryRunner();
try { try {
const newSubspaceModels = command.param?.new; await queryRunner.connect();
const updateSubspaceModels = command.param?.update; await queryRunner.startTransaction();
const deleteSubspaceModels = command.param?.delete; const spaces = await this.spaceRepository.find({
where: { spaceModel },
if (!newSubspaceModels && !updateSubspaceModels) { });
this.logger.warn('No new or updated subspace models provided.'); if (
return; modifiedSpaceModels.modifiedSubspaceModels.addedSubspaceModels.length >
} 0
) {
const spaceModelUuid = command.param.spaceModelUuid; await this.addSubspaceModels(
modifiedSpaceModels.modifiedSubspaceModels.addedSubspaceModels,
if (!spaceModelUuid) { spaces,
this.logger.error( queryRunner,
'Space model UUID is missing in the command parameters.',
); );
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 (modifiedSpaceModels.modifiedTags.added.length > 0) {
await this.createTags(
if (spaces.length === 0) { modifiedSpaceModels.modifiedTags.added,
this.logger.warn(`No spaces found for model UUID: ${spaceModelUuid}`); queryRunner,
return; null,
spaceModel,
);
} }
if (newSubspaceModels && newSubspaceModels.length > 0) { if (modifiedSpaceModels.modifiedTags.updated.length > 0) {
await this.processNewSubspaces(newSubspaceModels, spaces); await this.updateTags(
modifiedSpaceModels.modifiedTags.updated,
queryRunner,
);
} }
if (updateSubspaceModels && updateSubspaceModels.length > 0) { if (modifiedSpaceModels.modifiedTags.deleted.length > 0) {
await this.updateSubspaces(updateSubspaceModels); await this.deleteTags(
modifiedSpaceModels.modifiedTags.deleted,
queryRunner,
);
} }
if (deleteSubspaceModels && deleteSubspaceModels.length > 0) { await queryRunner.commitTransaction();
await this.deleteSubspaces(deleteSubspaceModels);
}
} catch (error) { } catch (error) {
this.logger.error( await queryRunner.rollbackTransaction();
'Error in PropogateSubspaceHandler execution', throw error;
error.stack, } 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( async updateSubspaceModels(
models: IUpdateSubspaceModelInterface[], subspaceModels: UpdatedSubspaceModelPayload[],
queryRunner: QueryRunner,
): Promise<void> { ): Promise<void> {
try { const subspaceUpdatePromises = subspaceModels.map(async (model) => {
const updatePromises = []; const {
updated: tagsToUpdate,
deleted: tagsToDelete,
added: tagsToAdd,
} = model.modifiedTags;
for (const model of models) { // Perform tag operations concurrently
const { uuid: subspaceModelUuid, subspaceName } = model; 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) { // Update subspace names
updatePromises.push( const subspaces = await queryRunner.manager.find(
this.subspaceRepository this.subspaceRepository.target,
.createQueryBuilder() {
.update() where: {
.set({ subspaceName }) subSpaceModel: {
.where('subSpaceModelUuid = :uuid', { uuid: subspaceModelUuid }) uuid: model.subspaceModelUuid,
.execute(), },
},
},
); );
}
}
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);
}
});
// 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),
);
await Promise.all(updatePromises); await Promise.all(updatePromises);
} catch (error) {
this.logger.error('Error in updateSubspaces method', error.stack);
}
} }
private async deleteSubspaces(models: IDeletedSubsaceModelInterface[]) { async deleteTags(uuids: string[], queryRunner: QueryRunner) {
try { const deletePromises = uuids.map((uuid) =>
const updatePromises = []; this.tagService.deleteTagFromModel(uuid, queryRunner),
);
await Promise.all(deletePromises);
}
for (const model of models) { async createTags(
const { uuid: subspaceModelUuid } = model; models: TagModel[],
queryRunner: QueryRunner,
subspaceModelUuid?: string,
spaceModel?: SpaceModelEntity,
): Promise<void> {
if (!models.length) {
return;
}
if (subspaceModelUuid) { if (subspaceModelUuid) {
updatePromises.push( await this.processSubspaces(subspaceModelUuid, models, queryRunner);
this.subspaceRepository }
.createQueryBuilder()
.update() if (spaceModel) {
.set({ disabled: true }) await this.processSpaces(spaceModel.uuid, models, queryRunner);
.where('subSpaceModelUuid = :uuid', { uuid: subspaceModelUuid })
.execute(),
);
} }
} }
await Promise.all(updatePromises); private async processSubspaces(
} catch (error) { subspaceModelUuid: string,
this.logger.error('Error in delete subspace models', error.stack); models: TagModel[],
} queryRunner: QueryRunner,
} ): Promise<void> {
const subspaces = await this.subspaceRepository.find({
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}`,
);
return createdSubspace;
}
private async getSpacesByModel(uuid: string): Promise<SpaceEntity[]> {
try {
return await this.spaceRepository.find({
where: { where: {
spaceModel: { uuid }, subSpaceModel: {
uuid: subspaceModelUuid,
},
}, },
}); });
} catch (error) {
this.logger.error( if (subspaces.length > 0) {
`Failed to fetch spaces for model UUID: ${uuid}`, const subspacePromises = subspaces.map((subspace) =>
error.stack, this.tagService.createTagsFromModel(
queryRunner,
models,
null,
subspace,
),
); );
throw error; await Promise.all(subspacePromises);
}
}
private async processSpaces(
spaceModelUuid: string,
models: TagModel[],
queryRunner: QueryRunner,
): Promise<void> {
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);
} }
} }
} }

View File

@ -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<PropogateDeleteSpaceModelCommand>
{
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<void> {
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();
}
}
}

View File

@ -1 +1,2 @@
export * from './update-subspace.interface' export * from './update-subspace.interface';
export * from './modify-subspace.interface';

View File

@ -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[];
}

View File

@ -19,6 +19,12 @@ import { ProjectService } from 'src/project/services';
import { ProjectEntity } from '@app/common/modules/project/entities'; import { ProjectEntity } from '@app/common/modules/project/entities';
import { TagModelService } from './tag-model.service'; import { TagModelService } from './tag-model.service';
import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { BaseResponseDto } from '@app/common/dto/base.response.dto';
import { CommandBus } from '@nestjs/cqrs';
import { PropogateUpdateSpaceModelCommand } from '../commands';
import {
ModifiedTagsModelPayload,
ModifySubspaceModelPayload,
} from '../interfaces';
@Injectable() @Injectable()
export class SpaceModelService { export class SpaceModelService {
@ -28,6 +34,7 @@ export class SpaceModelService {
private readonly projectService: ProjectService, private readonly projectService: ProjectService,
private readonly subSpaceModelService: SubSpaceModelService, private readonly subSpaceModelService: SubSpaceModelService,
private readonly tagModelService: TagModelService, private readonly tagModelService: TagModelService,
private commandBus: CommandBus,
) {} ) {}
async createSpaceModel( async createSpaceModel(
@ -136,6 +143,9 @@ export class SpaceModelService {
const queryRunner = this.dataSource.createQueryRunner(); const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect(); await queryRunner.connect();
await queryRunner.startTransaction(); await queryRunner.startTransaction();
let modifiedSubspaceModels: ModifySubspaceModelPayload = {};
let modifiedTagsModelPayload: ModifiedTagsModelPayload = {};
try { try {
const { modelName } = dto; const { modelName } = dto;
if (modelName) { if (modelName) {
@ -145,6 +155,7 @@ export class SpaceModelService {
} }
if (dto.subspaceModels) { if (dto.subspaceModels) {
modifiedSubspaceModels =
await this.subSpaceModelService.modifySubSpaceModels( await this.subSpaceModelService.modifySubSpaceModels(
dto.subspaceModels, dto.subspaceModels,
spaceModel, spaceModel,
@ -153,14 +164,25 @@ export class SpaceModelService {
} }
if (dto.tags) { if (dto.tags) {
await this.tagModelService.modifyTags( modifiedTagsModelPayload = await this.tagModelService.modifyTags(
dto.tags, dto.tags,
queryRunner, queryRunner,
spaceModel, spaceModel,
); );
} }
await queryRunner.commitTransaction(); await queryRunner.commitTransaction();
await this.commandBus.execute(
new PropogateUpdateSpaceModelCommand({
spaceModel: spaceModel,
modifiedSpaceModels: {
modifiedSubspaceModels,
modifiedTags: modifiedTagsModelPayload,
},
}),
);
return new SuccessResponseDto({ return new SuccessResponseDto({
message: 'SpaceModel updated successfully', message: 'SpaceModel updated successfully',
data: spaceModel, data: spaceModel,

View File

@ -6,7 +6,11 @@ import {
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateSubspaceModelDto, CreateTagModelDto } from '../../dtos'; import { CreateSubspaceModelDto, CreateTagModelDto } from '../../dtos';
import { In, QueryRunner } from 'typeorm'; import { In, QueryRunner } from 'typeorm';
import { IDeletedSubsaceModelInterface } from 'src/space-model/interfaces'; import {
IDeletedSubsaceModelInterface,
ModifySubspaceModelPayload,
UpdatedSubspaceModelPayload,
} from 'src/space-model/interfaces';
import { import {
DeleteSubspaceModelDto, DeleteSubspaceModelDto,
ModifySubspaceModelDto, ModifySubspaceModelDto,
@ -99,17 +103,30 @@ export class SubSpaceModelService {
subspaceDtos: ModifySubspaceModelDto[], subspaceDtos: ModifySubspaceModelDto[],
spaceModel: SpaceModelEntity, spaceModel: SpaceModelEntity,
queryRunner: QueryRunner, queryRunner: QueryRunner,
): Promise<void> { ): Promise<ModifySubspaceModelPayload> {
const modifiedSubspaceModels: ModifySubspaceModelPayload = {};
for (const subspace of subspaceDtos) { for (const subspace of subspaceDtos) {
switch (subspace.action) { switch (subspace.action) {
case ModifyAction.ADD: case ModifyAction.ADD:
await this.handleAddAction(subspace, spaceModel, queryRunner); const subspaceModel = await this.handleAddAction(
subspace,
spaceModel,
queryRunner,
);
modifiedSubspaceModels.addedSubspaceModels.push(subspaceModel);
break; break;
case ModifyAction.UPDATE: case ModifyAction.UPDATE:
await this.handleUpdateAction(subspace, queryRunner); const updatedSubspaceModel = await this.handleUpdateAction(
subspace,
queryRunner,
);
modifiedSubspaceModels.updatedSubspaceModels.push(
updatedSubspaceModel,
);
break; break;
case ModifyAction.DELETE: case ModifyAction.DELETE:
await this.handleDeleteAction(subspace, queryRunner); await this.handleDeleteAction(subspace, queryRunner);
modifiedSubspaceModels.deletedSubspaceModels.push(subspace.uuid);
break; break;
default: default:
throw new HttpException( throw new HttpException(
@ -118,29 +135,42 @@ export class SubSpaceModelService {
); );
} }
} }
return modifiedSubspaceModels;
} }
private async handleAddAction( private async handleAddAction(
subspace: ModifySubspaceModelDto, subspace: ModifySubspaceModelDto,
spaceModel: SpaceModelEntity, spaceModel: SpaceModelEntity,
queryRunner: QueryRunner, queryRunner: QueryRunner,
): Promise<void> { ): Promise<SubspaceModelEntity> {
const createTagDtos: CreateTagModelDto[] = const createTagDtos: CreateTagModelDto[] =
subspace.tags?.map((tag) => ({ subspace.tags?.map((tag) => ({
tag: tag.tag as string, tag: tag.tag,
productUuid: tag.productUuid as string, productUuid: tag.productUuid,
})) || []; })) || [];
await this.createSubSpaceModels(
[{ subspaceName: subspace.subspaceName, tags: createTagDtos }], const [createdSubspaceModel] = await this.createSubSpaceModels(
[
{
subspaceName: subspace.subspaceName,
tags: createTagDtos,
},
],
spaceModel, spaceModel,
queryRunner, queryRunner,
); );
return createdSubspaceModel;
} }
private async handleUpdateAction( private async handleUpdateAction(
modifyDto: ModifySubspaceModelDto, modifyDto: ModifySubspaceModelDto,
queryRunner: QueryRunner, queryRunner: QueryRunner,
): Promise<void> { ): Promise<UpdatedSubspaceModelPayload> {
const updatePayload: UpdatedSubspaceModelPayload = {
subspaceModelUuid: modifyDto.uuid,
};
const subspace = await this.findOne(modifyDto.uuid); const subspace = await this.findOne(modifyDto.uuid);
await this.updateSubspaceName( await this.updateSubspaceName(
@ -148,21 +178,24 @@ export class SubSpaceModelService {
subspace, subspace,
modifyDto.subspaceName, modifyDto.subspaceName,
); );
updatePayload.subspaceName = modifyDto.subspaceName;
if (modifyDto.tags?.length) { if (modifyDto.tags?.length) {
await this.tagModelService.modifyTags( updatePayload.modifiedTags = await this.tagModelService.modifyTags(
modifyDto.tags, modifyDto.tags,
queryRunner, queryRunner,
null, null,
subspace, subspace,
); );
} }
return updatePayload;
} }
private async handleDeleteAction( private async handleDeleteAction(
subspace: ModifySubspaceModelDto, subspace: ModifySubspaceModelDto,
queryRunner: QueryRunner, queryRunner: QueryRunner,
): Promise<void> { ) {
const subspaceModel = await this.findOne(subspace.uuid); const subspaceModel = await this.findOne(subspace.uuid);
await queryRunner.manager.update( await queryRunner.manager.update(

View File

@ -9,6 +9,7 @@ import { TagModelRepository } from '@app/common/modules/space-model';
import { CreateTagModelDto, ModifyTagModelDto } from '../dtos'; import { CreateTagModelDto, ModifyTagModelDto } from '../dtos';
import { ProductService } from 'src/product/services'; import { ProductService } from 'src/product/services';
import { ModifyAction } from '@app/common/constants/modify-action.enum'; import { ModifyAction } from '@app/common/constants/modify-action.enum';
import { ModifiedTagsModelPayload } from '../interfaces';
@Injectable() @Injectable()
export class TagModelService { export class TagModelService {
@ -132,9 +133,9 @@ export class TagModelService {
queryRunner: QueryRunner, queryRunner: QueryRunner,
spaceModel?: SpaceModelEntity, spaceModel?: SpaceModelEntity,
subspaceModel?: SubspaceModelEntity, subspaceModel?: SubspaceModelEntity,
): Promise<void> { ): Promise<ModifiedTagsModelPayload> {
const modifiedTagModels: ModifiedTagsModelPayload = {};
try { try {
console.log(tags);
for (const tag of tags) { for (const tag of tags) {
if (tag.action === ModifyAction.ADD) { if (tag.action === ModifyAction.ADD) {
const createTagDto: CreateTagModelDto = { const createTagDto: CreateTagModelDto = {
@ -142,20 +143,28 @@ export class TagModelService {
productUuid: tag.productUuid as string, productUuid: tag.productUuid as string,
}; };
await this.createTags( const newModel = await this.createTags(
[createTagDto], [createTagDto],
queryRunner, queryRunner,
spaceModel, spaceModel,
subspaceModel, subspaceModel,
); );
modifiedTagModels.added.push(...newModel);
} else if (tag.action === ModifyAction.UPDATE) { } 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) { } else if (tag.action === ModifyAction.DELETE) {
await queryRunner.manager.update( await queryRunner.manager.update(
this.tagModelRepository.target, this.tagModelRepository.target,
{ uuid: tag.uuid }, { uuid: tag.uuid },
{ disabled: true }, { disabled: true },
); );
modifiedTagModels.deleted.push(tag.uuid);
} else { } else {
throw new HttpException( throw new HttpException(
`Invalid action "${tag.action}" provided.`, `Invalid action "${tag.action}" provided.`,
@ -163,6 +172,7 @@ export class TagModelService {
); );
} }
} }
return modifiedTagModels;
} catch (error) { } catch (error) {
if (error instanceof HttpException) { if (error instanceof HttpException) {
throw error; throw error;
@ -235,7 +245,7 @@ export class TagModelService {
}); });
} }
private async getTagByUuid(uuid: string): Promise<TagModel> { async getTagByUuid(uuid: string): Promise<TagModel> {
const tag = await this.tagModelRepository.findOne({ const tag = await this.tagModelRepository.findOne({
where: { uuid }, where: { uuid },
relations: ['product'], relations: ['product'],
@ -248,4 +258,38 @@ export class TagModelService {
} }
return tag; return tag;
} }
async getTagByName(
tag: string,
subspaceUuid?: string,
spaceUuid?: string,
): Promise<TagModel> {
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;
}
} }

View File

@ -14,12 +14,34 @@ import {
} from '@app/common/modules/space-model'; } from '@app/common/modules/space-model';
import { ProjectRepository } from '@app/common/modules/project/repositiories'; import { ProjectRepository } from '@app/common/modules/project/repositiories';
import { ProductRepository } from '@app/common/modules/product/repositories'; import { ProductRepository } from '@app/common/modules/product/repositories';
import { PropogateSubspaceHandler } from './handlers'; import {
PropogateDeleteSpaceModelHandler,
PropogateUpdateSpaceModelHandler,
} from './handlers';
import { CqrsModule } from '@nestjs/cqrs'; 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 { 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({ @Module({
imports: [ConfigModule, SpaceRepositoryModule, CqrsModule], imports: [ConfigModule, SpaceRepositoryModule, CqrsModule],
@ -27,6 +49,7 @@ const CommandHandlers = [PropogateSubspaceHandler];
providers: [ providers: [
...CommandHandlers, ...CommandHandlers,
SpaceModelService, SpaceModelService,
SpaceService,
SpaceModelRepository, SpaceModelRepository,
SpaceRepository, SpaceRepository,
ProjectRepository, ProjectRepository,
@ -36,6 +59,17 @@ const CommandHandlers = [PropogateSubspaceHandler];
SubspaceRepository, SubspaceRepository,
TagModelService, TagModelService,
TagModelRepository, TagModelRepository,
SubSpaceService,
ValidationService,
TagService,
SubspaceDeviceService,
CommunityService,
TagRepository,
DeviceRepository,
TuyaService,
CommunityRepository,
SpaceLinkService,
SpaceLinkRepository,
], ],
exports: [CqrsModule, SpaceModelService], exports: [CqrsModule, SpaceModelService],
}) })

View File

@ -0,0 +1,5 @@
import { SubspaceEntity } from '@app/common/modules/space';
export interface ModifySubspacePayload {
addedSubspaces?: SubspaceEntity[];
}

View File

View File

@ -317,8 +317,8 @@ export class SpaceService {
if (hasSubspace) { if (hasSubspace) {
await this.subSpaceService.modifySubSpace( await this.subSpaceService.modifySubSpace(
updateSpaceDto.subspace, updateSpaceDto.subspace,
space,
queryRunner, queryRunner,
space,
); );
} }
@ -353,6 +353,37 @@ export class SpaceService {
} }
} }
async unlinkSpaceFromModel(
space: SpaceEntity,
queryRunner: QueryRunner,
): Promise<void> {
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( private updateSpaceProperties(
space: SpaceEntity, space: SpaceEntity,
updateSpaceDto: UpdateSpaceDto, updateSpaceDto: UpdateSpaceDto,

View File

@ -26,6 +26,7 @@ import { SubspaceRepository } from '@app/common/modules/space/repositories/subsp
import { TagService } from '../tag'; import { TagService } from '../tag';
import { ModifyAction } from '@app/common/constants/modify-action.enum'; import { ModifyAction } from '@app/common/constants/modify-action.enum';
import { SubspaceDeviceService } from './subspace-device.service'; import { SubspaceDeviceService } from './subspace-device.service';
import { ModifyTagDto } from 'src/space/dtos/tag/modify-tag.dto';
@Injectable() @Injectable()
export class SubSpaceService { export class SubSpaceService {
@ -317,9 +318,9 @@ export class SubSpaceService {
async modifySubSpace( async modifySubSpace(
subspaceDtos: ModifySubspaceDto[], subspaceDtos: ModifySubspaceDto[],
space: SpaceEntity,
queryRunner: QueryRunner, queryRunner: QueryRunner,
): Promise<void> { space?: SpaceEntity,
) {
for (const subspace of subspaceDtos) { for (const subspace of subspaceDtos) {
switch (subspace.action) { switch (subspace.action) {
case ModifyAction.ADD: case ModifyAction.ADD:
@ -382,17 +383,18 @@ export class SubSpaceService {
subspace: ModifySubspaceDto, subspace: ModifySubspaceDto,
space: SpaceEntity, space: SpaceEntity,
queryRunner: QueryRunner, queryRunner: QueryRunner,
): Promise<void> { ): Promise<SubspaceEntity> {
const createTagDtos: CreateTagDto[] = const createTagDtos: CreateTagDto[] =
subspace.tags?.map((tag) => ({ subspace.tags?.map((tag) => ({
tag: tag.tag as string, tag: tag.tag as string,
productUuid: tag.productUuid as string, productUuid: tag.productUuid as string,
})) || []; })) || [];
await this.createSubspacesFromDto( const subSpace = await this.createSubspacesFromDto(
[{ subspaceName: subspace.subspaceName, tags: createTagDtos }], [{ subspaceName: subspace.subspaceName, tags: createTagDtos }],
space, space,
queryRunner, queryRunner,
); );
return subSpace[0];
} }
private async handleUpdateAction( private async handleUpdateAction(
@ -400,16 +402,25 @@ export class SubSpaceService {
queryRunner: QueryRunner, queryRunner: QueryRunner,
): Promise<void> { ): Promise<void> {
const subspace = await this.findOne(modifyDto.uuid); const subspace = await this.findOne(modifyDto.uuid);
await this.update(
await this.updateSubspaceName(
queryRunner, queryRunner,
subspace, subspace,
modifyDto.subspaceName, modifyDto.subspaceName,
);
if (modifyDto.tags?.length) {
await this.tagService.modifyTags(
modifyDto.tags, modifyDto.tags,
);
}
async update(
queryRunner: QueryRunner,
subspace: SubspaceEntity,
subspaceName?: string,
modifyTagDto?: ModifyTagDto[],
) {
await this.updateSubspaceName(queryRunner, subspace, subspaceName);
if (modifyTagDto?.length) {
await this.tagService.modifyTags(
modifyTagDto,
queryRunner, queryRunner,
null, null,
subspace, subspace,
@ -417,11 +428,11 @@ export class SubSpaceService {
} }
} }
private async handleDeleteAction( async handleDeleteAction(
subspace: ModifySubspaceDto, modifyDto: ModifySubspaceDto,
queryRunner: QueryRunner, queryRunner: QueryRunner,
): Promise<void> { ): Promise<void> {
const subspaceModel = await this.findOne(subspace.uuid); const subspace = await this.findOne(modifyDto.uuid);
await queryRunner.manager.update( await queryRunner.manager.update(
this.subspaceRepository.target, this.subspaceRepository.target,
@ -429,8 +440,8 @@ export class SubSpaceService {
{ disabled: true }, { disabled: true },
); );
if (subspaceModel.tags?.length) { if (subspace.tags?.length) {
const modifyTagDtos = subspaceModel.tags.map((tag) => ({ const modifyTagDtos = subspace.tags.map((tag) => ({
uuid: tag.uuid, uuid: tag.uuid,
action: ModifyAction.DELETE, action: ModifyAction.DELETE,
})); }));
@ -438,7 +449,14 @@ export class SubSpaceService {
modifyTagDtos, modifyTagDtos,
queryRunner, queryRunner,
null, 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<SubspaceEntity> { private async findOne(subspaceUuid: string): Promise<SubspaceEntity> {
const subspace = await this.subspaceRepository.findOne({ const subspace = await this.subspaceRepository.findOne({
where: { uuid: subspaceUuid }, where: { uuid: subspaceUuid },
relations: ['tags', 'space'], relations: ['tags', 'space', 'devices', 'tags.product', 'tags.device'],
}); });
if (!subspace) { if (!subspace) {
throw new HttpException( throw new HttpException(
@ -457,7 +475,7 @@ export class SubSpaceService {
return subspace; return subspace;
} }
private async updateSubspaceName( async updateSubspaceName(
queryRunner: QueryRunner, queryRunner: QueryRunner,
subSpace: SubspaceEntity, subSpace: SubspaceEntity,
subspaceName?: string, subspaceName?: string,

View File

@ -63,6 +63,7 @@ export class TagService {
await queryRunner.manager.save(tags); await queryRunner.manager.save(tags);
} }
async updateTag( async updateTag(
tag: ModifyTagDto, tag: ModifyTagDto,
queryRunner: QueryRunner, queryRunner: QueryRunner,
@ -90,6 +91,38 @@ export class TagService {
} }
} }
async updateTagsFromModel(
model: TagModel,
queryRunner: QueryRunner,
): Promise<void> {
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) { async deleteTags(tagUuids: string[], queryRunner: QueryRunner) {
if (!tagUuids?.length) return; 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( async modifyTags(
tags: ModifyTagDto[], tags: ModifyTagDto[],
queryRunner: QueryRunner, queryRunner: QueryRunner,