propagate subspace allocations

This commit is contained in:
hannathkadher
2025-03-12 21:57:08 +04:00
parent 8456eea5dc
commit 537f20f8de
6 changed files with 314 additions and 315 deletions

View File

@ -1,13 +1,13 @@
import { ICommand } from '@nestjs/cqrs';
import { SpaceModelEntity } from '@app/common/modules/space-model';
import { ISingleSubspaceModel } from '../interfaces';
import { ISubspaceModelUpdates } from '../interfaces';
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
export class PropogateUpdateSpaceModelCommand implements ICommand {
constructor(
public readonly param: {
spaceModel: SpaceModelEntity;
subspaceModels: ISingleSubspaceModel[];
subspaceUpdates: ISubspaceModelUpdates;
spaces: SpaceEntity[];
},
) {}

View File

@ -1,303 +0,0 @@
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { PropogateUpdateSpaceModelCommand } from '../commands';
import {
SpaceProductAllocationRepository,
SpaceRepository,
} from '@app/common/modules/space';
import {
SubspaceProductAllocationRepository,
SubspaceRepository,
} from '@app/common/modules/space/repositories/subspace.repository';
import {
SpaceModelEntity,
SubspaceModelProductAllocationRepoitory,
TagModel,
} from '@app/common/modules/space-model';
import { In, QueryRunner } from 'typeorm';
import { TagService } from 'src/space/services/tag';
import {
ISingleSubspaceModel,
UpdatedSubspaceModelPayload,
} from '../interfaces';
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
import { ModifyAction } from '@app/common/constants/modify-action.enum';
@CommandHandler(PropogateUpdateSpaceModelCommand)
export class PropogateUpdateSpaceModelHandler
implements ICommandHandler<PropogateUpdateSpaceModelCommand>
{
constructor(
private readonly spaceRepository: SpaceRepository,
private readonly subspaceRepository: SubspaceRepository,
private readonly tagService: TagService,
private readonly subspaceModelProductRepository: SubspaceModelProductAllocationRepoitory,
private readonly subspaceProductRepository: SubspaceProductAllocationRepository,
private readonly spaceProductRepository: SpaceProductAllocationRepository,
) {}
async execute(command: PropogateUpdateSpaceModelCommand): Promise<void> {
const { subspaceModels, spaces } = command.param;
try {
if (!subspaceModels || subspaceModels.length === 0) return;
if (!spaces || spaces.length === 0) return;
for (const subspaceModel of subspaceModels) {
if (subspaceModel.action === ModifyAction.ADD) {
await this.addSubspaceModel(subspaceModel, spaces);
} else if (subspaceModel.action === ModifyAction.DELETE) {
await this.deleteSubspaceModel(subspaceModel, spaces);
} else if (subspaceModel.action === ModifyAction.UPDATE) {
await this.updateSubspaceModel(subspaceModel);
}
}
} catch (error) {
console.error(`Error processing subspaceModel updates`, error);
}
}
async addSubspaceModel(
subspaceModel: ISingleSubspaceModel,
spaces: SpaceEntity[],
) {
const subspaceModelAllocations =
await this.subspaceModelProductRepository.find({
where: {
subspaceModel: {
uuid: subspaceModel.subspaceModel.uuid,
},
},
relations: ['tags', 'product'],
});
for (const space of spaces) {
const subspace = this.subspaceRepository.create({
subspaceName: subspaceModel.subspaceModel.subspaceName,
space: space,
});
subspace.subSpaceModel = subspaceModel.subspaceModel;
await this.subspaceRepository.save(subspace);
if (subspaceModelAllocations?.length > 0) {
for (const allocation of subspaceModelAllocations) {
const subspaceAllocation = this.subspaceProductRepository.create({
subspace: subspace,
product: allocation.product,
tags: allocation.tags,
inheritedFromModel: allocation,
});
await this.subspaceProductRepository.save(subspaceAllocation);
}
}
}
}
async deleteSubspaceModel(
subspaceModel: ISingleSubspaceModel,
spaces: SpaceEntity[],
) {
const subspaces = await this.subspaceRepository.find({
where: {
subSpaceModel: { uuid: subspaceModel.subspaceModel.uuid },
disabled: false,
},
relations: [
'productAllocations',
'productAllocations.product',
'productAllocations.tags',
],
});
if (!subspaces.length) {
return;
}
const allocationUuidsToRemove = subspaces.flatMap((subspace) =>
subspace.productAllocations.map((allocation) => allocation.uuid),
);
if (allocationUuidsToRemove.length) {
await this.subspaceProductRepository.delete(allocationUuidsToRemove);
}
await this.subspaceRepository.update(
{ uuid: In(subspaces.map((s) => s.uuid)) },
{ disabled: true },
);
const relocatedAllocations = subspaceModel.relocatedAllocations || [];
if (!relocatedAllocations.length) {
return;
}
for (const space of spaces) {
for (const { allocation, tags = [] } of relocatedAllocations) {
const spaceAllocation = await this.spaceProductRepository.findOne({
where: {
inheritedFromModel: { uuid: allocation.uuid },
space: { uuid: space.uuid },
},
relations: ['tags'],
});
if (spaceAllocation) {
if (tags.length) {
spaceAllocation.tags.push(...tags);
await this.spaceProductRepository.save(spaceAllocation);
}
} else {
const newSpaceAllocation = this.spaceProductRepository.create({
space,
inheritedFromModel: allocation,
tags: allocation.tags,
product: allocation.product,
});
await this.spaceProductRepository.save(newSpaceAllocation);
}
}
}
}
async updateSubspaceModel(subspaceModel: ISingleSubspaceModel) {
return this.subspaceRepository.update(
{
subSpaceModel: { uuid: subspaceModel.subspaceModel.uuid },
disabled: false,
},
{ subspaceName: subspaceModel.subspaceModel.subspaceName },
);
}
async updateSubspaceModels(
subspaceModels: UpdatedSubspaceModelPayload[],
queryRunner: QueryRunner,
): Promise<void> {
const subspaceUpdatePromises = subspaceModels.map(async (model) => {
const {
updated: tagsToUpdate,
deleted: tagsToDelete,
added: tagsToAdd,
} = model.modifiedTags;
// 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,
),
]);
// 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);
}
});
// 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);
}
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<void> {
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<void> {
const subspaces = await this.subspaceRepository.find({
where: {
subSpaceModel: {
uuid: subspaceModelUuid,
},
},
});
if (subspaces.length > 0) {
const subspacePromises = subspaces.map((subspace) =>
this.tagService.createTagsFromModel(
queryRunner,
models,
null,
subspace,
),
);
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,292 @@
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { PropogateUpdateSpaceModelCommand } from '../commands';
import {
SpaceProductAllocationRepository,
SpaceRepository,
} from '@app/common/modules/space';
import {
SubspaceProductAllocationRepository,
SubspaceRepository,
} from '@app/common/modules/space/repositories/subspace.repository';
import { SubspaceModelProductAllocationRepoitory } from '@app/common/modules/space-model';
import { In } from 'typeorm';
import { TagService } from 'src/space/services/tag';
import { ISingleSubspaceModel } from '../interfaces';
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
import { ModifyAction } from '@app/common/constants/modify-action.enum';
import { IUpdatedAllocations } from '../interfaces/subspace-product-allocation-update-result.interface';
@CommandHandler(PropogateUpdateSpaceModelCommand)
export class PropogateUpdateSpaceModelHandler
implements ICommandHandler<PropogateUpdateSpaceModelCommand>
{
constructor(
private readonly spaceRepository: SpaceRepository,
private readonly subspaceRepository: SubspaceRepository,
private readonly tagService: TagService,
private readonly subspaceModelProductRepository: SubspaceModelProductAllocationRepoitory,
private readonly subspaceProductRepository: SubspaceProductAllocationRepository,
private readonly spaceProductRepository: SpaceProductAllocationRepository,
) {}
async execute(command: PropogateUpdateSpaceModelCommand): Promise<void> {
const { subspaceUpdates, spaces } = command.param;
try {
if (!subspaceUpdates?.subspaceModels?.length) return; // Exit if no updates
if (!spaces?.length) return; // Exit if no spaces
const subspaceModels = subspaceUpdates.subspaceModels;
// 🔹 Filter subspace models by action
const subspacesToAdd = subspaceModels.filter(
(m) => m.action === ModifyAction.ADD,
);
const subspacesToUpdate = subspaceModels.filter(
(m) => m.action === ModifyAction.UPDATE,
);
const subspacesToDelete = subspaceModels.filter(
(m) => m.action === ModifyAction.DELETE,
);
// 1⃣ Create Subspace Models First (if any)
await Promise.all(
subspacesToAdd.map((m) => this.addSubspaceModel(m, spaces)),
);
// 2⃣ Update Allocations
await this.updateAllocations(subspaceUpdates.updatedAllocations);
// 3⃣ Update Existing Subspace Models (if any)
await Promise.all(
subspacesToUpdate.map((m) => this.updateSubspaceModel(m)),
);
// 4⃣ Delete Subspace Models (if any)
await Promise.all(
subspacesToDelete.map((m) => this.deleteSubspaceModel(m, spaces)),
);
console.log('Successfully executed PropogateUpdateSpaceModelCommand');
} catch (error) {
console.error('Error processing subspace model updates', error);
}
}
async updateAllocations(allocations: IUpdatedAllocations[]) {
try {
for (const allocation of allocations) {
if (!allocation) continue;
if (allocation.allocation) {
try {
const subspaceAllocations =
await this.subspaceProductRepository.find({
where: {
inheritedFromModel: { uuid: allocation.allocation.uuid },
},
relations: ['tags'],
});
if (!subspaceAllocations || subspaceAllocations.length === 0)
continue;
if (allocation.tagsAdded?.length) {
for (const subspaceAllocation of subspaceAllocations) {
subspaceAllocation.tags.push(...allocation.tagsAdded);
}
await this.subspaceProductRepository.save(subspaceAllocations);
console.log(
`Added tags to ${subspaceAllocations.length} subspace allocations.`,
);
}
if (allocation.tagsRemoved?.length) {
const tagsToRemoveUUIDs = allocation.tagsRemoved.map(
(tag) => tag.uuid,
);
for (const subspaceAllocation of subspaceAllocations) {
subspaceAllocation.tags = subspaceAllocation.tags.filter(
(tag) => !tagsToRemoveUUIDs.includes(tag.uuid),
);
}
await this.subspaceProductRepository.save(subspaceAllocations);
console.log(
`Removed tags from ${subspaceAllocations.length} subspace allocations.`,
);
}
} catch (error) {
console.error('Error processing allocation update:', error);
}
}
if (allocation.newAllocation) {
try {
const subspaceModel = allocation.newAllocation.subspaceModel;
const subspaces = await this.subspaceRepository.find({
where: { subSpaceModel: { uuid: subspaceModel.uuid } },
});
if (!subspaces || subspaces.length === 0) continue;
const newAllocations = subspaces.map((subspace) =>
this.subspaceProductRepository.create({
product: allocation.newAllocation.product,
tags: allocation.newAllocation.tags,
subspace,
inheritedFromModel: allocation.newAllocation,
}),
);
await this.subspaceProductRepository.save(newAllocations);
console.log(
`Created ${newAllocations.length} new subspace allocations.`,
);
} catch (error) {
console.error('Error creating new subspace allocation:', error);
}
}
if (allocation.deletedAllocation) {
try {
const subspaceAllocations =
await this.subspaceProductRepository.find({
where: {
inheritedFromModel: {
uuid: allocation.deletedAllocation.uuid,
},
},
});
if (!subspaceAllocations || subspaceAllocations.length === 0)
continue;
await this.subspaceProductRepository.remove(subspaceAllocations);
console.log(
`Deleted ${subspaceAllocations.length} subspace allocations.`,
);
} catch (error) {
console.error('Error deleting subspace allocation:', error);
}
}
}
} catch (error) {
console.error('Error in updateAllocations method:', error);
}
}
async addSubspaceModel(
subspaceModel: ISingleSubspaceModel,
spaces: SpaceEntity[],
) {
const subspaceModelAllocations =
await this.subspaceModelProductRepository.find({
where: {
subspaceModel: {
uuid: subspaceModel.subspaceModel.uuid,
},
},
relations: ['tags', 'product'],
});
for (const space of spaces) {
const subspace = this.subspaceRepository.create({
subspaceName: subspaceModel.subspaceModel.subspaceName,
space: space,
});
subspace.subSpaceModel = subspaceModel.subspaceModel;
await this.subspaceRepository.save(subspace);
if (subspaceModelAllocations?.length > 0) {
for (const allocation of subspaceModelAllocations) {
const subspaceAllocation = this.subspaceProductRepository.create({
subspace: subspace,
product: allocation.product,
tags: allocation.tags,
inheritedFromModel: allocation,
});
await this.subspaceProductRepository.save(subspaceAllocation);
}
}
}
}
async deleteSubspaceModel(
subspaceModel: ISingleSubspaceModel,
spaces: SpaceEntity[],
) {
const subspaces = await this.subspaceRepository.find({
where: {
subSpaceModel: { uuid: subspaceModel.subspaceModel.uuid },
disabled: false,
},
relations: [
'productAllocations',
'productAllocations.product',
'productAllocations.tags',
],
});
if (!subspaces.length) {
return;
}
const allocationUuidsToRemove = subspaces.flatMap((subspace) =>
subspace.productAllocations.map((allocation) => allocation.uuid),
);
if (allocationUuidsToRemove.length) {
await this.subspaceProductRepository.delete(allocationUuidsToRemove);
}
await this.subspaceRepository.update(
{ uuid: In(subspaces.map((s) => s.uuid)) },
{ disabled: true },
);
const relocatedAllocations = subspaceModel.relocatedAllocations || [];
if (!relocatedAllocations.length) {
return;
}
for (const space of spaces) {
for (const { allocation, tags = [] } of relocatedAllocations) {
const spaceAllocation = await this.spaceProductRepository.findOne({
where: {
inheritedFromModel: { uuid: allocation.uuid },
space: { uuid: space.uuid },
},
relations: ['tags'],
});
if (spaceAllocation) {
if (tags.length) {
spaceAllocation.tags.push(...tags);
await this.spaceProductRepository.save(spaceAllocation);
}
} else {
const newSpaceAllocation = this.spaceProductRepository.create({
space,
inheritedFromModel: allocation,
tags: allocation.tags,
product: allocation.product,
});
await this.spaceProductRepository.save(newSpaceAllocation);
}
}
}
}
async updateSubspaceModel(subspaceModel: ISingleSubspaceModel) {
return this.subspaceRepository.update(
{
subSpaceModel: { uuid: subspaceModel.subspaceModel.uuid },
disabled: false,
},
{ subspaceName: subspaceModel.subspaceModel.subspaceName },
);
}
}

View File

@ -5,6 +5,7 @@ import {
import { ModifyTagModelDto } from '../dtos';
import { ModifyAction } from '@app/common/constants/modify-action.enum';
import { NewTagEntity } from '@app/common/modules/tag';
import { IUpdatedAllocations } from './subspace-product-allocation-update-result.interface';
export interface IRelocatedAllocation {
allocation: SpaceModelProductAllocationEntity;
@ -16,3 +17,8 @@ export interface ISingleSubspaceModel {
tags?: ModifyTagModelDto[];
relocatedAllocations?: IRelocatedAllocation[];
}
export interface ISubspaceModelUpdates {
subspaceModels: ISingleSubspaceModel[];
updatedAllocations: IUpdatedAllocations[];
}

View File

@ -47,7 +47,7 @@ import { SpaceProductAllocationEntity } from '@app/common/modules/space/entities
import { SubspaceEntity } from '@app/common/modules/space/entities/subspace/subspace.entity';
import { SubspaceProductAllocationEntity } from '@app/common/modules/space/entities/subspace/subspace-product-allocation.entity';
import { DeviceEntity } from '@app/common/modules/device/entities';
import { ISingleSubspaceModel } from '../interfaces';
import { ISingleSubspaceModel, ISubspaceModelUpdates } from '../interfaces';
@Injectable()
export class SpaceModelService {
@ -199,7 +199,7 @@ export class SpaceModelService {
param.projectUuid,
);
await queryRunner.connect();
let modifiedSubspaces: ISingleSubspaceModel[] = [];
let modifiedSubspaces: ISubspaceModelUpdates;
try {
await queryRunner.startTransaction();
const spaces = await this.fetchModelSpaces(spaceModel);

View File

@ -16,6 +16,7 @@ import { SubspaceModelProductAllocationService } from './subspace-model-product-
import {
IRelocatedAllocation,
ISingleSubspaceModel,
ISubspaceModelUpdates,
} from 'src/space-model/interfaces';
import { SpaceEntity } from '@app/common/modules/space/entities/space.entity';
import { SubSpaceService } from 'src/space/services/subspace';
@ -118,7 +119,7 @@ export class SubSpaceModelService {
projectUuid: string,
spaceTagUpdateDtos?: ModifyTagModelDto[],
spaces?: SpaceEntity[],
): Promise<ISingleSubspaceModel[]> {
): Promise<ISubspaceModelUpdates> {
try {
if (!dtos || dtos.length === 0) {
return;
@ -160,13 +161,16 @@ export class SubSpaceModelService {
spaceModel,
spaceTagUpdateDtos,
);
return [
createdSubspaces ?? [],
updatedSubspaces ?? [],
deletedSubspaces ?? [],
]
.filter((arr) => arr.length > 0)
.flat();
return {
subspaceModels: [
createdSubspaces ?? [],
updatedSubspaces ?? [],
deletedSubspaces ?? [],
]
.filter((arr) => arr.length > 0)
.flat(),
updatedAllocations: updatedAllocations,
};
} catch (error) {
console.error('Error in modifySubspaceModels:', error);
throw new HttpException(