diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 5c5497d501..a3eaaf751f 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -4570,13 +4570,11 @@ export type UpdateLogicFunctionFromSourceInput = { }; export type UpdateLogicFunctionFromSourceInputUpdates = { - checksum?: InputMaybe; cronTriggerSettings?: InputMaybe; databaseEventTriggerSettings?: InputMaybe; description?: InputMaybe; handlerName?: InputMaybe; httpRouteTriggerSettings?: InputMaybe; - isBuildUpToDate?: InputMaybe; isTool?: InputMaybe; name?: InputMaybe; sourceHandlerCode?: InputMaybe; diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-workflow-code-steps.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-workflow-code-steps.command.ts deleted file mode 100644 index 4c6c6a7d6a..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-workflow-code-steps.command.ts +++ /dev/null @@ -1,301 +0,0 @@ -import { Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import crypto from 'crypto'; - -import { Command } from 'nest-commander'; -import { FileFolder, type Sources } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; -import { In, Repository } from 'typeorm'; -import { v4 } from 'uuid'; - -import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; -import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner'; -import { - type CodeStepMigrationTarget, - collectCodeStepMigrationTargets, - migrateWorkflowCodeStepsWithMapping, - type ServerlessToLogicFunctionMapping, -} from 'src/database/commands/upgrade-version-command/1-17/utils/migrate-workflow-code-step.util'; -import { ApplicationService } from 'src/engine/core-modules/application/services/application.service'; -import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service'; -import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; -import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity'; -import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service'; -import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity'; - -const OLD_BUILT_FOLDER = 'built-function'; -const OLD_SOURCE_FOLDER = 'serverless-function'; -const NEW_WORKFLOW_RESOURCE_PREFIX = 'workflow'; - -@Command({ - name: 'upgrade:1-17:migrate-workflow-code-steps', - description: - 'Migrate workflow code steps from v1.16 (serverless) to v1.17 (logic function): create new logic function per (serverlessFunctionId, version), move files, update steps, delete old logic function.', -}) -export class MigrateWorkflowCodeStepsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { - protected readonly logger = new Logger(MigrateWorkflowCodeStepsCommand.name); - - constructor( - @InjectRepository(WorkspaceEntity) - protected readonly workspaceRepository: Repository, - @InjectRepository(LogicFunctionEntity) - private readonly logicFunctionRepository: Repository, - protected readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - protected readonly dataSourceService: DataSourceService, - private readonly fileStorageService: FileStorageService, - private readonly applicationService: ApplicationService, - private readonly logicFunctionMetadataService: LogicFunctionMetadataService, - ) { - super(workspaceRepository, globalWorkspaceOrmManager, dataSourceService); - } - - override async runOnWorkspace({ - workspaceId, - options, - }: RunOnWorkspaceArgs): Promise { - const isDryRun = options.dryRun ?? false; - - this.logger.log( - `Running MigrateWorkflowCodeStepsCommand for workspace ${workspaceId}`, - ); - - const workflowVersionRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workflowVersion', - { shouldBypassPermissionChecks: true }, - ); - - const workflowVersions = await workflowVersionRepository.find({ - select: ['id', 'steps', 'status'], - where: { - status: In([WorkflowVersionStatus.DRAFT, WorkflowVersionStatus.ACTIVE]), - }, - }); - - const allTargets = new Map(); - - for (const version of workflowVersions) { - const targets = collectCodeStepMigrationTargets(version.steps); - - for (const target of targets) { - const key = `${target.serverlessFunctionId}:${target.serverlessFunctionVersion}`; - - if (!allTargets.has(key)) { - allTargets.set(key, target); - } - } - } - - const targetsList = Array.from(allTargets.values()); - - if (targetsList.length === 0) { - this.logger.log(`No code steps to migrate in workspace ${workspaceId}`); - - return; - } - - const mapping = new Map(); - - if (!isDryRun) { - for (const target of targetsList) { - const newLogicFunctionId = - await this.createLogicFunctionAndMigrateFiles(workspaceId, target); - - if (isDefined(newLogicFunctionId)) { - const key = `${target.serverlessFunctionId}:${target.serverlessFunctionVersion}`; - - mapping.set(key, newLogicFunctionId); - } - } - - const serverlessToLogicMapping: ServerlessToLogicFunctionMapping = ( - serverlessFunctionId, - serverlessFunctionVersion, - ) => { - const key = `${serverlessFunctionId}:${serverlessFunctionVersion}`; - const newId = mapping.get(key); - - if (!isDefined(newId)) { - throw new Error( - `Missing mapping for ${serverlessFunctionId}:${serverlessFunctionVersion}`, - ); - } - - return newId; - }; - - for (const version of workflowVersions) { - const { migratedSteps, hasChanges } = - migrateWorkflowCodeStepsWithMapping( - version.steps, - serverlessToLogicMapping, - ); - - if (!hasChanges) { - continue; - } - - await workflowVersionRepository.update( - { id: version.id }, - { steps: migratedSteps }, - ); - - this.logger.log( - `Migrated workflow version ${version.id} in workspace ${workspaceId}`, - ); - } - } else { - this.logger.log( - `[DRY RUN] Would create ${targetsList.length} new logic function(s), migrate files, update workflow steps, and delete ${new Set(targetsList.map((t) => t.serverlessFunctionId)).size} old logic function(s) in workspace ${workspaceId}`, - ); - } - } - - private async getApplicationUniversalIdentifier( - applicationId: string, - ): Promise { - const application = await this.applicationService.findById(applicationId); - - return application?.universalIdentifier ?? null; - } - - private async createLogicFunctionAndMigrateFiles( - workspaceId: string, - target: CodeStepMigrationTarget, - ): Promise { - const { serverlessFunctionId, serverlessFunctionVersion } = target; - const version = serverlessFunctionVersion ?? 'draft'; - - const oldLogicFunction = await this.logicFunctionRepository.findOne({ - where: { id: serverlessFunctionId, workspaceId }, - }); - - if (!isDefined(oldLogicFunction)) { - this.logger.warn( - `Logic function ${serverlessFunctionId} not found in workspace ${workspaceId}, skipping`, - ); - - return null; - } - - const newLogicFunctionId = v4(); - const applicationUniversalIdentifier = - await this.getApplicationUniversalIdentifier( - oldLogicFunction.applicationId, - ); - - const { builtContent, sourceContent } = await this.readOldFunctionFiles( - workspaceId, - serverlessFunctionId, - version, - ); - - const checksum = crypto - .createHash('md5') - .update(builtContent) - .digest('hex'); - - if (isDefined(applicationUniversalIdentifier)) { - await this.uploadFunctionFiles( - workspaceId, - applicationUniversalIdentifier, - newLogicFunctionId, - { builtContent, sourceContent }, - ); - } - - await this.logicFunctionMetadataService.createOne({ - input: { - id: newLogicFunctionId, - name: oldLogicFunction.name, - description: oldLogicFunction.description ?? undefined, - timeoutSeconds: oldLogicFunction.timeoutSeconds ?? 300, - toolInputSchema: oldLogicFunction.toolInputSchema ?? {}, - isTool: oldLogicFunction.isTool ?? false, - handlerName: oldLogicFunction.handlerName, - sourceHandlerPath: `${NEW_WORKFLOW_RESOURCE_PREFIX}/${newLogicFunctionId}/src/index.ts`, - builtHandlerPath: `${NEW_WORKFLOW_RESOURCE_PREFIX}/${newLogicFunctionId}/src/index.mjs`, - checksum, - isBuildUpToDate: true, - }, - workspaceId, - ownerFlatApplication: oldLogicFunction.application, - }); - - this.logger.log( - `Created logic function ${newLogicFunctionId} (from ${serverlessFunctionId}/${version}) and migrated files in workspace ${workspaceId}`, - ); - - return newLogicFunctionId; - } - - private async readOldFunctionFiles( - workspaceId: string, - serverlessFunctionId: string, - version: string, - ): Promise<{ builtContent: string; sourceContent: string }> { - const workspacePrefix = `workspace-${workspaceId}`; - - const builtSources = await this.fileStorageService.readFolderLegacy( - `${workspacePrefix}/${OLD_BUILT_FOLDER}/${serverlessFunctionId}/${version}`, - ); - - const sourceSources = await this.fileStorageService.readFolderLegacy( - `${workspacePrefix}/${OLD_SOURCE_FOLDER}/${serverlessFunctionId}/${version}`, - ); - - // Old source layout may nest files under a `src/` key - const sourceRoot = - (sourceSources.src as Sources) ?? (sourceSources as Sources); - - const builtContent = builtSources['index.mjs'] as string; - const sourceContent = sourceRoot['index.ts'] as string; - - if (!isDefined(builtContent) || !isDefined(sourceContent)) { - throw new Error( - `Missing index.mjs or index.ts for serverless function ${serverlessFunctionId}/${version} in workspace ${workspaceId}`, - ); - } - - return { builtContent, sourceContent }; - } - - private async uploadFunctionFiles( - workspaceId: string, - applicationUniversalIdentifier: string, - newLogicFunctionId: string, - files: { builtContent: string; sourceContent: string }, - ): Promise { - const resourcePath = `${NEW_WORKFLOW_RESOURCE_PREFIX}/${newLogicFunctionId}/src`; - - await this.fileStorageService.writeFile({ - workspaceId, - applicationUniversalIdentifier, - fileFolder: FileFolder.BuiltLogicFunction, - resourcePath: `${resourcePath}/index.mjs`, - sourceFile: Buffer.from(files.builtContent), - mimeType: 'application/javascript', - settings: { - isTemporaryFile: false, - toDelete: false, - }, - }); - - await this.fileStorageService.writeFile({ - workspaceId, - applicationUniversalIdentifier, - fileFolder: FileFolder.Source, - resourcePath: `${resourcePath}/index.ts`, - sourceFile: Buffer.from(files.sourceContent), - mimeType: 'application/typescript', - settings: { - isTemporaryFile: false, - toDelete: false, - }, - }); - } -} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-upgrade-version-command.module.ts index 733c02b882..ff0678f022 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-upgrade-version-command.module.ts @@ -11,7 +11,6 @@ import { MigrateDateTimeIsFilterValuesCommand } from 'src/database/commands/upgr import { MigrateNoteTargetToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-note-target-to-morph-relations.command'; import { MigrateSendEmailRecipientsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-send-email-recipients.command'; import { MigrateTaskTargetToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-task-target-to-morph-relations.command'; -import { MigrateWorkflowCodeStepsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-workflow-code-steps.command'; import { SeedWorkflowV1_16Command } from 'src/database/commands/upgrade-version-command/1-17/1-17-seed-workflow-v1-16.command'; import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; @@ -25,7 +24,6 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module'; import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity'; -import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity'; @@ -65,7 +63,6 @@ import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/tas ApplicationModule, UserWorkspaceModule, WorkspaceMigrationModule, - LogicFunctionModule, RecordPositionModule, GlobalWorkspaceDataSourceModule, ], @@ -79,7 +76,6 @@ import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/tas DeleteFileRecordsAndUpdateTableCommand, MigrateSendEmailRecipientsCommand, MigrateDateTimeIsFilterValuesCommand, - MigrateWorkflowCodeStepsCommand, SeedWorkflowV1_16Command, BackfillApplicationPackageFilesCommand, ], @@ -93,7 +89,6 @@ import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/tas MigrateSendEmailRecipientsCommand, MigrateDateTimeIsFilterValuesCommand, DeleteFileRecordsAndUpdateTableCommand, - MigrateWorkflowCodeStepsCommand, SeedWorkflowV1_16Command, BackfillApplicationPackageFilesCommand, ], diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/utils/migrate-workflow-code-step.util.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/utils/migrate-workflow-code-step.util.ts deleted file mode 100644 index 0fac58ca72..0000000000 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/utils/migrate-workflow-code-step.util.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { isDefined } from 'twenty-shared/utils'; - -import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; - -type LegacyCodeStepInput = { - serverlessFunctionId: string; - serverlessFunctionVersion?: string; - serverlessFunctionInput?: Record; -}; - -type MigratedCodeStepInput = { - logicFunctionId: string; - logicFunctionInput?: Record; -}; - -type WorkflowStep = { - id: string; - type: string; - settings: { - input: LegacyCodeStepInput | MigratedCodeStepInput; - outputSchema?: unknown; - }; -}; - -export const needsCodeStepMigration = ( - input: LegacyCodeStepInput | MigratedCodeStepInput, -): input is LegacyCodeStepInput => { - return ( - 'serverlessFunctionId' in input && isDefined(input.serverlessFunctionId) - ); -}; - -export type CodeStepMigrationTarget = { - serverlessFunctionId: string; - serverlessFunctionVersion: string; -}; - -export type ServerlessToLogicFunctionMapping = ( - serverlessFunctionId: string, - serverlessFunctionVersion: string, -) => string; - -export const collectCodeStepMigrationTargets = ( - steps: unknown, -): CodeStepMigrationTarget[] => { - if (!isDefined(steps) || !Array.isArray(steps) || steps.length === 0) { - return []; - } - - const typedSteps = steps as WorkflowStep[]; - const seen = new Set(); - - const targets: CodeStepMigrationTarget[] = []; - - for (const step of typedSteps) { - if (step.type !== WorkflowActionType.CODE) { - continue; - } - - const input = step.settings.input as - | LegacyCodeStepInput - | MigratedCodeStepInput; - - if (!needsCodeStepMigration(input)) { - continue; - } - - const version = input.serverlessFunctionVersion ?? 'draft'; - const key = `${input.serverlessFunctionId}:${version}`; - - if (seen.has(key)) { - continue; - } - seen.add(key); - targets.push({ - serverlessFunctionId: input.serverlessFunctionId, - serverlessFunctionVersion: version, - }); - } - - return targets; -}; - -export const migrateWorkflowCodeStepsWithMapping = ( - steps: unknown, - mapping: ServerlessToLogicFunctionMapping, -): { migratedSteps: WorkflowStep[]; hasChanges: boolean } => { - if (!isDefined(steps) || !Array.isArray(steps) || steps.length === 0) { - return { migratedSteps: [], hasChanges: false }; - } - - const typedSteps = steps as WorkflowStep[]; - let hasChanges = false; - - const migratedSteps = typedSteps.map((step) => { - if (step.type !== WorkflowActionType.CODE) { - return step; - } - - const input = step.settings.input as - | LegacyCodeStepInput - | MigratedCodeStepInput; - - if (!needsCodeStepMigration(input)) { - return step; - } - - hasChanges = true; - const version = input.serverlessFunctionVersion ?? 'draft'; - const newLogicFunctionId = mapping(input.serverlessFunctionId, version); - - return { - ...step, - settings: { - ...step.settings, - input: { - logicFunctionId: newLogicFunctionId, - logicFunctionInput: input.serverlessFunctionInput ?? {}, - } as MigratedCodeStepInput, - }, - }; - }); - - return { migratedSteps, hasChanges }; -}; diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts index c1abb02a3b..b60470b05d 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts @@ -17,7 +17,6 @@ import { MakeWebhookUniversalIdentifierAndApplicationIdNotNullableMigrationComma import { MigrateAttachmentToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-attachment-to-morph-relations.command'; import { MigrateNoteTargetToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-note-target-to-morph-relations.command'; import { MigrateTaskTargetToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-task-target-to-morph-relations.command'; -import { MigrateWorkflowCodeStepsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-workflow-code-steps.command'; import { BackfillFileSizeAndMimeTypeCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-backfill-file-size-and-mime-type.command'; import { BackfillMessageChannelThrottleRetryAfterCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-backfill-message-channel-throttle-retry-after.command'; import { BackfillStandardViewsAndFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/1-18/1-18-backfill-standard-views-and-field-metadata.command'; @@ -57,7 +56,6 @@ export class UpgradeCommand extends UpgradeCommandRunner { protected readonly migrateTaskTargetToMorphRelationsCommand: MigrateTaskTargetToMorphRelationsCommand, protected readonly identifyWebhookMetadataCommand: IdentifyWebhookMetadataCommand, protected readonly makeWebhookUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeWebhookUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, - protected readonly migrateWorkflowCodeStepsCommand: MigrateWorkflowCodeStepsCommand, protected readonly fixMorphRelationFieldNamesCommand: FixMorphRelationFieldNamesCommand, // 1.18 Commands @@ -94,7 +92,6 @@ export class UpgradeCommand extends UpgradeCommandRunner { this .makeWebhookUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, this.deleteFileRecordsAndUpdateTableCommand, - this.migrateWorkflowCodeStepsCommand, this.backfillApplicationPackageFilesCommand, this.fixMorphRelationFieldNamesCommand, ]; diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-logic-function.input.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-logic-function.input.ts deleted file mode 100644 index d00e16f721..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-logic-function.input.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { Field, InputType } from '@nestjs/graphql'; - -import { - IsBoolean, - IsNotEmpty, - IsNumber, - IsObject, - IsOptional, - IsString, - IsUUID, - Matches, - Max, - Min, -} from 'class-validator'; -import graphqlTypeJson from 'graphql-type-json'; -import { - CronTriggerSettings, - DatabaseEventTriggerSettings, - HttpRouteTriggerSettings, -} from 'twenty-shared/application'; - -import type { InputJsonSchema } from 'twenty-shared/logic-function'; - -import type { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type'; -import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; - -@InputType() -export class CreateLogicFunction { - @IsUUID() - @IsOptional() - @Field(() => UUIDScalarType, { nullable: true }) - id?: string; - - @IsUUID() - @IsOptional() - @Field(() => UUIDScalarType, { nullable: true }) - universalIdentifier?: string; - - @IsString() - @IsNotEmpty() - @Field() - name: string; - - @IsString() - @IsOptional() - @Field({ nullable: true }) - description?: string; - - @IsNumber() - @Field({ nullable: true }) - @Min(1) - @Max(900) - @IsOptional() - timeoutSeconds?: number; - - @Field(() => graphqlTypeJson, { nullable: false }) - @IsObject() - toolInputSchema: InputJsonSchema; - - @IsBoolean() - @Field({ nullable: true }) - @IsOptional() - isTool?: boolean; - - @IsBoolean() - @Field({ nullable: false }) - isBuildUpToDate: boolean; - - @IsString() - @Field({ nullable: true }) - @IsOptional() - checksum?: string; - - @IsString() - @Matches(/^[a-zA-Z_$][a-zA-Z0-9_$]*$/, { - message: 'handlerName must be a valid JavaScript identifier', - }) - @Field({ nullable: false }) - handlerName: string; - - @IsString() - @Field({ nullable: false }) - sourceHandlerPath: string; - - @IsString() - @Field({ nullable: false }) - builtHandlerPath: string; - - @IsObject() - @Field(() => graphqlTypeJson, { nullable: true }) - @IsOptional() - cronTriggerSettings?: JsonbProperty; - - @IsObject() - @Field(() => graphqlTypeJson, { nullable: true }) - @IsOptional() - databaseEventTriggerSettings?: JsonbProperty; - - @IsObject() - @Field(() => graphqlTypeJson, { nullable: true }) - @IsOptional() - httpRouteTriggerSettings?: JsonbProperty; -} diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input.ts index c1de0b29d7..ee7cc84c5c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input.ts @@ -71,16 +71,6 @@ class UpdateLogicFunctionFromSourceInputUpdates { @IsOptional() isTool?: boolean; - @IsBoolean() - @Field({ nullable: true }) - @IsOptional() - isBuildUpToDate?: boolean; - - @IsString() - @Field({ nullable: true }) - @IsOptional() - checksum?: string; - @IsObject() @Field(() => graphqlTypeJson, { nullable: true }) @IsOptional() diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.module.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.module.ts index 7d52dd4ec9..ddc365ca7b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.module.ts @@ -17,7 +17,7 @@ import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata import { LogicFunctionLayerModule } from 'src/engine/metadata-modules/logic-function-layer/logic-function-layer.module'; import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity'; import { LogicFunctionResolver } from 'src/engine/metadata-modules/logic-function/logic-function.resolver'; -import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service'; +import { LogicFunctionFromSourceHelperService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source-helper.service'; import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service'; import { WorkspaceFlatLogicFunctionMapCacheService } from 'src/engine/metadata-modules/logic-function/services/workspace-flat-logic-function-map-cache.service'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; @@ -43,11 +43,11 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace SecretEncryptionModule, ], providers: [ - LogicFunctionMetadataService, + LogicFunctionFromSourceHelperService, LogicFunctionFromSourceService, LogicFunctionResolver, WorkspaceFlatLogicFunctionMapCacheService, ], - exports: [LogicFunctionMetadataService, LogicFunctionFromSourceService], + exports: [LogicFunctionFromSourceService], }) export class LogicFunctionModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.resolver.ts index 78300287d6..96c6cc603b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.resolver.ts @@ -14,19 +14,22 @@ import { FeatureFlagGuard } from 'src/engine/guards/feature-flag.guard'; import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard'; import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +import { CreateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function-from-source.input'; import { ExecuteOneLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/execute-logic-function.input'; import { LogicFunctionExecutionResultDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto'; import { LogicFunctionIdInput } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-id.input'; import { LogicFunctionLogsDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-logs.dto'; import { LogicFunctionLogsInput } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-logs.input'; import { LogicFunctionDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function.dto'; +import { UpdateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input'; import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service'; +import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type'; +import { findFlatLogicFunctionOrThrow } from 'src/engine/metadata-modules/logic-function/utils/find-flat-logic-function-or-throw.util'; +import { fromFlatLogicFunctionToLogicFunctionDto } from 'src/engine/metadata-modules/logic-function/utils/from-flat-logic-function-to-logic-function-dto.util'; import { logicFunctionGraphQLApiExceptionHandler } from 'src/engine/metadata-modules/logic-function/utils/logic-function-graphql-api-exception-handler.utils'; import { SubscriptionChannel } from 'src/engine/subscriptions/enums/subscription-channel.enum'; import { SubscriptionService } from 'src/engine/subscriptions/subscription.service'; -import { CreateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function-from-source.input'; -import { UpdateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input'; -import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service'; @UseGuards(WorkspaceAuthGuard, FeatureFlagGuard, NoPermissionGuard) @MetadataResolver() @@ -35,7 +38,7 @@ import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic- export class LogicFunctionResolver { constructor( private readonly logicFunctionFromSourceService: LogicFunctionFromSourceService, - private readonly logicFunctionMetadataService: LogicFunctionMetadataService, + private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, private readonly subscriptionService: SubscriptionService, ) {} @@ -45,10 +48,20 @@ export class LogicFunctionResolver { @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { try { - return await this.logicFunctionMetadataService.findOne({ + const { flatLogicFunctionMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatLogicFunctionMaps'], + }, + ); + + const flatLogicFunction = findFlatLogicFunctionOrThrow({ id, - workspaceId, + flatLogicFunctionMaps, }); + + return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction }); } catch (error) { return logicFunctionGraphQLApiExceptionHandler(error); } @@ -59,9 +72,23 @@ export class LogicFunctionResolver { @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { try { - return await this.logicFunctionMetadataService.findMany({ - workspaceId, - }); + const { flatLogicFunctionMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatLogicFunctionMaps'], + }, + ); + + return Object.values(flatLogicFunctionMaps.byUniversalIdentifier) + .filter( + (flatLogicFunction): flatLogicFunction is FlatLogicFunction => + isDefined(flatLogicFunction) && + !isDefined(flatLogicFunction.deletedAt), + ) + .map((flatLogicFunction) => + fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction }), + ); } catch (error) { return logicFunctionGraphQLApiExceptionHandler(error); } @@ -74,10 +101,33 @@ export class LogicFunctionResolver { @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ) { try { - return await this.logicFunctionMetadataService.getAvailablePackages({ - logicFunctionId: id, - workspaceId, - }); + const { flatLogicFunctionMaps, flatApplicationMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatLogicFunctionMaps', 'flatApplicationMaps'], + }, + ); + + const logicFunctionUniversalIdentifier = + flatLogicFunctionMaps.universalIdentifierById[id]; + + if (!logicFunctionUniversalIdentifier) { + return {}; + } + + const logicFunction = + flatLogicFunctionMaps.byUniversalIdentifier[ + logicFunctionUniversalIdentifier + ]; + + if (!logicFunction) { + return {}; + } + + const application = flatApplicationMaps.byId[logicFunction.applicationId]; + + return application?.availablePackages ?? {}; } catch (error) { return logicFunctionGraphQLApiExceptionHandler(error); } @@ -90,7 +140,7 @@ export class LogicFunctionResolver { @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { try { - return await this.logicFunctionFromSourceService.deleteOne({ + return await this.logicFunctionFromSourceService.deleteOneWithSource({ id, workspaceId, }); @@ -106,7 +156,7 @@ export class LogicFunctionResolver { @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { try { - return await this.logicFunctionFromSourceService.createOne({ + return await this.logicFunctionFromSourceService.createOneFromSource({ input, workspaceId, }); @@ -122,7 +172,7 @@ export class LogicFunctionResolver { @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { try { - return await this.logicFunctionFromSourceService.executeOne({ + return await this.logicFunctionFromSourceService.executeOneFromSource({ id, payload, workspaceId, @@ -151,13 +201,13 @@ export class LogicFunctionResolver { @Mutation(() => Boolean) @UseGuards(SettingsPermissionGuard(PermissionFlagType.WORKFLOWS)) async updateOneLogicFunction( - @Args('input') { id, update }: UpdateLogicFunctionFromSourceInput, + @Args('input') + updateLogicFunctionFromSourceInput: UpdateLogicFunctionFromSourceInput, @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, ): Promise { try { - await this.logicFunctionFromSourceService.updateOne({ - id, - update, + await this.logicFunctionFromSourceService.updateOneFromSource({ + updateLogicFunctionFromSourceInput, workspaceId, }); diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-from-source-helper.service.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-from-source-helper.service.ts new file mode 100644 index 0000000000..9afa21fbe0 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-from-source-helper.service.ts @@ -0,0 +1,153 @@ +import { Injectable } from '@nestjs/common'; + +import { join } from 'path'; + +import { ApplicationService } from 'src/engine/core-modules/application/services/application.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 { + DEFAULT_BUILT_HANDLER_PATH, + DEFAULT_SOURCE_HANDLER_PATH, +} from 'src/engine/metadata-modules/logic-function/constants/handler.contant'; +import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type'; +import { findFlatLogicFunctionOrThrow } from 'src/engine/metadata-modules/logic-function/utils/find-flat-logic-function-or-throw.util'; +import { getLogicFunctionSubfolderForFromSource } from 'src/engine/metadata-modules/logic-function/utils/get-logic-function-subfolder-for-from-source'; +import { type UniversalFlatLogicFunction } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-logic-function.type'; +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'; + +@Injectable() +export class LogicFunctionFromSourceHelperService { + constructor( + private readonly applicationService: ApplicationService, + private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, + private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, + ) {} + + async findLogicFunctionAndApplicationOrThrow({ + id, + workspaceId, + }: { + id: string; + workspaceId: string; + }) { + const [{ flatLogicFunctionMaps }, { workspaceCustomFlatApplication }] = + await Promise.all([ + this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps({ + workspaceId, + flatMapsKeys: ['flatLogicFunctionMaps'], + }), + this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { workspaceId }, + ), + ]); + + const flatLogicFunction = findFlatLogicFunctionOrThrow({ + id, + flatLogicFunctionMaps, + }); + + return { + flatLogicFunction, + ownerFlatApplication: workspaceCustomFlatApplication, + }; + } + + buildHandlerPaths(logicFunctionId: string) { + const logicFunctionSubfolder = + getLogicFunctionSubfolderForFromSource(logicFunctionId); + + return { + sourceHandlerPath: join( + logicFunctionSubfolder, + DEFAULT_SOURCE_HANDLER_PATH, + ), + builtHandlerPath: join( + logicFunctionSubfolder, + DEFAULT_BUILT_HANDLER_PATH, + ), + }; + } + + async createOneFromMetadata({ + universalFlatLogicFunctionToCreate, + workspaceId, + }: { + universalFlatLogicFunctionToCreate: UniversalFlatLogicFunction & { + id: string; + }; + workspaceId: string; + }): Promise { + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + allFlatEntityOperationByMetadataName: { + logicFunction: { + flatEntityToCreate: [universalFlatLogicFunctionToCreate], + flatEntityToDelete: [], + flatEntityToUpdate: [], + }, + }, + workspaceId, + isSystemBuild: false, + applicationUniversalIdentifier: + universalFlatLogicFunctionToCreate.applicationUniversalIdentifier, + }, + ); + + if (validateAndBuildResult.status === 'fail') { + throw new WorkspaceMigrationBuilderException( + validateAndBuildResult, + 'Multiple validation errors occurred while creating logic function', + ); + } + + return universalFlatLogicFunctionToCreate; + } + + async updateOneFromMetadata({ + flatLogicFunctionToUpdate, + workspaceId, + applicationUniversalIdentifier, + }: { + flatLogicFunctionToUpdate: FlatLogicFunction; + workspaceId: string; + applicationUniversalIdentifier: string; + }): Promise { + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + allFlatEntityOperationByMetadataName: { + logicFunction: { + flatEntityToCreate: [], + flatEntityToDelete: [], + flatEntityToUpdate: [flatLogicFunctionToUpdate], + }, + }, + workspaceId, + isSystemBuild: false, + applicationUniversalIdentifier, + }, + ); + + if (validateAndBuildResult.status === 'fail') { + throw new WorkspaceMigrationBuilderException( + validateAndBuildResult, + 'Multiple validation errors occurred while updating logic function', + ); + } + + const { flatLogicFunctionMaps: recomputedFlatLogicFunctionMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatLogicFunctionMaps'], + }, + ); + + return findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: flatLogicFunctionToUpdate.id, + flatEntityMaps: recomputedFlatLogicFunctionMaps, + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-from-source.service.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-from-source.service.ts index 649cb27831..25982602d9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-from-source.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-from-source.service.ts @@ -1,77 +1,50 @@ import { Injectable } from '@nestjs/common'; -import { join } from 'path'; - import { v4 } from 'uuid'; +import { isDefined } from 'twenty-shared/utils'; import { SEED_LOGIC_FUNCTION_INPUT_SCHEMA } from 'twenty-shared/logic-function'; import { ApplicationService } from 'src/engine/core-modules/application/services/application.service'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { LogicFunctionExecutorService } from 'src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service'; import { LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.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 { CreateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function-from-source.input'; import { LogicFunctionExecutionResultDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto'; import { LogicFunctionDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function.dto'; -import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service'; -import { findFlatLogicFunctionOrThrow } from 'src/engine/metadata-modules/logic-function/utils/find-flat-logic-function-or-throw.util'; -import { fromFlatLogicFunctionToLogicFunctionDto } from 'src/engine/metadata-modules/logic-function/utils/from-flat-logic-function-to-logic-function-dto.util'; -import { CreateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function-from-source.input'; -import { UpdateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input'; -import { getLogicFunctionSubfolderForFromSource } from 'src/engine/metadata-modules/logic-function/utils/get-logic-function-subfolder-for-from-source'; import { - DEFAULT_BUILT_HANDLER_PATH, - DEFAULT_SOURCE_HANDLER_PATH, -} from 'src/engine/metadata-modules/logic-function/constants/handler.contant'; + LogicFunctionException, + LogicFunctionExceptionCode, +} from 'src/engine/metadata-modules/logic-function/logic-function.exception'; +import { LogicFunctionFromSourceHelperService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source-helper.service'; +import { type UpdateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input'; +import { buildUniversalFlatLogicFunctionToCreate } from 'src/engine/metadata-modules/logic-function/utils/build-universal-flat-logic-function-to-create.util'; +import { fromCreateLogicFunctionFromSourceInputToUniversalFlatLogicFunctionToCreate } from 'src/engine/metadata-modules/logic-function/utils/from-create-logic-function-from-source-input-to-universal-flat-logic-function-to-create.util'; +import { fromFlatLogicFunctionToLogicFunctionDto } from 'src/engine/metadata-modules/logic-function/utils/from-flat-logic-function-to-logic-function-dto.util'; +import { fromUpdateLogicFunctionFromSourceInputToFlatLogicFunctionToUpdate } from 'src/engine/metadata-modules/logic-function/utils/from-update-logic-function-from-source-input-to-flat-logic-function-to-update.util'; +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'; @Injectable() export class LogicFunctionFromSourceService { constructor( private readonly logicFunctionExecutorService: LogicFunctionExecutorService, - private readonly logicFunctionMetadataService: LogicFunctionMetadataService, private readonly logicFunctionResourceService: LogicFunctionResourceService, private readonly applicationService: ApplicationService, + private readonly helperService: LogicFunctionFromSourceHelperService, private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, + private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, ) {} - async deleteOne({ - id, - workspaceId, - }: { - id: string; - workspaceId: string; - }): Promise { - const flatLogicFunction = - await this.logicFunctionMetadataService.destroyOne({ - id, - workspaceId, - }); - - return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction }); - } - - private getHandlerPaths(logicFunctionId: string) { - const logicFunctionSubfolder = - getLogicFunctionSubfolderForFromSource(logicFunctionId); - - return { - sourceHandlerPath: join( - logicFunctionSubfolder, - DEFAULT_SOURCE_HANDLER_PATH, - ), - builtHandlerPath: join( - logicFunctionSubfolder, - DEFAULT_BUILT_HANDLER_PATH, - ), - }; - } - - async createOne({ + async createOneFromSource({ input, workspaceId, }: { input: CreateLogicFunctionFromSourceInput; workspaceId: string; }): Promise { - const { workspaceCustomFlatApplication } = + const { workspaceCustomFlatApplication: ownerFlatApplication } = await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( { workspaceId }, ); @@ -79,33 +52,44 @@ export class LogicFunctionFromSourceService { const logicFunctionId = input.id ?? v4(); const { sourceHandlerPath, builtHandlerPath } = - this.getHandlerPaths(logicFunctionId); + this.helperService.buildHandlerPaths(logicFunctionId); if (input.source) { await this.logicFunctionResourceService.uploadSourceFile({ sourceHandlerPath, sourceHandlerCode: input.source.sourceHandlerCode, applicationUniversalIdentifier: - workspaceCustomFlatApplication.universalIdentifier, + ownerFlatApplication.universalIdentifier, workspaceId, }); - const flatLogicFunction = - await this.logicFunctionMetadataService.createOne({ - input: { - ...input, - handlerName: input.source.handlerName, - toolInputSchema: input.source.toolInputSchema, + const universalFlatLogicFunctionToCreate = + fromCreateLogicFunctionFromSourceInputToUniversalFlatLogicFunctionToCreate( + { + createLogicFunctionFromSourceInput: { + ...input, + id: logicFunctionId, + }, sourceHandlerPath, builtHandlerPath, - id: logicFunctionId, + handlerName: input.source.handlerName, + checksum: null, + toolInputSchema: input.source.toolInputSchema, isBuildUpToDate: false, + applicationUniversalIdentifier: + ownerFlatApplication.universalIdentifier, }, - workspaceId, - ownerFlatApplication: workspaceCustomFlatApplication, - }); + ); - return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction }); + await this.helperService.createOneFromMetadata({ + universalFlatLogicFunctionToCreate, + workspaceId, + }); + + return this.findFlatLogicFunctionByIdAndConvertToDto({ + id: universalFlatLogicFunctionToCreate.id, + workspaceId, + }); } const { handlerName, checksum } = @@ -114,63 +98,194 @@ export class LogicFunctionFromSourceService { builtHandlerPath, workspaceId, applicationUniversalIdentifier: - workspaceCustomFlatApplication.universalIdentifier, + ownerFlatApplication.universalIdentifier, }); - const flatLogicFunction = await this.logicFunctionMetadataService.createOne( - { - input: { - ...input, - id: logicFunctionId, + const universalFlatLogicFunctionToCreate = + fromCreateLogicFunctionFromSourceInputToUniversalFlatLogicFunctionToCreate( + { + createLogicFunctionFromSourceInput: { ...input, id: logicFunctionId }, sourceHandlerPath, builtHandlerPath, handlerName, checksum, toolInputSchema: SEED_LOGIC_FUNCTION_INPUT_SCHEMA, isBuildUpToDate: true, + applicationUniversalIdentifier: + ownerFlatApplication.universalIdentifier, }, - workspaceId, - ownerFlatApplication: workspaceCustomFlatApplication, - }, - ); + ); - return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction }); + await this.helperService.createOneFromMetadata({ + universalFlatLogicFunctionToCreate, + workspaceId, + }); + + return this.findFlatLogicFunctionByIdAndConvertToDto({ + id: universalFlatLogicFunctionToCreate.id, + workspaceId, + }); } - async updateOne({ - id, - update, + async duplicateOneWithSource({ + existingLogicFunctionId, workspaceId, }: { - id: string; - update: UpdateLogicFunctionFromSourceInput['update']; + existingLogicFunctionId: string; workspaceId: string; - }): Promise { - const { applicationUniversalIdentifier, flatLogicFunction } = - await this.getLogicFunctionContext({ id, workspaceId }); - - let formattedUpdate: UpdateLogicFunctionFromSourceInput['update'] = { - ...update, - }; - - if (update.sourceHandlerCode) { - await this.logicFunctionResourceService.uploadSourceFile({ - sourceHandlerPath: flatLogicFunction.sourceHandlerPath, - sourceHandlerCode: update.sourceHandlerCode, - applicationUniversalIdentifier, + }): Promise<{ id: string }> { + const { flatLogicFunction: existingLogicFunction, ownerFlatApplication } = + await this.helperService.findLogicFunctionAndApplicationOrThrow({ + id: existingLogicFunctionId, workspaceId, }); - formattedUpdate = { - ...formattedUpdate, - isBuildUpToDate: false, - }; + const newId = v4(); + + const { sourceHandlerPath, builtHandlerPath } = existingLogicFunction; + + const toSourceHandlerPath = sourceHandlerPath.replace( + existingLogicFunction.id, + newId, + ); + const toBuiltHandlerPath = builtHandlerPath.replace( + existingLogicFunction.id, + newId, + ); + + await this.logicFunctionResourceService.copyResources({ + fromSourceHandlerPath: sourceHandlerPath, + toSourceHandlerPath, + fromBuiltHandlerPath: builtHandlerPath, + toBuiltHandlerPath, + workspaceId, + applicationUniversalIdentifier: ownerFlatApplication.universalIdentifier, + }); + + const universalFlatLogicFunctionToCreate = + buildUniversalFlatLogicFunctionToCreate({ + id: newId, + name: existingLogicFunction.name, + description: existingLogicFunction.description, + timeoutSeconds: existingLogicFunction.timeoutSeconds, + toolInputSchema: existingLogicFunction.toolInputSchema, + isTool: existingLogicFunction.isTool, + isBuildUpToDate: existingLogicFunction.isBuildUpToDate, + checksum: existingLogicFunction.checksum, + handlerName: existingLogicFunction.handlerName, + sourceHandlerPath: toSourceHandlerPath, + builtHandlerPath: toBuiltHandlerPath, + cronTriggerSettings: existingLogicFunction.cronTriggerSettings, + databaseEventTriggerSettings: + existingLogicFunction.databaseEventTriggerSettings, + httpRouteTriggerSettings: + existingLogicFunction.httpRouteTriggerSettings, + applicationUniversalIdentifier: + ownerFlatApplication.universalIdentifier, + }); + + const created = await this.helperService.createOneFromMetadata({ + universalFlatLogicFunctionToCreate, + workspaceId, + }); + + if (!isDefined(created)) { + throw new LogicFunctionException( + 'Failed to duplicate logic function', + LogicFunctionExceptionCode.LOGIC_FUNCTION_NOT_FOUND, + ); } - await this.logicFunctionMetadataService.updateOne({ - id, - update: formattedUpdate, + return { id: created.id }; + } + + async updateOneFromSource({ + updateLogicFunctionFromSourceInput, + workspaceId, + }: { + updateLogicFunctionFromSourceInput: UpdateLogicFunctionFromSourceInput; + workspaceId: string; + }): Promise { + const { flatLogicFunction, ownerFlatApplication } = + await this.helperService.findLogicFunctionAndApplicationOrThrow({ + id: updateLogicFunctionFromSourceInput.id, + workspaceId, + }); + + const sourceHandlerCode = + updateLogicFunctionFromSourceInput.update.sourceHandlerCode; + + if (sourceHandlerCode) { + await this.logicFunctionResourceService.uploadSourceFile({ + sourceHandlerPath: flatLogicFunction.sourceHandlerPath, + sourceHandlerCode, + applicationUniversalIdentifier: + ownerFlatApplication.universalIdentifier, + workspaceId, + }); + } + + const flatLogicFunctionToUpdate = + fromUpdateLogicFunctionFromSourceInputToFlatLogicFunctionToUpdate({ + updateLogicFunctionFromSourceInput, + existingFlatLogicFunction: flatLogicFunction, + }); + + await this.helperService.updateOneFromMetadata({ + flatLogicFunctionToUpdate, workspaceId, + applicationUniversalIdentifier: ownerFlatApplication.universalIdentifier, + }); + } + + async deleteOneWithSource({ + id, + workspaceId, + isSystemBuild = false, + ownerFlatApplication, + }: { + id: string; + workspaceId: string; + isSystemBuild?: boolean; + ownerFlatApplication?: FlatApplication; + }): Promise { + const { + flatLogicFunction: existingFlatLogicFunction, + ownerFlatApplication: resolvedOwnerFlatApplication, + } = await this.helperService.findLogicFunctionAndApplicationOrThrow({ + id, + workspaceId, + }); + + const effectiveOwnerFlatApplication = + ownerFlatApplication ?? resolvedOwnerFlatApplication; + + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + allFlatEntityOperationByMetadataName: { + logicFunction: { + flatEntityToCreate: [], + flatEntityToDelete: [existingFlatLogicFunction], + flatEntityToUpdate: [], + }, + }, + workspaceId, + isSystemBuild, + applicationUniversalIdentifier: + effectiveOwnerFlatApplication.universalIdentifier, + }, + ); + + if (validateAndBuildResult.status === 'fail') { + throw new WorkspaceMigrationBuilderException( + validateAndBuildResult, + 'Multiple validation errors occurred while destroying logic function', + ); + } + + return fromFlatLogicFunctionToLogicFunctionDto({ + flatLogicFunction: existingFlatLogicFunction, }); } @@ -181,25 +296,34 @@ export class LogicFunctionFromSourceService { id: string; workspaceId: string; }): Promise { - const { flatLogicFunction, applicationUniversalIdentifier } = - await this.getLogicFunctionContext({ id, workspaceId }); + const { flatLogicFunction, ownerFlatApplication } = + await this.helperService.findLogicFunctionAndApplicationOrThrow({ + id, + workspaceId, + }); const { checksum } = await this.logicFunctionResourceService.buildFromSourceFile({ workspaceId, - applicationUniversalIdentifier, + applicationUniversalIdentifier: + ownerFlatApplication.universalIdentifier, sourceHandlerPath: flatLogicFunction.sourceHandlerPath, builtHandlerPath: flatLogicFunction.builtHandlerPath, }); - await this.logicFunctionMetadataService.updateOne({ - id, - update: { checksum, isBuildUpToDate: true }, + await this.helperService.updateOneFromMetadata({ + flatLogicFunctionToUpdate: { + ...flatLogicFunction, + checksum, + isBuildUpToDate: true, + updatedAt: new Date().toISOString(), + }, workspaceId, + applicationUniversalIdentifier: ownerFlatApplication.universalIdentifier, }); } - async executeOne({ + async executeOneFromSource({ id, payload, workspaceId, @@ -208,10 +332,11 @@ export class LogicFunctionFromSourceService { payload: object; workspaceId: string; }): Promise { - const { flatLogicFunction } = await this.getLogicFunctionContext({ - id, - workspaceId, - }); + const { flatLogicFunction } = + await this.helperService.findLogicFunctionAndApplicationOrThrow({ + id, + workspaceId, + }); if (!flatLogicFunction.isBuildUpToDate) { await this.buildOneFromSource({ workspaceId, id }); @@ -247,43 +372,39 @@ export class LogicFunctionFromSourceService { id: string; workspaceId: string; }): Promise { - const { flatLogicFunction, applicationUniversalIdentifier } = - await this.getLogicFunctionContext({ id, workspaceId }); + const { flatLogicFunction, ownerFlatApplication } = + await this.helperService.findLogicFunctionAndApplicationOrThrow({ + id, + workspaceId, + }); return this.logicFunctionResourceService.getSourceFile({ workspaceId, - applicationUniversalIdentifier, + applicationUniversalIdentifier: ownerFlatApplication.universalIdentifier, sourceHandlerPath: flatLogicFunction.sourceHandlerPath, }); } - private async getLogicFunctionContext({ + private async findFlatLogicFunctionByIdAndConvertToDto({ id, workspaceId, }: { id: string; workspaceId: string; - }) { - const [{ flatLogicFunctionMaps }, { workspaceCustomFlatApplication }] = - await Promise.all([ - this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps({ + }): Promise { + const { flatLogicFunctionMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { workspaceId, flatMapsKeys: ['flatLogicFunctionMaps'], - }), - this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( - { workspaceId }, - ), - ]); + }, + ); - const flatLogicFunction = findFlatLogicFunctionOrThrow({ - id, - flatLogicFunctionMaps, + return fromFlatLogicFunctionToLogicFunctionDto({ + flatLogicFunction: findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: id, + flatEntityMaps: flatLogicFunctionMaps, + }), }); - - return { - flatLogicFunction, - applicationUniversalIdentifier: - workspaceCustomFlatApplication.universalIdentifier, - }; } } diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-metadata.service.ts deleted file mode 100644 index d395dbbee0..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-metadata.service.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { isDefined } from 'twenty-shared/utils'; - -import { ApplicationService } from 'src/engine/core-modules/application/services/application.service'; -import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; -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 { - LogicFunctionException, - LogicFunctionExceptionCode, -} from 'src/engine/metadata-modules/logic-function/logic-function.exception'; -import { FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type'; -import { findFlatLogicFunctionOrThrow } from 'src/engine/metadata-modules/logic-function/utils/find-flat-logic-function-or-throw.util'; -import { fromCreateLogicFunctionInputToFlatLogicFunction } from 'src/engine/metadata-modules/logic-function/utils/from-create-logic-function-from-source-input-to-flat-logic-function.util'; -import { fromUpdateLogicFunctionInputToFlatLogicFunctionToUpdateOrThrow } from 'src/engine/metadata-modules/logic-function/utils/from-update-logic-function-input-to-flat-logic-function-to-update-or-throw.util'; -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'; -import { LogicFunctionDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function.dto'; -import { fromFlatLogicFunctionToLogicFunctionDto } from 'src/engine/metadata-modules/logic-function/utils/from-flat-logic-function-to-logic-function-dto.util'; -import { CreateLogicFunction } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function.input'; -import { UpdateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input'; - -@Injectable() -export class LogicFunctionMetadataService { - constructor( - private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, - private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, - private readonly applicationService: ApplicationService, - ) {} - - async findOne({ - id, - workspaceId, - }: { - id: string; - workspaceId: string; - }): Promise { - const { flatLogicFunctionMaps } = - await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatLogicFunctionMaps'], - }, - ); - - const flatLogicFunction = findFlatLogicFunctionOrThrow({ - id, - flatLogicFunctionMaps, - }); - - return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction }); - } - - async findMany({ - workspaceId, - }: { - workspaceId: string; - }): Promise { - const { flatLogicFunctionMaps } = - await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatLogicFunctionMaps'], - }, - ); - - return Object.values(flatLogicFunctionMaps.byUniversalIdentifier) - .filter( - (flatLogicFunction): flatLogicFunction is FlatLogicFunction => - isDefined(flatLogicFunction) && - !isDefined(flatLogicFunction.deletedAt), - ) - .map((flatLogicFunction) => - fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction }), - ); - } - - async createOne({ - input, - workspaceId, - ownerFlatApplication, - }: { - input: CreateLogicFunction; - ownerFlatApplication: FlatApplication; - workspaceId: string; - }) { - const resolvedOwnerFlatApplication = - ownerFlatApplication ?? - ( - await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( - { workspaceId }, - ) - ).workspaceCustomFlatApplication; - - const flatLogicFunctionToCreate = - fromCreateLogicFunctionInputToFlatLogicFunction({ - createLogicFunctionInput: input, - workspaceId, - ownerFlatApplication: resolvedOwnerFlatApplication, - }); - - const validateAndBuildResult = - await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( - { - allFlatEntityOperationByMetadataName: { - logicFunction: { - flatEntityToCreate: [flatLogicFunctionToCreate], - flatEntityToDelete: [], - flatEntityToUpdate: [], - }, - }, - workspaceId, - isSystemBuild: false, - applicationUniversalIdentifier: - resolvedOwnerFlatApplication.universalIdentifier, - }, - ); - - if (validateAndBuildResult.status === 'fail') { - throw new WorkspaceMigrationBuilderException( - validateAndBuildResult, - 'Multiple validation errors occurred while creating logic function', - ); - } - - return flatLogicFunctionToCreate; - } - - async updateOne({ - id, - update, - workspaceId, - ownerFlatApplication, - }: { - id: string; - update: Omit; - workspaceId: string; - ownerFlatApplication?: FlatApplication; - }) { - const resolvedOwnerFlatApplication = - ownerFlatApplication ?? - ( - await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( - { workspaceId }, - ) - ).workspaceCustomFlatApplication; - - const { flatLogicFunctionMaps } = - await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatLogicFunctionMaps'], - }, - ); - - const optimisticallyUpdatedFlatLogicFunction = - fromUpdateLogicFunctionInputToFlatLogicFunctionToUpdateOrThrow({ - flatLogicFunctionMaps, - updateLogicFunctionInput: { id, update }, - }); - - const validateAndBuildResult = - await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( - { - allFlatEntityOperationByMetadataName: { - logicFunction: { - flatEntityToCreate: [], - flatEntityToDelete: [], - flatEntityToUpdate: [optimisticallyUpdatedFlatLogicFunction], - }, - }, - workspaceId, - isSystemBuild: false, - applicationUniversalIdentifier: - resolvedOwnerFlatApplication.universalIdentifier, - }, - ); - - if (validateAndBuildResult.status === 'fail') { - throw new WorkspaceMigrationBuilderException( - validateAndBuildResult, - 'Multiple validation errors occurred while updating logic function', - ); - } - - return this.getFlatLogicFunctionAfterUpdate( - optimisticallyUpdatedFlatLogicFunction.id, - workspaceId, - ); - } - - private async getFlatLogicFunctionAfterUpdate( - id: string, - workspaceId: string, - ) { - const { flatLogicFunctionMaps: recomputedExistingFlatLogicFunctionMaps } = - await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatLogicFunctionMaps'], - }, - ); - - return findFlatEntityByIdInFlatEntityMapsOrThrow({ - flatEntityId: id, - flatEntityMaps: recomputedExistingFlatLogicFunctionMaps, - }); - } - - async destroyOne({ - id, - workspaceId, - applicationId: _applicationId, - isSystemBuild = false, - ownerFlatApplication, - }: { - id: string; - workspaceId: string; - applicationId?: string; - isSystemBuild?: boolean; - ownerFlatApplication?: FlatApplication; - }): Promise { - const resolvedOwnerFlatApplication = - ownerFlatApplication ?? - ( - await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( - { workspaceId }, - ) - ).workspaceCustomFlatApplication; - - const { flatLogicFunctionMaps: existingFlatLogicFunctionMaps } = - await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatLogicFunctionMaps'], - }, - ); - - const existingFlatLogicFunction = findFlatEntityByIdInFlatEntityMaps({ - flatEntityId: id, - flatEntityMaps: existingFlatLogicFunctionMaps, - }); - - if (!isDefined(existingFlatLogicFunction)) { - throw new LogicFunctionException( - 'Logic function to destroy not found', - LogicFunctionExceptionCode.LOGIC_FUNCTION_NOT_FOUND, - ); - } - - const validateAndBuildResult = - await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( - { - allFlatEntityOperationByMetadataName: { - logicFunction: { - flatEntityToCreate: [], - flatEntityToDelete: [existingFlatLogicFunction], - flatEntityToUpdate: [], - }, - }, - workspaceId, - isSystemBuild, - applicationUniversalIdentifier: - resolvedOwnerFlatApplication.universalIdentifier, - }, - ); - - if (validateAndBuildResult.status === 'fail') { - throw new WorkspaceMigrationBuilderException( - validateAndBuildResult, - 'Multiple validation errors occurred while destroying logic function', - ); - } - - return existingFlatLogicFunction; - } - - async getAvailablePackages({ - logicFunctionId, - workspaceId, - }: { - logicFunctionId: string; - workspaceId: string; - }) { - const { flatLogicFunctionMaps, flatApplicationMaps } = - await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatLogicFunctionMaps', 'flatApplicationMaps'], - }, - ); - - const logicFunctionUniversalIdentifier = - flatLogicFunctionMaps.universalIdentifierById[logicFunctionId]; - - if (!logicFunctionUniversalIdentifier) { - return {}; - } - - const logicFunction = - flatLogicFunctionMaps.byUniversalIdentifier[ - logicFunctionUniversalIdentifier - ]; - - if (!logicFunction) { - return {}; - } - - const application = flatApplicationMaps.byId[logicFunction.applicationId]; - - return application?.availablePackages ?? {}; - } -} diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/build-universal-flat-logic-function-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/build-universal-flat-logic-function-to-create.util.ts new file mode 100644 index 0000000000..80010374eb --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/build-universal-flat-logic-function-to-create.util.ts @@ -0,0 +1,56 @@ +import { v4 } from 'uuid'; + +import { LogicFunctionRuntime } from 'src/engine/metadata-modules/logic-function/logic-function.entity'; +import { type UniversalFlatLogicFunction } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-logic-function.type'; + +type AutoGeneratedFields = 'createdAt' | 'updatedAt' | 'deletedAt' | 'runtime'; + +type RequiredFields = + | 'name' + | 'handlerName' + | 'sourceHandlerPath' + | 'builtHandlerPath' + | 'isBuildUpToDate' + | 'applicationUniversalIdentifier'; + +type OptionalFields = Exclude< + keyof UniversalFlatLogicFunction, + AutoGeneratedFields | RequiredFields +>; + +export type BuildUniversalFlatLogicFunctionToCreateInput = Pick< + UniversalFlatLogicFunction, + RequiredFields +> & + Partial> & { + id?: string; + }; + +export const buildUniversalFlatLogicFunctionToCreate = ( + input: BuildUniversalFlatLogicFunctionToCreateInput, +): UniversalFlatLogicFunction & { id: string } => { + const now = new Date().toISOString(); + + return { + id: input.id ?? v4(), + universalIdentifier: input.universalIdentifier ?? v4(), + name: input.name, + description: input.description ?? null, + runtime: LogicFunctionRuntime.NODE22, + timeoutSeconds: input.timeoutSeconds ?? 300, + checksum: input.checksum ?? null, + toolInputSchema: input.toolInputSchema ?? null, + isTool: input.isTool ?? false, + isBuildUpToDate: input.isBuildUpToDate, + handlerName: input.handlerName, + sourceHandlerPath: input.sourceHandlerPath, + builtHandlerPath: input.builtHandlerPath, + cronTriggerSettings: input.cronTriggerSettings ?? null, + databaseEventTriggerSettings: input.databaseEventTriggerSettings ?? null, + httpRouteTriggerSettings: input.httpRouteTriggerSettings ?? null, + createdAt: now, + updatedAt: now, + deletedAt: null, + applicationUniversalIdentifier: input.applicationUniversalIdentifier, + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-from-source-input-to-flat-logic-function.util.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-from-source-input-to-flat-logic-function.util.ts deleted file mode 100644 index b7aa567e86..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-from-source-input-to-flat-logic-function.util.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { v4 } from 'uuid'; - -import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; -import { LogicFunctionRuntime } from 'src/engine/metadata-modules/logic-function/logic-function.entity'; -import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type'; -import { type CreateLogicFunction } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function.input'; - -export type FromCreateLogicFunctionInputToFlatLogicFunctionArgs = { - createLogicFunctionInput: CreateLogicFunction; - workspaceId: string; - ownerFlatApplication: FlatApplication; -}; - -export const fromCreateLogicFunctionInputToFlatLogicFunction = ({ - createLogicFunctionInput: rawCreateLogicFunctionInput, - workspaceId, - ownerFlatApplication, -}: FromCreateLogicFunctionInputToFlatLogicFunctionArgs): FlatLogicFunction => { - const id = rawCreateLogicFunctionInput.id ?? v4(); - const currentDate = new Date(); - - const universalIdentifier = - rawCreateLogicFunctionInput.universalIdentifier ?? v4(); - - return { - id, - cronTriggerSettings: - rawCreateLogicFunctionInput.cronTriggerSettings ?? null, - databaseEventTriggerSettings: - rawCreateLogicFunctionInput.databaseEventTriggerSettings ?? null, - httpRouteTriggerSettings: - rawCreateLogicFunctionInput.httpRouteTriggerSettings ?? null, - name: rawCreateLogicFunctionInput.name, - description: rawCreateLogicFunctionInput.description ?? null, - sourceHandlerPath: rawCreateLogicFunctionInput.sourceHandlerPath, - handlerName: rawCreateLogicFunctionInput.handlerName, - builtHandlerPath: rawCreateLogicFunctionInput.builtHandlerPath, - universalIdentifier, - createdAt: currentDate.toISOString(), - updatedAt: currentDate.toISOString(), - deletedAt: null, - applicationId: ownerFlatApplication.id, - runtime: LogicFunctionRuntime.NODE22, - timeoutSeconds: rawCreateLogicFunctionInput.timeoutSeconds ?? 300, - workspaceId, - checksum: rawCreateLogicFunctionInput.checksum ?? null, - toolInputSchema: rawCreateLogicFunctionInput.toolInputSchema ?? null, - isTool: rawCreateLogicFunctionInput?.isTool ?? false, - isBuildUpToDate: rawCreateLogicFunctionInput.isBuildUpToDate, - applicationUniversalIdentifier: ownerFlatApplication.universalIdentifier, - }; -}; diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-from-source-input-to-universal-flat-logic-function-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-from-source-input-to-universal-flat-logic-function-to-create.util.ts new file mode 100644 index 0000000000..24a622efbe --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-from-source-input-to-universal-flat-logic-function-to-create.util.ts @@ -0,0 +1,64 @@ +import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; +import { v4 } from 'uuid'; + +import { LogicFunctionRuntime } from 'src/engine/metadata-modules/logic-function/logic-function.entity'; +import { type CreateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function-from-source.input'; +import { type UniversalFlatLogicFunction } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-logic-function.type'; + +export const fromCreateLogicFunctionFromSourceInputToUniversalFlatLogicFunctionToCreate = + ({ + createLogicFunctionFromSourceInput, + sourceHandlerPath, + builtHandlerPath, + handlerName, + checksum, + toolInputSchema, + isBuildUpToDate, + applicationUniversalIdentifier, + }: { + createLogicFunctionFromSourceInput: CreateLogicFunctionFromSourceInput; + sourceHandlerPath: string; + builtHandlerPath: string; + handlerName: string; + checksum: string | null; + toolInputSchema: object | null; + isBuildUpToDate: boolean; + applicationUniversalIdentifier: string; + }): UniversalFlatLogicFunction & { id: string } => { + const now = new Date().toISOString(); + + const { name, description } = + trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( + createLogicFunctionFromSourceInput, + ['name', 'description'], + ); + + const id = createLogicFunctionFromSourceInput.id ?? v4(); + + return { + id, + universalIdentifier: + createLogicFunctionFromSourceInput.universalIdentifier ?? v4(), + name, + description: description ?? null, + runtime: LogicFunctionRuntime.NODE22, + timeoutSeconds: createLogicFunctionFromSourceInput.timeoutSeconds ?? 300, + checksum, + toolInputSchema, + isTool: createLogicFunctionFromSourceInput.isTool ?? false, + isBuildUpToDate, + handlerName, + sourceHandlerPath, + builtHandlerPath, + cronTriggerSettings: + createLogicFunctionFromSourceInput.cronTriggerSettings ?? null, + databaseEventTriggerSettings: + createLogicFunctionFromSourceInput.databaseEventTriggerSettings ?? null, + httpRouteTriggerSettings: + createLogicFunctionFromSourceInput.httpRouteTriggerSettings ?? null, + createdAt: now, + updatedAt: now, + deletedAt: null, + applicationUniversalIdentifier, + }; + }; diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-update-logic-function-from-source-input-to-flat-logic-function-to-update.util.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-update-logic-function-from-source-input-to-flat-logic-function-to-update.util.ts new file mode 100644 index 0000000000..50db0a6678 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-update-logic-function-from-source-input-to-flat-logic-function-to-update.util.ts @@ -0,0 +1,30 @@ +import { isDefined } from 'twenty-shared/utils'; + +import { FLAT_LOGIC_FUNCTION_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/logic-function/constants/flat-logic-function-editable-properties.constant'; +import { type UpdateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input'; +import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type'; +import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util'; + +export const fromUpdateLogicFunctionFromSourceInputToFlatLogicFunctionToUpdate = + ({ + updateLogicFunctionFromSourceInput, + existingFlatLogicFunction, + }: { + updateLogicFunctionFromSourceInput: UpdateLogicFunctionFromSourceInput; + existingFlatLogicFunction: FlatLogicFunction; + }): FlatLogicFunction => { + const { sourceHandlerCode, ...metadataUpdates } = + updateLogicFunctionFromSourceInput.update; + + return { + ...mergeUpdateInExistingRecord({ + existing: existingFlatLogicFunction, + properties: [...FLAT_LOGIC_FUNCTION_EDITABLE_PROPERTIES], + update: { + ...metadataUpdates, + ...(isDefined(sourceHandlerCode) ? { isBuildUpToDate: false } : {}), + }, + }), + updatedAt: new Date().toISOString(), + }; + }; diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-update-logic-function-input-to-flat-logic-function-to-update-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-update-logic-function-input-to-flat-logic-function-to-update-or-throw.util.ts deleted file mode 100644 index aa97ef3eb9..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-update-logic-function-input-to-flat-logic-function-to-update-or-throw.util.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils'; - -import { type MetadataFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity-maps.type'; -import { FLAT_LOGIC_FUNCTION_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/logic-function/constants/flat-logic-function-editable-properties.constant'; -import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type'; -import { findFlatLogicFunctionOrThrow } from 'src/engine/metadata-modules/logic-function/utils/find-flat-logic-function-or-throw.util'; -import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util'; -import { type UpdateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input'; - -export const fromUpdateLogicFunctionInputToFlatLogicFunctionToUpdateOrThrow = ({ - updateLogicFunctionInput: rawUpdateLogicFunctionInput, - flatLogicFunctionMaps, -}: { - updateLogicFunctionInput: UpdateLogicFunctionFromSourceInput; - flatLogicFunctionMaps: MetadataFlatEntityMaps<'logicFunction'>; -}): FlatLogicFunction => { - const { id: logicFunctionToUpdateId } = - trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( - rawUpdateLogicFunctionInput, - ['id'], - ); - - const existingFlatLogicFunctionToUpdate = findFlatLogicFunctionOrThrow({ - id: logicFunctionToUpdateId, - flatLogicFunctionMaps, - }); - - return mergeUpdateInExistingRecord({ - existing: existingFlatLogicFunctionToUpdate, - properties: FLAT_LOGIC_FUNCTION_EDITABLE_PROPERTIES, - update: rawUpdateLogicFunctionInput.update, - }); -}; diff --git a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts index 7d3d635aa1..367373778e 100644 --- a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts @@ -8,7 +8,7 @@ import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-m import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { buildObjectIdByNameMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/build-object-id-by-name-maps.util'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; -import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service'; +import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; @@ -42,7 +42,7 @@ export type ObjectMetadataInfo = { export class WorkflowCommonWorkspaceService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly logicFunctionMetadataService: LogicFunctionMetadataService, + private readonly logicFunctionFromSourceService: LogicFunctionFromSourceService, private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, ) {} @@ -322,7 +322,7 @@ export class WorkflowCommonWorkspaceService { for (const workflowVersion of workflowVersions) { for (const step of workflowVersion.steps ?? []) { if (step.type === WorkflowActionType.CODE) { - await this.logicFunctionMetadataService.destroyOne({ + await this.logicFunctionFromSourceService.deleteOneWithSource({ id: step.settings.input.logicFunctionId, workspaceId, }); diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts index ab442c008c..3a6ba9cf63 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts @@ -6,8 +6,7 @@ import { AgentService } from 'src/engine/metadata-modules/ai/ai-agent/agent.serv import { createEmptyAllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-all-flat-entity-maps.constant'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; import { LogicFunctionRuntime } from 'src/engine/metadata-modules/logic-function/logic-function.entity'; -import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service'; -import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type'; +import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; @@ -25,7 +24,7 @@ const mockWorkspaceId = 'workspace-id'; describe('WorkflowVersionStepOperationsWorkspaceService', () => { let service: WorkflowVersionStepOperationsWorkspaceService; let globalWorkspaceOrmManager: jest.Mocked; - let logicFunctionMetadataService: jest.Mocked; + let logicFunctionFromSourceService: jest.Mocked; let codeStepBuildService: jest.Mocked; let agentService: jest.Mocked; let roleTargetRepository: jest.Mocked; @@ -33,7 +32,6 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { let workflowCommonWorkspaceService: jest.Mocked; let aiAgentRoleService: jest.Mocked; let workspaceCacheService: jest.Mocked; - let flatEntityMapsCacheService: jest.Mocked; beforeEach(async () => { codeStepBuildService = { @@ -89,10 +87,9 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { }), } as unknown as jest.Mocked; - logicFunctionMetadataService = { - createOne: jest.fn(), - destroyOne: jest.fn(), - } as unknown as jest.Mocked; + logicFunctionFromSourceService = { + deleteOneWithSource: jest.fn(), + } as unknown as jest.Mocked; agentService = { deleteManyAgents: jest.fn().mockResolvedValue([]), @@ -132,8 +129,8 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { useValue: globalWorkspaceOrmManager, }, { - provide: LogicFunctionMetadataService, - useValue: logicFunctionMetadataService, + provide: LogicFunctionFromSourceService, + useValue: logicFunctionFromSourceService, }, { provide: CodeStepBuildService, @@ -165,12 +162,12 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { }, { provide: WorkspaceManyOrAllFlatEntityMapsCacheService, - useValue: (flatEntityMapsCacheService = { + useValue: { flushFlatEntityMaps: jest.fn(), getOrRecomputeManyOrAllFlatEntityMaps: jest .fn() .mockResolvedValue(createEmptyAllFlatEntityMaps()), - } as unknown as jest.Mocked), + }, }, ], }).compile(); @@ -203,7 +200,9 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { workspaceId: mockWorkspaceId, }); - expect(logicFunctionMetadataService.destroyOne).toHaveBeenCalledWith({ + expect( + logicFunctionFromSourceService.deleteOneWithSource, + ).toHaveBeenCalledWith({ id: 'function-id', workspaceId: mockWorkspaceId, }); @@ -286,35 +285,6 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { describe('runStepCreationSideEffectsAndBuildStep', () => { it('should create code step with logic function', async () => { - const mockFlatLogicFunction: FlatLogicFunction = { - id: 'new-function-id', - name: 'Test Function', - description: 'Test Description', - workspaceId: mockWorkspaceId, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - deletedAt: null, - runtime: LogicFunctionRuntime.NODE22, - timeoutSeconds: 30, - sourceHandlerPath: 'src/index.ts', - builtHandlerPath: 'src/index.mjs', - handlerName: 'main', - checksum: null, - toolInputSchema: null, - isTool: false, - universalIdentifier: 'universal-id', - applicationId: 'application-id', - applicationUniversalIdentifier: 'application-universal-id', - cronTriggerSettings: null, - databaseEventTriggerSettings: null, - httpRouteTriggerSettings: null, - isBuildUpToDate: false, - }; - - logicFunctionMetadataService.createOne.mockResolvedValue( - mockFlatLogicFunction, - ); - const result = await service.runStepCreationSideEffectsAndBuildStep({ type: WorkflowActionType.CODE, workspaceId: mockWorkspaceId, @@ -360,78 +330,6 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { nextStepIds: ['next-step'], } as unknown as WorkflowAction; - const mockExistingFlatLogicFunction: FlatLogicFunction = { - id: 'function-id', - name: 'Existing Function', - description: 'Existing Description', - workspaceId: mockWorkspaceId, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - deletedAt: null, - runtime: LogicFunctionRuntime.NODE22, - timeoutSeconds: 30, - sourceHandlerPath: 'workflow/function-id/src/index.ts', - builtHandlerPath: 'workflow/function-id/src/index.mjs', - handlerName: 'main', - checksum: 'existing-checksum', - toolInputSchema: null, - isTool: false, - universalIdentifier: 'existing-universal-id', - applicationId: 'application-id', - applicationUniversalIdentifier: 'application-universal-id', - cronTriggerSettings: null, - databaseEventTriggerSettings: null, - httpRouteTriggerSettings: null, - isBuildUpToDate: true, - }; - - const mockNewFlatLogicFunction: FlatLogicFunction = { - id: 'new-function-id', - name: 'Test Function', - description: 'Test Description', - workspaceId: mockWorkspaceId, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - deletedAt: null, - runtime: LogicFunctionRuntime.NODE22, - timeoutSeconds: 30, - sourceHandlerPath: 'src/index.ts', - builtHandlerPath: 'src/index.mjs', - handlerName: 'main', - checksum: null, - toolInputSchema: null, - isTool: false, - universalIdentifier: 'universal-id', - applicationId: 'application-id', - applicationUniversalIdentifier: 'application-universal-id', - cronTriggerSettings: null, - databaseEventTriggerSettings: null, - httpRouteTriggerSettings: null, - isBuildUpToDate: false, - }; - - const emptyMaps = createEmptyAllFlatEntityMaps(); - const flatLogicFunctionMapsKey = 'flatLogicFunctionMaps' as const; - - flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps.mockResolvedValueOnce( - { - ...emptyMaps, - [flatLogicFunctionMapsKey]: { - byUniversalIdentifier: { - 'existing-universal-id': mockExistingFlatLogicFunction, - }, - universalIdentifierById: { - 'function-id': 'existing-universal-id', - }, - universalIdentifiersByApplicationId: {}, - }, - }, - ); - - logicFunctionMetadataService.createOne.mockResolvedValue( - mockNewFlatLogicFunction, - ); - const clonedStep = await service.cloneStep({ step: originalStep, workspaceId: mockWorkspaceId, diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/code-step/services/code-step-build.service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/code-step/services/code-step-build.service.ts index 42ffaaca2d..5002976319 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/code-step/services/code-step-build.service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/code-step/services/code-step-build.service.ts @@ -1,36 +1,20 @@ import { Injectable } from '@nestjs/common'; import { isDefined } from 'twenty-shared/utils'; -import { v4 } from 'uuid'; -import { - CronTriggerSettings, - DatabaseEventTriggerSettings, - HttpRouteTriggerSettings, -} from 'twenty-shared/application'; -import { ApplicationService } from 'src/engine/core-modules/application/services/application.service'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; -import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service'; -import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type'; -import { findFlatLogicFunctionOrThrow } from 'src/engine/metadata-modules/logic-function/utils/find-flat-logic-function-or-throw.util'; -import { fromCreateLogicFunctionInputToFlatLogicFunction } from 'src/engine/metadata-modules/logic-function/utils/from-create-logic-function-from-source-input-to-flat-logic-function.util'; +import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service'; import { WorkflowActionType, type WorkflowAction, } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; -import { LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service'; -import type { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type'; -import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service'; @Injectable() export class CodeStepBuildService { constructor( private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, - private readonly logicFunctionMetadataService: LogicFunctionMetadataService, private readonly logicFunctionFromSourceService: LogicFunctionFromSourceService, - private readonly applicationService: ApplicationService, - private readonly logicFunctionResourceService: LogicFunctionResourceService, ) {} async createCodeStepLogicFunction({ @@ -40,7 +24,7 @@ export class CodeStepBuildService { logicFunctionId: string; workspaceId: string; }) { - return await this.logicFunctionFromSourceService.createOne({ + return await this.logicFunctionFromSourceService.createOneFromSource({ input: { id: logicFunctionId, name: 'A Code Step', @@ -56,109 +40,11 @@ export class CodeStepBuildService { }: { existingLogicFunctionId: string; workspaceId: string; - }): Promise { - const { flatLogicFunctionMaps } = - await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatLogicFunctionMaps'], - }, - ); - - const existingLogicFunction = findFlatLogicFunctionOrThrow({ - id: existingLogicFunctionId, - flatLogicFunctionMaps, - }); - - const resolvedOwnerFlatApplication = ( - await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( - { workspaceId }, - ) - ).workspaceCustomFlatApplication; - - const applicationUniversalIdentifier = - resolvedOwnerFlatApplication.universalIdentifier; - - const newId = v4(); - - const newUniversalIdentifier = v4(); - - const { sourceHandlerPath, builtHandlerPath } = existingLogicFunction; - - const toSourceHandlerPath = sourceHandlerPath.replace( - existingLogicFunction.id, - newId, - ); - const toBuiltHandlerPath = builtHandlerPath.replace( - existingLogicFunction.id, - newId, - ); - - await this.logicFunctionResourceService.copyResources({ - fromSourceHandlerPath: sourceHandlerPath, - toSourceHandlerPath, - fromBuiltHandlerPath: builtHandlerPath, - toBuiltHandlerPath: toBuiltHandlerPath, + }): Promise<{ id: string }> { + return this.logicFunctionFromSourceService.duplicateOneWithSource({ + existingLogicFunctionId, workspaceId, - applicationUniversalIdentifier, }); - - const newFlatLogicFunction = - fromCreateLogicFunctionInputToFlatLogicFunction({ - createLogicFunctionInput: { - ...existingLogicFunction, - id: newId, - universalIdentifier: newUniversalIdentifier, - description: existingLogicFunction.description ?? undefined, - sourceHandlerPath: toSourceHandlerPath, - builtHandlerPath: toBuiltHandlerPath, - toolInputSchema: existingLogicFunction.toolInputSchema ?? {}, - checksum: existingLogicFunction.checksum ?? '[default-checksum]', // TODO: checksum should never be null, update column in logicFunction entity to set it non nullable - cronTriggerSettings: existingLogicFunction.cronTriggerSettings as - | JsonbProperty - | undefined, - databaseEventTriggerSettings: - existingLogicFunction.databaseEventTriggerSettings as - | JsonbProperty - | undefined, - httpRouteTriggerSettings: - existingLogicFunction.httpRouteTriggerSettings as - | JsonbProperty - | undefined, - }, - workspaceId, - ownerFlatApplication: resolvedOwnerFlatApplication, - }); - - const created = await this.logicFunctionMetadataService.createOne({ - input: { - ...newFlatLogicFunction, - description: newFlatLogicFunction.description ?? undefined, - checksum: newFlatLogicFunction.checksum ?? '[default-checksum]', - toolInputSchema: newFlatLogicFunction.toolInputSchema ?? {}, - cronTriggerSettings: newFlatLogicFunction.cronTriggerSettings as - | JsonbProperty - | undefined, - databaseEventTriggerSettings: - newFlatLogicFunction.databaseEventTriggerSettings as - | JsonbProperty - | undefined, - httpRouteTriggerSettings: - newFlatLogicFunction.httpRouteTriggerSettings as - | JsonbProperty - | undefined, - }, - workspaceId, - ownerFlatApplication: resolvedOwnerFlatApplication, - }); - - if (!isDefined(created)) { - throw new Error( - 'Failed to create logic function when duplicating code step', - ); - } - - return created; } async buildCodeStepsFromSourceForSteps({ diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts index abfab6a2aa..e3df157088 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts @@ -21,7 +21,7 @@ import { AiAgentRoleService } from 'src/engine/metadata-modules/ai/ai-agent-role import { AgentService } from 'src/engine/metadata-modules/ai/ai-agent/agent.service'; import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; -import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service'; +import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service'; import { findFlatLogicFunctionOrThrow } from 'src/engine/metadata-modules/logic-function/utils/find-flat-logic-function-or-throw.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; @@ -65,7 +65,7 @@ const ITERATOR_EMPTY_STEP_POSITION_OFFSET = { export class WorkflowVersionStepOperationsWorkspaceService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly logicFunctionMetadataService: LogicFunctionMetadataService, + private readonly logicFunctionFromSourceService: LogicFunctionFromSourceService, private readonly codeStepBuildService: CodeStepBuildService, private readonly agentService: AgentService, @InjectRepository(RoleTargetEntity) @@ -87,7 +87,7 @@ export class WorkflowVersionStepOperationsWorkspaceService { }) { switch (step.type) { case WorkflowActionType.CODE: { - await this.logicFunctionMetadataService.destroyOne({ + await this.logicFunctionFromSourceService.deleteOneWithSource({ id: step.settings.input.logicFunctionId, workspaceId, }); diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/update-logic-function-source.tool.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/update-logic-function-source.tool.ts index 02849fe8f1..18760b0e7d 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/update-logic-function-source.tool.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/update-logic-function-source.tool.ts @@ -50,10 +50,12 @@ To find the logicFunctionId, look at the code step's settings.input.logicFunctio const { logicFunctionId, code } = parameters; const { workspaceId } = context; - await deps.logicFunctionFromSourceService.updateOne({ - id: logicFunctionId, - update: { - sourceHandlerCode: code, + await deps.logicFunctionFromSourceService.updateOneFromSource({ + updateLogicFunctionFromSourceInput: { + id: logicFunctionId, + update: { + sourceHandlerCode: code, + }, }, workspaceId, });