Spaces:
Build error
Build error
twenty / packages /twenty-server /src /engine /metadata-modules /front-component /front-component.service.ts
| import { Injectable } from '@nestjs/common'; | |
| import { FileFolder } from 'twenty-shared/types'; | |
| import { isDefined } from 'twenty-shared/utils'; | |
| import { ApplicationService } from 'src/engine/core-modules/application/application.service'; | |
| import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; | |
| import { FileStorageService } from 'src/engine/core-modules/file-storage/services/file-storage.service'; | |
| import { type FileResponse } from 'src/engine/core-modules/file/types/file-response.type'; | |
| import { getContentDisposition } from 'src/engine/core-modules/file/utils/get-content-disposition.utils'; | |
| import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; | |
| import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; | |
| import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; | |
| import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; | |
| import { type FlatFrontComponent } from 'src/engine/metadata-modules/flat-front-component/types/flat-front-component.type'; | |
| import { fromCreateFrontComponentInputToFlatFrontComponentToCreate } from 'src/engine/metadata-modules/flat-front-component/utils/from-create-front-component-input-to-flat-front-component-to-create.util'; | |
| import { fromFlatFrontComponentToFrontComponentDto } from 'src/engine/metadata-modules/flat-front-component/utils/from-flat-front-component-to-front-component-dto.util'; | |
| import { fromUpdateFrontComponentInputToFlatFrontComponentToUpdateOrThrow } from 'src/engine/metadata-modules/flat-front-component/utils/from-update-front-component-input-to-flat-front-component-to-update-or-throw.util'; | |
| import { type CreateFrontComponentInput } from 'src/engine/metadata-modules/front-component/dtos/create-front-component.input'; | |
| import { type FrontComponentDTO } from 'src/engine/metadata-modules/front-component/dtos/front-component.dto'; | |
| import { type UpdateFrontComponentInput } from 'src/engine/metadata-modules/front-component/dtos/update-front-component.input'; | |
| import { | |
| FrontComponentException, | |
| FrontComponentExceptionCode, | |
| } from 'src/engine/metadata-modules/front-component/front-component.exception'; | |
| import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception'; | |
| import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; | |
| () | |
| export class FrontComponentService { | |
| constructor( | |
| private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, | |
| private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, | |
| private readonly applicationService: ApplicationService, | |
| private readonly fileStorageService: FileStorageService, | |
| private readonly twentyConfigService: TwentyConfigService, | |
| ) {} | |
| async findAll(workspaceId: string): Promise<FrontComponentDTO[]> { | |
| const { flatFrontComponentMaps } = | |
| await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( | |
| { | |
| workspaceId, | |
| flatMapsKeys: ['flatFrontComponentMaps'], | |
| }, | |
| ); | |
| return Object.values(flatFrontComponentMaps.byUniversalIdentifier) | |
| .filter(isDefined) | |
| .sort((a, b) => a.name.localeCompare(b.name)) | |
| .map(fromFlatFrontComponentToFrontComponentDto); | |
| } | |
| async findById( | |
| id: string, | |
| workspaceId: string, | |
| ): Promise<FrontComponentDTO | null> { | |
| const { flatFrontComponentMaps } = | |
| await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( | |
| { | |
| workspaceId, | |
| flatMapsKeys: ['flatFrontComponentMaps'], | |
| }, | |
| ); | |
| const flatFrontComponent = findFlatEntityByIdInFlatEntityMaps({ | |
| flatEntityId: id, | |
| flatEntityMaps: flatFrontComponentMaps, | |
| }); | |
| if (!isDefined(flatFrontComponent)) { | |
| return null; | |
| } | |
| return fromFlatFrontComponentToFrontComponentDto(flatFrontComponent); | |
| } | |
| async createOne({ | |
| input, | |
| workspaceId, | |
| ownerFlatApplication, | |
| }: { | |
| input: Omit<CreateFrontComponentInput, 'applicationId'>; | |
| workspaceId: string; | |
| ownerFlatApplication?: FlatApplication; | |
| }): Promise<FlatFrontComponent> { | |
| const resolvedOwnerFlatApplication = | |
| ownerFlatApplication ?? | |
| ( | |
| await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( | |
| { workspaceId }, | |
| ) | |
| ).workspaceCustomFlatApplication; | |
| const flatFrontComponentToCreate = | |
| fromCreateFrontComponentInputToFlatFrontComponentToCreate({ | |
| createFrontComponentInput: input, | |
| workspaceId, | |
| flatApplication: resolvedOwnerFlatApplication, | |
| }); | |
| const validateAndBuildResult = | |
| await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( | |
| { | |
| allFlatEntityOperationByMetadataName: { | |
| frontComponent: { | |
| flatEntityToCreate: [flatFrontComponentToCreate], | |
| flatEntityToDelete: [], | |
| flatEntityToUpdate: [], | |
| }, | |
| }, | |
| workspaceId, | |
| isSystemBuild: false, | |
| applicationUniversalIdentifier: | |
| resolvedOwnerFlatApplication.universalIdentifier, | |
| }, | |
| ); | |
| if (validateAndBuildResult.status === 'fail') { | |
| throw new WorkspaceMigrationBuilderException( | |
| validateAndBuildResult, | |
| 'Multiple validation errors occurred while creating front component', | |
| ); | |
| } | |
| const { flatFrontComponentMaps: recomputedFlatFrontComponentMaps } = | |
| await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( | |
| { | |
| workspaceId, | |
| flatMapsKeys: ['flatFrontComponentMaps'], | |
| }, | |
| ); | |
| const createdFlatFrontComponent = findFlatEntityByIdInFlatEntityMapsOrThrow( | |
| { | |
| flatEntityId: flatFrontComponentToCreate.id, | |
| flatEntityMaps: recomputedFlatFrontComponentMaps, | |
| }, | |
| ); | |
| return createdFlatFrontComponent; | |
| } | |
| async updateOne({ | |
| id, | |
| update, | |
| workspaceId, | |
| ownerFlatApplication, | |
| }: { | |
| id: string; | |
| update: UpdateFrontComponentInput['update']; | |
| workspaceId: string; | |
| ownerFlatApplication?: FlatApplication; | |
| }): Promise<FlatFrontComponent> { | |
| const resolvedOwnerFlatApplication = | |
| ownerFlatApplication ?? | |
| ( | |
| await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( | |
| { workspaceId }, | |
| ) | |
| ).workspaceCustomFlatApplication; | |
| const { flatFrontComponentMaps: existingFlatFrontComponentMaps } = | |
| await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( | |
| { | |
| workspaceId, | |
| flatMapsKeys: ['flatFrontComponentMaps'], | |
| }, | |
| ); | |
| const flatFrontComponentToUpdate = | |
| fromUpdateFrontComponentInputToFlatFrontComponentToUpdateOrThrow({ | |
| flatFrontComponentMaps: existingFlatFrontComponentMaps, | |
| updateFrontComponentInput: { id, update }, | |
| }); | |
| const validateAndBuildResult = | |
| await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( | |
| { | |
| allFlatEntityOperationByMetadataName: { | |
| frontComponent: { | |
| flatEntityToCreate: [], | |
| flatEntityToDelete: [], | |
| flatEntityToUpdate: [flatFrontComponentToUpdate], | |
| }, | |
| }, | |
| workspaceId, | |
| isSystemBuild: false, | |
| applicationUniversalIdentifier: | |
| resolvedOwnerFlatApplication.universalIdentifier, | |
| }, | |
| ); | |
| if (validateAndBuildResult.status === 'fail') { | |
| throw new WorkspaceMigrationBuilderException( | |
| validateAndBuildResult, | |
| 'Multiple validation errors occurred while updating front component', | |
| ); | |
| } | |
| const { flatFrontComponentMaps: recomputedFlatFrontComponentMaps } = | |
| await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( | |
| { | |
| workspaceId, | |
| flatMapsKeys: ['flatFrontComponentMaps'], | |
| }, | |
| ); | |
| const updatedFlatFrontComponent = findFlatEntityByIdInFlatEntityMapsOrThrow( | |
| { | |
| flatEntityId: id, | |
| flatEntityMaps: recomputedFlatFrontComponentMaps, | |
| }, | |
| ); | |
| return updatedFlatFrontComponent; | |
| } | |
| async destroyOne({ | |
| id, | |
| workspaceId, | |
| isSystemBuild = false, | |
| ownerFlatApplication, | |
| }: { | |
| id: string; | |
| workspaceId: string; | |
| isSystemBuild?: boolean; | |
| ownerFlatApplication?: FlatApplication; | |
| }): Promise<FlatFrontComponent> { | |
| const resolvedOwnerFlatApplication = | |
| ownerFlatApplication ?? | |
| ( | |
| await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( | |
| { workspaceId }, | |
| ) | |
| ).workspaceCustomFlatApplication; | |
| const { flatFrontComponentMaps: existingFlatFrontComponentMaps } = | |
| await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( | |
| { | |
| workspaceId, | |
| flatMapsKeys: ['flatFrontComponentMaps'], | |
| }, | |
| ); | |
| const existingFlatFrontComponent = findFlatEntityByIdInFlatEntityMaps({ | |
| flatEntityId: id, | |
| flatEntityMaps: existingFlatFrontComponentMaps, | |
| }); | |
| if (!isDefined(existingFlatFrontComponent)) { | |
| throw new FrontComponentException( | |
| 'Front component to destroy not found', | |
| FrontComponentExceptionCode.FRONT_COMPONENT_NOT_FOUND, | |
| ); | |
| } | |
| const validateAndBuildResult = | |
| await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( | |
| { | |
| allFlatEntityOperationByMetadataName: { | |
| frontComponent: { | |
| flatEntityToCreate: [], | |
| flatEntityToDelete: [existingFlatFrontComponent], | |
| flatEntityToUpdate: [], | |
| }, | |
| }, | |
| workspaceId, | |
| isSystemBuild, | |
| applicationUniversalIdentifier: | |
| resolvedOwnerFlatApplication.universalIdentifier, | |
| }, | |
| ); | |
| if (validateAndBuildResult.status === 'fail') { | |
| throw new WorkspaceMigrationBuilderException( | |
| validateAndBuildResult, | |
| 'Multiple validation errors occurred while destroying front component', | |
| ); | |
| } | |
| return existingFlatFrontComponent; | |
| } | |
| async findByIdOrThrow( | |
| id: string, | |
| workspaceId: string, | |
| ): Promise<FrontComponentDTO> { | |
| const frontComponent = await this.findById(id, workspaceId); | |
| if (!isDefined(frontComponent)) { | |
| throw new FrontComponentException( | |
| 'Front component not found', | |
| FrontComponentExceptionCode.FRONT_COMPONENT_NOT_FOUND, | |
| ); | |
| } | |
| return frontComponent; | |
| } | |
| async getBuiltComponentPresignedUrlOrStream({ | |
| frontComponentId, | |
| workspaceId, | |
| }: { | |
| frontComponentId: string; | |
| workspaceId: string; | |
| }): Promise<FileResponse> { | |
| const frontComponent = await this.findByIdOrThrow( | |
| frontComponentId, | |
| workspaceId, | |
| ); | |
| const application = await this.applicationService.findOneApplicationOrThrow( | |
| { | |
| id: frontComponent.applicationId, | |
| workspaceId, | |
| }, | |
| ); | |
| const mimeType = 'application/javascript'; | |
| const resourceIdentifier = { | |
| workspaceId, | |
| applicationUniversalIdentifier: application.universalIdentifier, | |
| fileFolder: FileFolder.BuiltFrontComponent, | |
| resourcePath: frontComponent.builtComponentPath, | |
| }; | |
| const presignedUrl = await this.fileStorageService.getPresignedUrl({ | |
| ...resourceIdentifier, | |
| expiresInSeconds: this.twentyConfigService.get( | |
| 'STORAGE_S3_PRESIGNED_URL_EXPIRES_IN', | |
| ), | |
| responseContentType: mimeType, | |
| responseContentDisposition: getContentDisposition(mimeType), | |
| }); | |
| if (presignedUrl) { | |
| return { type: 'redirect', presignedUrl }; | |
| } | |
| const stream = await this.fileStorageService.readFile(resourceIdentifier); | |
| return { type: 'stream', stream, mimeType }; | |
| } | |
| } | |