Reorganize logic function files (#17766)

reorganize according to

<img width="1243" height="725" alt="Pasted Graphic"
src="https://github.com/user-attachments/assets/ba65dd10-8eec-4b13-ad49-9726edd3b79c"
/>

Not working yet
This commit is contained in:
martmull
2026-02-09 12:36:39 +01:00
committed by GitHub
parent bf4c348c8b
commit 9162685b2e
149 changed files with 1715 additions and 5272 deletions
@@ -1,5 +1,3 @@
// Default tool input schema matching the seed-project template
// Template params: { a: string; b: number; }
export const DEFAULT_TOOL_INPUT_SCHEMA = {
type: 'object',
properties: {
@@ -5,9 +5,11 @@ export const FLAT_LOGIC_FUNCTION_EDITABLE_PROPERTIES = [
'description',
'timeoutSeconds',
'checksum',
'code',
'sourceHandlerPath',
'handlerName',
'toolInputSchema',
'isTool',
'cronTriggerSettings',
'databaseEventTriggerSettings',
'httpRouteTriggerSettings',
] as const satisfies (keyof FlatLogicFunction)[];
@@ -0,0 +1,52 @@
import { Field, HideField, InputType } from '@nestjs/graphql';
import {
IsBoolean,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsString,
Max,
Min,
} from 'class-validator';
import graphqlTypeJson from 'graphql-type-json';
@InputType()
export class CreateDefaultLogicFunctionInput {
@IsString()
@IsNotEmpty()
@Field()
name: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
description?: string;
@IsNumber()
@Field({ nullable: true })
@Min(1)
@Max(900)
@IsOptional()
timeoutSeconds?: number;
@HideField()
applicationId?: string;
@HideField()
universalIdentifier?: string;
@HideField()
id?: string;
@Field(() => graphqlTypeJson, { nullable: true })
@IsObject()
@IsOptional()
toolInputSchema?: object;
@IsBoolean()
@Field({ nullable: true })
@IsOptional()
isTool?: boolean;
}
@@ -11,7 +11,13 @@ import {
Min,
} from 'class-validator';
import graphqlTypeJson from 'graphql-type-json';
import { Sources } from 'twenty-shared/types';
import {
CronTriggerSettings,
DatabaseEventTriggerSettings,
HttpRouteTriggerSettings,
} from 'twenty-shared/application';
import type { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
@InputType()
export class CreateLogicFunctionInput {
@@ -33,36 +39,28 @@ export class CreateLogicFunctionInput {
timeoutSeconds?: number;
@HideField()
applicationId?: string;
applicationId: string;
@HideField()
universalIdentifier?: string;
@HideField()
id?: string;
id: string;
@HideField()
checksum?: string;
@Field(() => graphqlTypeJson, { nullable: true })
@IsObject()
@IsOptional()
code?: Sources;
checksum: string;
@IsString()
@Field({ nullable: true })
@IsOptional()
handlerName?: string;
@Field({ nullable: false })
handlerName: string;
@IsString()
@Field({ nullable: true })
@IsOptional()
sourceHandlerPath?: string;
@Field({ nullable: false })
sourceHandlerPath: string;
@IsString()
@Field({ nullable: true })
@IsOptional()
builtHandlerPath?: string;
@Field({ nullable: false })
builtHandlerPath: string;
@Field(() => graphqlTypeJson, { nullable: true })
@IsObject()
@@ -73,4 +71,19 @@ export class CreateLogicFunctionInput {
@Field({ nullable: true })
@IsOptional()
isTool?: boolean;
@IsObject()
@Field(() => graphqlTypeJson, { nullable: true })
@IsOptional()
cronTriggerSettings?: JsonbProperty<CronTriggerSettings>;
@IsObject()
@Field(() => graphqlTypeJson, { nullable: true })
@IsOptional()
databaseEventTriggerSettings?: JsonbProperty<DatabaseEventTriggerSettings>;
@IsObject()
@Field(() => graphqlTypeJson, { nullable: true })
@IsOptional()
httpRouteTriggerSettings?: JsonbProperty<HttpRouteTriggerSettings>;
}
@@ -1,9 +0,0 @@
import { ID, InputType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
@InputType()
export class GetLogicFunctionSourceCodeInput {
@IDField(() => ID, { description: 'The id of the function.' })
id!: string;
}
@@ -16,13 +16,13 @@ import {
IsUUID,
} from 'class-validator';
import graphqlTypeJson from 'graphql-type-json';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import {
CronTriggerSettings,
DatabaseEventTriggerSettings,
HttpRouteTriggerSettings,
} from 'src/engine/metadata-modules/logic-function/logic-function.entity';
} from 'twenty-shared/application';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@ObjectType('LogicFunction')
@Authorize({
@@ -14,10 +14,14 @@ import {
ValidateNested,
} from 'class-validator';
import graphqlTypeJson from 'graphql-type-json';
import type { Sources } from 'twenty-shared/types';
import {
CronTriggerSettings,
DatabaseEventTriggerSettings,
HttpRouteTriggerSettings,
} from 'twenty-shared/application';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import type { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
@InputType()
class UpdateLogicFunctionInputUpdates {
@@ -38,11 +42,6 @@ class UpdateLogicFunctionInputUpdates {
@IsOptional()
timeoutSeconds?: number;
@Field(() => graphqlTypeJson, { nullable: true })
@IsObject()
@IsOptional()
code?: Sources;
@IsString()
@Field({ nullable: true })
@IsOptional()
@@ -67,10 +66,30 @@ class UpdateLogicFunctionInputUpdates {
@Field({ nullable: true })
@IsOptional()
isTool?: boolean;
@IsString()
@Field({ nullable: true })
@IsOptional()
checksum?: string;
@IsObject()
@Field(() => graphqlTypeJson, { nullable: true })
@IsOptional()
cronTriggerSettings?: JsonbProperty<CronTriggerSettings>;
@IsObject()
@Field(() => graphqlTypeJson, { nullable: true })
@IsOptional()
databaseEventTriggerSettings?: JsonbProperty<DatabaseEventTriggerSettings>;
@IsObject()
@Field(() => graphqlTypeJson, { nullable: true })
@IsOptional()
httpRouteTriggerSettings?: JsonbProperty<HttpRouteTriggerSettings>;
}
@InputType()
export class UpdateLogicFunctionInput {
export class UpdateLogicFunctionSourceInput {
@Field(() => UUIDScalarType, {
description: 'Id of the logic function to update',
})
@@ -1,4 +1,3 @@
import { HTTPMethod } from 'twenty-shared/types';
import {
Check,
Column,
@@ -9,26 +8,15 @@ import {
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import {
CronTriggerSettings,
DatabaseEventTriggerSettings,
HttpRouteTriggerSettings,
} from 'twenty-shared/application';
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
import { type JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
export type CronTriggerSettings = {
pattern: string;
};
export type DatabaseEventTriggerSettings = {
eventName: string;
updatedFields?: string[];
};
export type HttpRouteTriggerSettings = {
path: string;
httpMethod: HTTPMethod;
isAuthRequired: boolean;
forwardedRequestHeaders?: string[];
};
const DEFAULT_LOGIC_FUNCTION_TIMEOUT_SECONDS = 300; // 5 minutes
export enum LogicFunctionRuntime {
@@ -10,48 +10,44 @@ import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
import { FileModule } from 'src/engine/core-modules/file/file.module';
import { LogicFunctionExecutorModule } from 'src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.module';
import { LogicFunctionSourceBuilderModule } from 'src/engine/core-modules/logic-function/logic-function-source-builder/logic-function-source-builder.module';
import { LogicFunctionResourceModule } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.module';
import { SecretEncryptionModule } from 'src/engine/core-modules/secret-encryption/secret-encryption.module';
import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
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 { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/services/logic-function.service';
import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.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';
import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
import { ApplicationLayerModule } from 'src/engine/core-modules/application-layer/application-layer.module';
@Module({
imports: [
FileUploadModule,
NestjsQueryTypeOrmModule.forFeature([LogicFunctionEntity]),
TypeOrmModule.forFeature([ApplicationEntity, FeatureFlagEntity]),
FileModule,
ThrottlerModule,
ApplicationModule,
ApplicationLayerModule,
AuditModule,
FeatureFlagModule,
PermissionsModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
WorkspaceMigrationModule,
LogicFunctionLayerModule,
LogicFunctionExecutorModule,
LogicFunctionSourceBuilderModule,
LogicFunctionResourceModule,
SubscriptionsModule,
WorkspaceCacheModule,
TokenModule,
SecretEncryptionModule,
],
providers: [
LogicFunctionService,
LogicFunctionMetadataService,
LogicFunctionResolver,
WorkspaceFlatLogicFunctionMapCacheService,
],
exports: [LogicFunctionService, LogicFunctionExecutorModule],
exports: [LogicFunctionMetadataService],
})
export class LogicFunctionModule {}
@@ -4,34 +4,35 @@ import { Args, Mutation, Query, Resolver, Subscription } from '@nestjs/graphql';
import graphqlTypeJson from 'graphql-type-json';
import { PermissionFlagType } from 'twenty-shared/constants';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
import { LogicFunctionExecutorService } from 'src/engine/core-modules/logic-function/logic-function-executor/services/logic-function-executor.service';
import { LogicFunctionSourceBuilderService } from 'src/engine/core-modules/logic-function/logic-function-source-builder/logic-function-source-builder.service';
import { LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { FeatureFlagGuard } from 'src/engine/guards/feature-flag.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 { CreateLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function.input';
import { ExecuteOneLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/execute-logic-function.input';
import { GetLogicFunctionSourceCodeInput } from 'src/engine/metadata-modules/logic-function/dtos/get-logic-function-source-code.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 { UpdateLogicFunctionSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-source.input';
import { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/services/logic-function.service';
import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service';
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 { 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 { LogicFunctionExecutorService } from 'src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service';
import { ApplicationLayerService } from 'src/engine/core-modules/application-layer/application-layer.service';
import { CreateDefaultLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.input';
@UseGuards(
WorkspaceAuthGuard,
@@ -43,44 +44,15 @@ import { SubscriptionService } from 'src/engine/subscriptions/subscription.servi
@UseFilters(PreventNestToAutoLogGraphqlErrorsFilter)
export class LogicFunctionResolver {
constructor(
private readonly logicFunctionService: LogicFunctionService,
private readonly logicFunctionExecutorService: LogicFunctionExecutorService,
private readonly logicFunctionSourceBuilderService: LogicFunctionSourceBuilderService,
private readonly logicFunctionMetadataService: LogicFunctionMetadataService,
private readonly logicFunctionResourceService: LogicFunctionResourceService,
private readonly applicationService: ApplicationService,
private readonly applicationLayerService: ApplicationLayerService,
private readonly subscriptionService: SubscriptionService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
) {}
private async getLogicFunctionContext({
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,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
};
}
@Query(() => LogicFunctionDTO)
async findOneLogicFunction(
@Args('input') { id }: LogicFunctionIdInput,
@@ -138,9 +110,15 @@ export class LogicFunctionResolver {
}
@Query(() => graphqlTypeJson)
async getAvailablePackages(@Args('input') { id }: LogicFunctionIdInput) {
async getAvailablePackages(
@Args('input') { id }: LogicFunctionIdInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
) {
try {
return await this.logicFunctionExecutorService.getAvailablePackages(id);
return await this.applicationLayerService.getAvailablePackages({
logicFunctionId: id,
workspaceId,
});
} catch (error) {
return logicFunctionGraphQLApiExceptionHandler(error);
}
@@ -153,10 +131,11 @@ export class LogicFunctionResolver {
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<LogicFunctionDTO> {
try {
const flatLogicFunction = await this.logicFunctionService.destroyOne({
id: input.id,
workspaceId,
});
const flatLogicFunction =
await this.logicFunctionMetadataService.destroyOne({
id: input.id,
workspaceId,
});
return fromFlatLogicFunctionToLogicFunctionDto({
flatLogicFunction,
@@ -168,16 +147,42 @@ export class LogicFunctionResolver {
@Mutation(() => LogicFunctionDTO)
@UseGuards(SettingsPermissionGuard(PermissionFlagType.WORKFLOWS))
async createOneLogicFunction(
async createDefaultLogicFunction(
@Args('input')
input: CreateLogicFunctionInput,
input: CreateDefaultLogicFunctionInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<LogicFunctionDTO> {
try {
const flatLogicFunction = await this.logicFunctionService.createOne({
input,
workspaceId,
});
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const logicFunctionId = input.id ?? v4();
const { sourceHandlerPath, builtHandlerPath, handlerName, checksum } =
await this.logicFunctionResourceService.seedSourceFiles({
sourceSubfolder: logicFunctionId,
workspaceId,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
});
const flatLogicFunction =
await this.logicFunctionMetadataService.createOne({
input: {
...input,
id: logicFunctionId,
sourceHandlerPath,
builtHandlerPath,
handlerName,
checksum,
},
workspaceId,
ownerFlatApplication: workspaceCustomFlatApplication,
});
return fromFlatLogicFunctionToLogicFunctionDto({
flatLogicFunction,
@@ -193,32 +198,32 @@ export class LogicFunctionResolver {
@Args('input') { id, payload, forceRebuild }: ExecuteOneLogicFunctionInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<LogicFunctionExecutionResultDTO> {
// TODO: remove forceRebuild parameter and add a column called shouldRebuild or isBuiltUpToDate
try {
if (forceRebuild) {
const { flatLogicFunction, applicationUniversalIdentifier } =
await this.getLogicFunctionContext({ id, workspaceId });
const { checksum } =
await this.logicFunctionSourceBuilderService.buildFromSource({
await this.logicFunctionResourceService.buildFromSource({
workspaceId,
applicationUniversalIdentifier,
sourceHandlerPath: flatLogicFunction.sourceHandlerPath,
builtHandlerPath: flatLogicFunction.builtHandlerPath,
});
await this.logicFunctionService.updateChecksum({
await this.logicFunctionMetadataService.updateChecksum({
id,
checksum,
workspaceId,
});
}
const result =
await this.logicFunctionExecutorService.executeOneLogicFunction({
id,
workspaceId,
payload,
});
const result = await this.logicFunctionExecutorService.execute({
logicFunctionId: id,
workspaceId,
payload,
});
return {
data: result.data as LogicFunctionExecutionResultDTO['data'],
@@ -242,14 +247,14 @@ export class LogicFunctionResolver {
@Query(() => graphqlTypeJson, { nullable: true })
async getLogicFunctionSourceCode(
@Args('input') { id }: GetLogicFunctionSourceCodeInput,
@Args('input') { id }: LogicFunctionIdInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
) {
try {
const { flatLogicFunction, applicationUniversalIdentifier } =
await this.getLogicFunctionContext({ id, workspaceId });
return this.logicFunctionSourceBuilderService.getSourceCode({
return this.logicFunctionResourceService.getSourceCode({
workspaceId,
applicationUniversalIdentifier,
sourceHandlerPath: flatLogicFunction.sourceHandlerPath,
@@ -269,7 +274,7 @@ export class LogicFunctionResolver {
const { flatLogicFunction, applicationUniversalIdentifier } =
await this.getLogicFunctionContext({ id, workspaceId });
await this.logicFunctionSourceBuilderService.updateSourceFiles({
await this.logicFunctionResourceService.updateSourceFiles({
workspaceId,
applicationUniversalIdentifier,
sourceHandlerPath: flatLogicFunction.sourceHandlerPath,
@@ -325,4 +330,34 @@ export class LogicFunctionResolver {
workspaceId: workspace.id,
});
}
private async getLogicFunctionContext({
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,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
};
}
}
@@ -4,13 +4,11 @@ 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 { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { LogicFunctionSourceBuilderService } from 'src/engine/core-modules/logic-function/logic-function-source-builder/logic-function-source-builder.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import type { CreateLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function.input';
import type { UpdateLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function.input';
import type { UpdateLogicFunctionSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function.input';
import {
LogicFunctionException,
LogicFunctionExceptionCode,
@@ -19,19 +17,15 @@ import { FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/ty
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-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 { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
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 LogicFunctionService {
export class LogicFunctionMetadataService {
constructor(
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly applicationService: ApplicationService,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly fileStorageService: FileStorageService,
private readonly logicFunctionSourceBuilderService: LogicFunctionSourceBuilderService,
) {}
async createOne({
@@ -40,9 +34,8 @@ export class LogicFunctionService {
ownerFlatApplication,
}: {
input: Omit<CreateLogicFunctionInput, 'applicationId'>;
ownerFlatApplication?: FlatApplication;
ownerFlatApplication: FlatApplication;
workspaceId: string;
applicationId?: string;
}) {
const resolvedOwnerFlatApplication =
ownerFlatApplication ??
@@ -52,7 +45,6 @@ export class LogicFunctionService {
)
).workspaceCustomFlatApplication;
// Generate flat logic function to get the ID
const flatLogicFunctionToCreate =
fromCreateLogicFunctionInputToFlatLogicFunction({
createLogicFunctionInput: input,
@@ -60,21 +52,6 @@ export class LogicFunctionService {
ownerFlatApplication: resolvedOwnerFlatApplication,
});
// Seed the source files (uses provided code or default seed project)
const { sourceHandlerPath, builtHandlerPath, checksum } =
await this.logicFunctionSourceBuilderService.seedSourceFiles({
logicFunctionId: flatLogicFunctionToCreate.id,
workspaceId,
applicationUniversalIdentifier:
resolvedOwnerFlatApplication.universalIdentifier,
code: input.code,
});
// Update paths and checksum with actual values from seeding
flatLogicFunctionToCreate.sourceHandlerPath = sourceHandlerPath;
flatLogicFunctionToCreate.builtHandlerPath = builtHandlerPath;
flatLogicFunctionToCreate.checksum = checksum;
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
@@ -109,7 +86,7 @@ export class LogicFunctionService {
ownerFlatApplication,
}: {
id: string;
update: UpdateLogicFunctionInput['update'];
update: UpdateLogicFunctionSourceInput['update'];
workspaceId: string;
ownerFlatApplication?: FlatApplication;
}) {
@@ -1,22 +1,15 @@
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { DEFAULT_TOOL_INPUT_SCHEMA } from 'src/engine/metadata-modules/logic-function/constants/default-tool-input-schema.constant';
import { type CreateLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function.input';
import {
DEFAULT_BUILT_HANDLER_PATH,
DEFAULT_HANDLER_NAME,
DEFAULT_SOURCE_HANDLER_PATH,
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 { logicFunctionCreateHash } from 'src/engine/metadata-modules/logic-function/utils/logic-function-create-hash.utils';
const WORKFLOW_BASE_FOLDER_PREFIX = 'workflow';
export type FromCreateLogicFunctionInputToFlatLogicFunctionArgs = {
createLogicFunctionInput: CreateLogicFunctionInput;
createLogicFunctionInput: Omit<CreateLogicFunctionInput, 'applicationId'>;
workspaceId: string;
ownerFlatApplication: FlatApplication;
};
@@ -26,34 +19,25 @@ export const fromCreateLogicFunctionInputToFlatLogicFunction = ({
workspaceId,
ownerFlatApplication,
}: FromCreateLogicFunctionInputToFlatLogicFunctionArgs): FlatLogicFunction => {
const id = rawCreateLogicFunctionInput.id ?? v4();
const id = rawCreateLogicFunctionInput.id;
const currentDate = new Date();
// Build full paths including the base folder
const baseFolder = `${WORKFLOW_BASE_FOLDER_PREFIX}/${id}`;
const sourceHandlerPath =
rawCreateLogicFunctionInput.sourceHandlerPath ??
`${baseFolder}/${DEFAULT_SOURCE_HANDLER_PATH}`;
const builtHandlerPath =
rawCreateLogicFunctionInput.builtHandlerPath ??
`${baseFolder}/${DEFAULT_BUILT_HANDLER_PATH}`;
const sourceHandlerPath = rawCreateLogicFunctionInput.sourceHandlerPath;
const builtHandlerPath = rawCreateLogicFunctionInput.builtHandlerPath;
const universalIdentifier =
rawCreateLogicFunctionInput.universalIdentifier ?? v4();
const checksum = isDefined(rawCreateLogicFunctionInput.checksum)
? rawCreateLogicFunctionInput.checksum
: rawCreateLogicFunctionInput?.code
? logicFunctionCreateHash(
JSON.stringify(rawCreateLogicFunctionInput.code),
)
: null;
const checksum = rawCreateLogicFunctionInput.checksum;
return {
id,
cronTriggerSettings: null,
databaseEventTriggerSettings: null,
httpRouteTriggerSettings: null,
cronTriggerSettings:
rawCreateLogicFunctionInput.cronTriggerSettings ?? null,
databaseEventTriggerSettings:
rawCreateLogicFunctionInput.databaseEventTriggerSettings ?? null,
httpRouteTriggerSettings:
rawCreateLogicFunctionInput.httpRouteTriggerSettings ?? null,
name: rawCreateLogicFunctionInput.name,
description: rawCreateLogicFunctionInput.description ?? null,
sourceHandlerPath,
@@ -68,15 +52,8 @@ export const fromCreateLogicFunctionInputToFlatLogicFunction = ({
runtime: LogicFunctionRuntime.NODE22,
timeoutSeconds: rawCreateLogicFunctionInput.timeoutSeconds ?? 300,
workspaceId,
code: rawCreateLogicFunctionInput?.code,
checksum,
// If no schema provided and no code provided, use default schema
// (because the default template will be used)
toolInputSchema: isDefined(rawCreateLogicFunctionInput?.toolInputSchema)
? rawCreateLogicFunctionInput.toolInputSchema
: !isDefined(rawCreateLogicFunctionInput?.code)
? DEFAULT_TOOL_INPUT_SCHEMA
: null,
toolInputSchema: rawCreateLogicFunctionInput.toolInputSchema ?? null,
isTool: rawCreateLogicFunctionInput?.isTool ?? false,
applicationUniversalIdentifier: ownerFlatApplication.universalIdentifier,
};
@@ -1,22 +1,17 @@
import {
extractAndSanitizeObjectStringFields,
isDefined,
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
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 UpdateLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function.input';
import { type UpdateLogicFunctionSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function.input';
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 { logicFunctionCreateHash } from 'src/engine/metadata-modules/logic-function/utils/logic-function-create-hash.utils';
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
export const fromUpdateLogicFunctionInputToFlatLogicFunctionToUpdateOrThrow = ({
updateLogicFunctionInput: rawUpdateLogicFunctionInput,
flatLogicFunctionMaps,
}: {
updateLogicFunctionInput: UpdateLogicFunctionInput;
updateLogicFunctionInput: UpdateLogicFunctionSourceInput;
flatLogicFunctionMaps: MetadataFlatEntityMaps<'logicFunction'>;
}): FlatLogicFunction => {
const { id: logicFunctionToUpdateId } =
@@ -30,23 +25,9 @@ export const fromUpdateLogicFunctionInputToFlatLogicFunctionToUpdateOrThrow = ({
flatLogicFunctionMaps,
});
const update = rawUpdateLogicFunctionInput.update;
const updatedEditableFieldProperties = {
...extractAndSanitizeObjectStringFields(
{
...update,
...(isDefined(update.code) && {
checksum: logicFunctionCreateHash(JSON.stringify(update.code)),
}),
},
FLAT_LOGIC_FUNCTION_EDITABLE_PROPERTIES,
),
...(isDefined(update.code) && { code: update.code }),
};
return mergeUpdateInExistingRecord({
existing: existingFlatLogicFunctionToUpdate,
properties: FLAT_LOGIC_FUNCTION_EDITABLE_PROPERTIES,
update: updatedEditableFieldProperties,
update: rawUpdateLogicFunctionInput.update,
});
};
@@ -1,5 +1,6 @@
import { createHash } from 'crypto';
// TODO: migrate to twenty-shared and spread everywhere
export const logicFunctionCreateHash = (fileContent: string) => {
return createHash('sha512')
.update(fileContent)