Files
twenty/packages/twenty-server/src/engine/metadata-modules/view/services/view-v2.service.ts
T
Paul Rastoin 59fbe35a8c Move view in metadata-modules/ and create atomic folder + module for each view entity (#14990)
# Introduction
Preparing view-filter and view-group introduction in v2 core engine
Moving view from `core-modules` to `metadata-modules`

## What happened

### Created dedicated modules for each view entity:
- ViewFieldModule
- ViewFilterModule
- ViewFilterGroupModule
- ViewGroupModule
- ViewSortModule

### Each module is now completely independent with its own:
- Controller
- Resolver
- Service
- Entity

### Created dedicated abstraction metadata module folder for:
- flat-view-field
- flat-view

### Dependencies 
- Eleminated circular dep on ViewModule to all others ones
- Granular import not importing the whole viewModule anymore everywhere


close https://github.com/twentyhq/core-team-issues/issues/1703
2025-10-09 15:18:15 +02:00

287 lines
9.9 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/core-modules/common/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { getSubFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/get-sub-flat-entity-maps-or-throw.util';
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
import { fromCreateViewInputToFlatViewToCreate } from 'src/engine/metadata-modules/flat-view/utils/from-create-view-input-to-flat-view-to-create.util';
import { fromDeleteViewInputToFlatViewOrThrow } from 'src/engine/metadata-modules/flat-view/utils/from-delete-view-input-to-flat-view-or-throw.util';
import { fromDestroyViewInputToFlatViewOrThrow } from 'src/engine/metadata-modules/flat-view/utils/from-destroy-view-input-to-flat-view-or-throw.util';
import { fromUpdateViewInputToFlatViewToUpdateOrThrow } from 'src/engine/metadata-modules/flat-view/utils/from-update-view-input-to-flat-view-to-update-or-throw.util';
import { CreateViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/create-view.input';
import { DeleteViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/delete-view.input';
import { DestroyViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/destroy-view.input';
import { UpdateViewInput } from 'src/engine/metadata-modules/view/dtos/inputs/update-view.input';
import { ViewDTO } from 'src/engine/metadata-modules/view/dtos/view.dto';
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
@Injectable()
export class ViewV2Service {
constructor(
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
) {}
async createOne({
createViewInput,
workspaceId,
}: {
createViewInput: CreateViewInput;
workspaceId: string;
}): Promise<ViewDTO> {
const { flatObjectMetadataMaps, flatViewMaps: existingFlatViewMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatObjectMetadataMaps', 'flatViewMaps'],
},
);
const flatViewFromCreateInput = fromCreateViewInputToFlatViewToCreate({
createViewInput,
workspaceId,
});
const toFlatViewMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: flatViewFromCreateInput,
flatEntityMaps: existingFlatViewMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewMaps: {
from: existingFlatViewMaps,
to: toFlatViewMaps,
},
},
dependencyAllFlatEntityMaps: {
flatObjectMetadataMaps: flatObjectMetadataMaps,
},
buildOptions: {
isSystemBuild: false,
inferDeletionFromMissingEntities: false,
},
workspaceId,
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderExceptionV2(
validateAndBuildResult,
'Multiple validation errors occurred while creating view',
);
}
const { flatViewMaps: recomputedExistingFlatViewMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewMaps'],
},
);
return findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatViewFromCreateInput.id,
flatEntityMaps: recomputedExistingFlatViewMaps,
});
}
async updateOne({
updateViewInput,
workspaceId,
}: {
updateViewInput: UpdateViewInput;
workspaceId: string;
}): Promise<ViewDTO> {
const { flatViewMaps: existingFlatViewMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewMaps'],
},
);
const flatViewFromUpdateInput =
fromUpdateViewInputToFlatViewToUpdateOrThrow({
updateViewInput,
flatViewMaps: existingFlatViewMaps,
});
const fromFlatViewMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [flatViewFromUpdateInput.id],
flatEntityMaps: existingFlatViewMaps,
});
const toFlatViewMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: flatViewFromUpdateInput,
flatEntityMaps: fromFlatViewMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewMaps: {
from: fromFlatViewMaps,
to: toFlatViewMaps,
},
},
buildOptions: {
isSystemBuild: false,
inferDeletionFromMissingEntities: false,
},
workspaceId,
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderExceptionV2(
validateAndBuildResult,
'Multiple validation errors occurred while updating view',
);
}
const { flatViewMaps: recomputedExistingFlatViewMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewMaps'],
},
);
return findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: updateViewInput.id,
flatEntityMaps: recomputedExistingFlatViewMaps,
});
}
async deleteOne({
deleteViewInput,
workspaceId,
}: {
deleteViewInput: DeleteViewInput;
workspaceId: string;
}): Promise<ViewDTO> {
const { flatViewMaps: existingFlatViewMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewMaps'],
},
);
const flatViewFromDeleteInput = fromDeleteViewInputToFlatViewOrThrow({
deleteViewInput,
flatViewMaps: existingFlatViewMaps,
});
const fromFlatViewMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [flatViewFromDeleteInput.id],
flatEntityMaps: existingFlatViewMaps,
});
const toFlatViewMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
flatEntity: flatViewFromDeleteInput,
flatEntityMaps: fromFlatViewMaps,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewMaps: {
from: fromFlatViewMaps,
to: toFlatViewMaps,
},
},
buildOptions: {
isSystemBuild: false,
inferDeletionFromMissingEntities: true,
},
workspaceId,
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderExceptionV2(
validateAndBuildResult,
'Multiple validation errors occurred while deleting view',
);
}
const { flatViewMaps: recomputedExistingFlatViewMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewMaps'],
},
);
return findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: deleteViewInput.id,
flatEntityMaps: recomputedExistingFlatViewMaps,
});
}
async destroyOne({
destroyViewInput,
workspaceId,
}: {
destroyViewInput: DestroyViewInput;
workspaceId: string;
}): Promise<ViewDTO> {
const { flatViewMaps: existingFlatViewMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatViewMaps'],
},
);
const flatViewFromDestroyInput = fromDestroyViewInputToFlatViewOrThrow({
destroyViewInput,
flatViewMaps: existingFlatViewMaps,
});
const fromFlatViewMaps = getSubFlatEntityMapsOrThrow({
flatEntityIds: [flatViewFromDestroyInput.id],
flatEntityMaps: existingFlatViewMaps,
});
const toFlatViewMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({
flatEntityMaps: fromFlatViewMaps,
entityToDeleteId: flatViewFromDestroyInput.id,
});
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
fromToAllFlatEntityMaps: {
flatViewMaps: {
from: fromFlatViewMaps,
to: toFlatViewMaps,
},
},
buildOptions: {
isSystemBuild: false,
inferDeletionFromMissingEntities: true,
},
workspaceId,
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderExceptionV2(
validateAndBuildResult,
'Multiple validation errors occurred while destroying view',
);
}
return flatViewFromDestroyInput;
}
}