-12
@@ -1,12 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ApplicationLayerService } from 'src/engine/core-modules/application-layer/application-layer.service';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
|
||||
@Module({
|
||||
imports: [WorkspaceCacheModule, FileModule],
|
||||
providers: [ApplicationLayerService],
|
||||
exports: [ApplicationLayerService],
|
||||
})
|
||||
export class ApplicationLayerModule {}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationLayerService {
|
||||
constructor(
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
) {}
|
||||
|
||||
async getAvailablePackages({
|
||||
logicFunctionId,
|
||||
workspaceId,
|
||||
}: {
|
||||
logicFunctionId: string;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
const { flatLogicFunctionMaps, flatApplicationMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'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 ?? {};
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -6,6 +6,6 @@ export const SEED_DEPENDENCIES_DIRNAME = path.resolve(
|
||||
__dirname,
|
||||
path.join(
|
||||
ASSET_PATH,
|
||||
'engine/core-modules/application-layer/constants/seed-dependencies',
|
||||
'engine/core-modules/application/constants/seed-dependencies',
|
||||
),
|
||||
);
|
||||
+1
-1
@@ -6,6 +6,6 @@ export const YARN_ENGINE_DIRNAME = path.resolve(
|
||||
__dirname,
|
||||
path.join(
|
||||
ASSET_PATH,
|
||||
'engine/core-modules/application-layer/constants/yarn-engine',
|
||||
'engine/core-modules/application/constants/yarn-engine',
|
||||
),
|
||||
);
|
||||
+1
-1
@@ -4,7 +4,6 @@ import { FileFolder } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { PackageJson } from 'type-fest';
|
||||
|
||||
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application-layer/utils/get-default-application-package-fields.util';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import {
|
||||
ApplicationException,
|
||||
@@ -15,6 +14,7 @@ import { ApplicationInput } from 'src/engine/core-modules/application/dtos/appli
|
||||
import { ApplicationManifestMigrationService } from 'src/engine/core-modules/application/services/application-manifest-migration.service';
|
||||
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 { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application/utils/get-default-application-package-fields.util';
|
||||
import { getEmptyApplicationManifestAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-empty-application-manifest-all-universal-flat-entity-maps.util';
|
||||
import { getSubApplicationFromToAllFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-sub-application-from-to-all-flat-entity-maps.util';
|
||||
import { ApplicationVariableEntityService } from 'src/engine/core-modules/applicationVariable/application-variable.service';
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@ import {
|
||||
ApplicationException,
|
||||
ApplicationExceptionCode,
|
||||
} from 'src/engine/core-modules/application/application.exception';
|
||||
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application-layer/utils/get-default-application-package-fields.util';
|
||||
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application-layer/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
|
||||
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application/utils/get-default-application-package-fields.util';
|
||||
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { ALL_FLAT_ENTITY_MAPS_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-flat-entity-maps-properties.constant';
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { promises as fs, statSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import { YARN_ENGINE_DIRNAME } from 'src/engine/core-modules/application-layer/constants/yarn-engine-dirname';
|
||||
import { YARN_ENGINE_DIRNAME } from 'src/engine/core-modules/application/constants/yarn-engine-dirname';
|
||||
|
||||
const execFilePromise = promisify(execFile);
|
||||
|
||||
+1
@@ -35,6 +35,7 @@ export const fromLogicFunctionManifestToUniversalFlatLogicFunction = ({
|
||||
logicFunctionManifest.databaseEventTriggerSettings ?? null,
|
||||
httpRouteTriggerSettings:
|
||||
logicFunctionManifest.httpRouteTriggerSettings ?? null,
|
||||
isBuildUpToDate: true,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { readFile } from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application-layer/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
|
||||
import { SEED_DEPENDENCIES_DIRNAME } from 'src/engine/core-modules/application-layer/constants/seed-dependencies-dirname';
|
||||
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
|
||||
import { SEED_DEPENDENCIES_DIRNAME } from 'src/engine/core-modules/application/constants/seed-dependencies-dirname';
|
||||
|
||||
// To regenerate: use logicFunctionCreateHash from logic-function-create-hash.utils.
|
||||
// package.json: hash(JSON.stringify(JSON.parse(content))). yarn.lock: hash(content).
|
||||
+1
-1
@@ -314,7 +314,7 @@ export class CacheStorageService {
|
||||
const formattedKey = `${this.namespace}:${key}`;
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return `integration-tests:${formattedKey}`;
|
||||
return `${CacheStorageNamespace.IntegrationTests}:${formattedKey}`;
|
||||
}
|
||||
|
||||
return formattedKey;
|
||||
|
||||
+1
@@ -7,4 +7,5 @@ export enum CacheStorageNamespace {
|
||||
EngineHealth = 'engine:health',
|
||||
EngineMetrics = 'engine:metrics',
|
||||
EngineSubscriptions = 'engine:subscriptions',
|
||||
IntegrationTests = 'integration-tests',
|
||||
}
|
||||
|
||||
@@ -66,7 +66,6 @@ import { TrashCleanupModule } from 'src/engine/trash-cleanup/trash-cleanup.modul
|
||||
import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module';
|
||||
import { ChannelSyncModule } from 'src/modules/connected-account/channel-sync/channel-sync.module';
|
||||
import { DashboardModule } from 'src/modules/dashboard/dashboard.module';
|
||||
import { ApplicationLayerModule } from 'src/engine/core-modules/application-layer/application-layer.module';
|
||||
import { LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service';
|
||||
|
||||
import { AuditModule } from './audit/audit.module';
|
||||
@@ -89,7 +88,6 @@ import { FileModule } from './file/file.module';
|
||||
RowLevelPermissionModule,
|
||||
OpenApiModule,
|
||||
ApplicationModule,
|
||||
ApplicationLayerModule,
|
||||
ApplicationSyncModule,
|
||||
AppTokenModule,
|
||||
TimelineMessagingModule,
|
||||
|
||||
+16
-109
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { mkdir, readdir, readFile, stat } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import { basename, dirname, join } from 'path';
|
||||
import { type Readable } from 'stream';
|
||||
|
||||
import { isObject } from '@sniptt/guards';
|
||||
@@ -197,45 +197,6 @@ export class FileStorageService {
|
||||
return sources;
|
||||
}
|
||||
|
||||
async readFolder(params: ResourceIdentifier): Promise<Sources> {
|
||||
const driver = this.fileStorageDriverFactory.getCurrentDriver();
|
||||
const onStoragePath = this.buildOnStoragePath(params);
|
||||
const tempDir = `/tmp/twenty-read-folder-${Date.now()}`;
|
||||
|
||||
await mkdir(tempDir, { recursive: true });
|
||||
|
||||
await driver.downloadFolder({
|
||||
onStoragePath,
|
||||
localPath: tempDir,
|
||||
});
|
||||
|
||||
return this.readLocalFolderToSources(tempDir);
|
||||
}
|
||||
|
||||
uploadFolder(
|
||||
params: ResourceIdentifier & { localPath: string },
|
||||
): Promise<void> {
|
||||
const driver = this.fileStorageDriverFactory.getCurrentDriver();
|
||||
const onStoragePath = this.buildOnStoragePath(params);
|
||||
|
||||
return driver.uploadFolder({
|
||||
localPath: params.localPath,
|
||||
onStoragePath,
|
||||
});
|
||||
}
|
||||
|
||||
downloadFolder(
|
||||
params: ResourceIdentifier & { localPath: string },
|
||||
): Promise<void> {
|
||||
const driver = this.fileStorageDriverFactory.getCurrentDriver();
|
||||
const onStoragePath = this.buildOnStoragePath(params);
|
||||
|
||||
return driver.downloadFolder({
|
||||
onStoragePath,
|
||||
localPath: params.localPath,
|
||||
});
|
||||
}
|
||||
|
||||
downloadFile(
|
||||
params: ResourceIdentifier & { localPath: string },
|
||||
): Promise<void> {
|
||||
@@ -339,7 +300,7 @@ export class FileStorageService {
|
||||
return driver.copy(params);
|
||||
}
|
||||
|
||||
copy({
|
||||
async copy({
|
||||
from,
|
||||
to,
|
||||
}: {
|
||||
@@ -348,68 +309,21 @@ export class FileStorageService {
|
||||
}): Promise<void> {
|
||||
const driver = this.fileStorageDriverFactory.getCurrentDriver();
|
||||
|
||||
const fromPath = this.buildOnStoragePath(from);
|
||||
const toPath = this.buildOnStoragePath(to);
|
||||
|
||||
const isFile = await driver.checkFileExists({ filePath: fromPath });
|
||||
|
||||
if (isFile) {
|
||||
return driver.copy({
|
||||
from: { folderPath: dirname(fromPath), filename: basename(fromPath) },
|
||||
to: { folderPath: dirname(toPath), filename: basename(toPath) },
|
||||
});
|
||||
}
|
||||
|
||||
return driver.copy({
|
||||
from: { folderPath: this.buildOnStoragePath(from) },
|
||||
to: { folderPath: this.buildOnStoragePath(to) },
|
||||
});
|
||||
}
|
||||
|
||||
async moveFile({
|
||||
from,
|
||||
to,
|
||||
workspaceId,
|
||||
}: {
|
||||
from: {
|
||||
applicationId: string;
|
||||
fileFolder: FileFolder;
|
||||
destinationPath: string;
|
||||
};
|
||||
to: {
|
||||
applicationId: string;
|
||||
fileFolder: FileFolder;
|
||||
destinationPath: string;
|
||||
};
|
||||
workspaceId: string;
|
||||
}): Promise<void> {
|
||||
const file = await this.fileRepository.findOneOrFail({
|
||||
where: {
|
||||
workspaceId,
|
||||
applicationId: from.applicationId,
|
||||
path: `${from.fileFolder}/${from.destinationPath}`,
|
||||
},
|
||||
});
|
||||
|
||||
const driver = this.fileStorageDriverFactory.getCurrentDriver();
|
||||
|
||||
await driver.move({
|
||||
from: {
|
||||
folderPath: `${file.workspaceId}/${from.applicationId}/${from.fileFolder}`,
|
||||
filename: from.destinationPath,
|
||||
},
|
||||
to: {
|
||||
folderPath: `${file.workspaceId}/${to.applicationId}/${to.fileFolder}`,
|
||||
filename: to.destinationPath,
|
||||
},
|
||||
});
|
||||
|
||||
await this.fileRepository.update(file.id, {
|
||||
applicationId: to.applicationId,
|
||||
path: `${to.fileFolder}/${to.destinationPath}`,
|
||||
});
|
||||
}
|
||||
|
||||
move({
|
||||
from,
|
||||
to,
|
||||
}: {
|
||||
from: ResourceIdentifier;
|
||||
to: ResourceIdentifier;
|
||||
}): Promise<void> {
|
||||
const driver = this.fileStorageDriverFactory.getCurrentDriver();
|
||||
|
||||
return driver.move({
|
||||
from: { folderPath: this.buildOnStoragePath(from) },
|
||||
to: { folderPath: this.buildOnStoragePath(to) },
|
||||
from: { folderPath: fromPath },
|
||||
to: { folderPath: toPath },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -425,11 +339,4 @@ export class FileStorageService {
|
||||
|
||||
return driver.checkFileExists({ filePath: onStoragePath });
|
||||
}
|
||||
|
||||
checkFolderExists(params: ResourceIdentifier): Promise<boolean> {
|
||||
const driver = this.fileStorageDriverFactory.getCurrentDriver();
|
||||
const onStoragePath = this.buildOnStoragePath(params);
|
||||
|
||||
return driver.checkFolderExists({ folderPath: onStoragePath });
|
||||
}
|
||||
}
|
||||
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
import { readS3FolderContent } from 'src/engine/core-modules/file-storage/utils/read-s3-folder-content';
|
||||
|
||||
describe('read-s3-folder-content', () => {
|
||||
it('should format files to sources properly', () => {
|
||||
const files = [
|
||||
{ path: 'f1/file1.ts', fileContent: 'content1' },
|
||||
{ path: 'f1/f11/file11.ts', fileContent: 'content11' },
|
||||
{ path: 'f1/file2.ts', fileContent: 'content2' },
|
||||
{ path: 'file3.ts', fileContent: 'content3' },
|
||||
];
|
||||
|
||||
const expectedResult = {
|
||||
'file3.ts': 'content3',
|
||||
f1: {
|
||||
'file1.ts': 'content1',
|
||||
'file2.ts': 'content2',
|
||||
f11: { 'file11.ts': 'content11' },
|
||||
},
|
||||
};
|
||||
|
||||
const result = readS3FolderContent(files);
|
||||
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isObject } from '@sniptt/guards';
|
||||
import { type Sources } from 'twenty-shared/types';
|
||||
|
||||
export const readS3FolderContent = (
|
||||
files: { path: string; fileContent: string }[],
|
||||
) => {
|
||||
const result: Sources = {};
|
||||
|
||||
for (const { path, fileContent } of files) {
|
||||
const segments = path.split('/');
|
||||
const fileName = segments.pop();
|
||||
|
||||
if (!isDefined(fileName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cursor: Sources = result;
|
||||
|
||||
for (const segment of segments) {
|
||||
if (!isObject(cursor[segment])) {
|
||||
cursor[segment] = {};
|
||||
}
|
||||
cursor = cursor[segment] as Sources;
|
||||
}
|
||||
|
||||
cursor[fileName] = fileContent;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
+9
-2
@@ -38,8 +38,9 @@ import {
|
||||
LogicFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/logic-function/logic-function.exception';
|
||||
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
|
||||
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/application-layer/utils/copy-yarn-engine-and-build-dependencies';
|
||||
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/application/utils/copy-yarn-engine-and-build-dependencies';
|
||||
import { type LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service';
|
||||
import { callWithTimeout } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/call-with-timeout';
|
||||
|
||||
const UPDATE_FUNCTION_DURATION_TIMEOUT_IN_SECONDS = 60;
|
||||
const CREDENTIALS_DURATION_IN_SECONDS = 60 * 60; // 1h
|
||||
@@ -322,6 +323,7 @@ export class LambdaDriver implements LogicFunctionDriver {
|
||||
applicationUniversalIdentifier,
|
||||
payload,
|
||||
env,
|
||||
timeoutMs = 900_000,
|
||||
}: LogicFunctionExecuteParams): Promise<LogicFunctionExecuteResult> {
|
||||
await this.build({
|
||||
flatLogicFunction,
|
||||
@@ -355,7 +357,12 @@ export class LambdaDriver implements LogicFunctionDriver {
|
||||
const command = new InvokeCommand(params);
|
||||
|
||||
try {
|
||||
const result = await (await this.getLambdaClient()).send(command);
|
||||
const lambdaClient = await this.getLambdaClient();
|
||||
|
||||
const result = await callWithTimeout({
|
||||
callback: () => lambdaClient.send(command),
|
||||
timeoutMs,
|
||||
});
|
||||
|
||||
const parsedResult = result.Payload
|
||||
? JSON.parse(result.Payload.transformToString())
|
||||
|
||||
+10
-13
@@ -13,7 +13,7 @@ import { LOGIC_FUNCTION_EXECUTOR_TMPDIR_FOLDER } from 'src/engine/core-modules/l
|
||||
import { ConsoleListener } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/intercept-console';
|
||||
import { TemporaryDirManager } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/temporary-dir-manager';
|
||||
import { LogicFunctionExecutionStatus } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto';
|
||||
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/application-layer/utils/copy-yarn-engine-and-build-dependencies';
|
||||
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/application/utils/copy-yarn-engine-and-build-dependencies';
|
||||
import type { LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service';
|
||||
|
||||
export interface LocalDriverOptions {
|
||||
@@ -76,6 +76,7 @@ export class LocalDriver implements LogicFunctionDriver {
|
||||
applicationUniversalIdentifier,
|
||||
payload,
|
||||
env,
|
||||
timeoutMs = 900_000,
|
||||
}: LogicFunctionExecuteParams): Promise<LogicFunctionExecuteResult> {
|
||||
await this.build({
|
||||
flatApplication,
|
||||
@@ -89,17 +90,13 @@ export class LocalDriver implements LogicFunctionDriver {
|
||||
try {
|
||||
const { sourceTemporaryDir } = await temporaryDirManager.init();
|
||||
|
||||
const inMemoryBuiltHandlerPath = join(
|
||||
sourceTemporaryDir,
|
||||
flatLogicFunction.builtHandlerPath,
|
||||
);
|
||||
|
||||
await this.logicFunctionResourceService.copyBuiltCodeInMemory({
|
||||
workspaceId: flatLogicFunction.workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
builtHandlerPath: flatLogicFunction.builtHandlerPath,
|
||||
inMemoryDestinationPath: inMemoryBuiltHandlerPath,
|
||||
});
|
||||
const inMemoryBuiltHandlerPath =
|
||||
await this.logicFunctionResourceService.copyBuiltCodeInMemory({
|
||||
workspaceId: flatLogicFunction.workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
builtHandlerPath: flatLogicFunction.builtHandlerPath,
|
||||
inMemoryDestinationPath: sourceTemporaryDir,
|
||||
});
|
||||
|
||||
try {
|
||||
await fs.symlink(
|
||||
@@ -161,7 +158,7 @@ export class LocalDriver implements LogicFunctionDriver {
|
||||
runnerPath,
|
||||
env: env ?? {},
|
||||
payload,
|
||||
timeoutMs: 900_000, // timeout is handled by the logic function service
|
||||
timeoutMs,
|
||||
});
|
||||
|
||||
if (stdout)
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ export type LogicFunctionExecuteParams = {
|
||||
applicationUniversalIdentifier: string;
|
||||
payload: object;
|
||||
env?: Record<string, string>;
|
||||
timeoutMs?: number;
|
||||
};
|
||||
|
||||
export interface LogicFunctionDriver {
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
export const callWithTimeout = async <T>({
|
||||
callback,
|
||||
timeoutMs,
|
||||
}: {
|
||||
callback: () => Promise<T>;
|
||||
timeoutMs: number;
|
||||
}): Promise<T> => {
|
||||
return Promise.race([
|
||||
callback(),
|
||||
new Promise<T>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Execution timed out')), timeoutMs),
|
||||
),
|
||||
]);
|
||||
};
|
||||
+7
-25
@@ -83,16 +83,13 @@ export class LogicFunctionExecutorService {
|
||||
flatLogicFunction,
|
||||
});
|
||||
|
||||
const resultLogicFunction = await this.callWithTimeout({
|
||||
callback: () =>
|
||||
this.driver.execute({
|
||||
flatLogicFunction,
|
||||
flatApplication,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
payload,
|
||||
env: envVariables,
|
||||
}),
|
||||
timeoutMs: flatLogicFunction.timeoutSeconds * 1000,
|
||||
const resultLogicFunction = await this.driver.execute({
|
||||
flatLogicFunction,
|
||||
flatApplication,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
payload,
|
||||
env: envVariables,
|
||||
timeoutMs: flatLogicFunction.timeoutSeconds * 1_000,
|
||||
});
|
||||
|
||||
await this.handleExecutionResult({
|
||||
@@ -121,21 +118,6 @@ export class LogicFunctionExecutorService {
|
||||
}
|
||||
}
|
||||
|
||||
private async callWithTimeout<T>({
|
||||
callback,
|
||||
timeoutMs,
|
||||
}: {
|
||||
callback: () => Promise<T>;
|
||||
timeoutMs: number;
|
||||
}): Promise<T> {
|
||||
return Promise.race([
|
||||
callback(),
|
||||
new Promise<T>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Execution timed out')), timeoutMs),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
private async getFlatEntitiesOrThrow({
|
||||
workspaceId,
|
||||
logicFunctionId,
|
||||
|
||||
+63
-125
@@ -4,9 +4,8 @@ import crypto from 'crypto';
|
||||
import fs from 'fs/promises';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
import { isObject } from '@sniptt/guards';
|
||||
import { build } from 'esbuild';
|
||||
import { FileFolder, Sources } from 'twenty-shared/types';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { NODE_ESM_CJS_BANNER } from 'twenty-shared/application';
|
||||
|
||||
@@ -14,64 +13,56 @@ import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/i
|
||||
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { TemporaryDirManager } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/temporary-dir-manager';
|
||||
import {
|
||||
getLogicFunctionBaseFolderPath,
|
||||
getRelativePathFromBase,
|
||||
} from 'src/engine/core-modules/logic-function/logic-function-resource/utils/get-logic-function-handler-path.util';
|
||||
import {
|
||||
getLogicFunctionSeedProjectFiles,
|
||||
LogicFunctionSeedProjectFile,
|
||||
} from 'src/engine/core-modules/logic-function/logic-function-resource/utils/get-logic-function-seed-project-files.util';
|
||||
import {
|
||||
DEFAULT_BUILT_HANDLER_PATH,
|
||||
DEFAULT_SOURCE_HANDLER_PATH,
|
||||
} from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
import {
|
||||
LogicFunctionException,
|
||||
LogicFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/logic-function/logic-function.exception';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
|
||||
type SeedSourceFilesParams = {
|
||||
type Identifier = {
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
sourceSubfolder: string;
|
||||
};
|
||||
|
||||
type SeedSourceFilesParams = Identifier & {
|
||||
sourceHandlerPath: string;
|
||||
builtHandlerPath: string;
|
||||
};
|
||||
|
||||
type SeedSourceFilesResult = {
|
||||
sourceHandlerPath: string;
|
||||
builtHandlerPath: string;
|
||||
handlerName: string;
|
||||
checksum: string;
|
||||
};
|
||||
|
||||
type UpdateSourceFilesParams = {
|
||||
sourceHandlerPath: string;
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
code: Sources;
|
||||
type UpdateSourceFilesParams = Omit<
|
||||
SeedSourceFilesParams,
|
||||
'builtHandlerPath'
|
||||
> & {
|
||||
sourceHandlerCode: string;
|
||||
};
|
||||
|
||||
type BuildFromSourceParams = {
|
||||
type BuildFromSourceParams = Identifier & {
|
||||
sourceHandlerPath: string;
|
||||
builtHandlerPath: string;
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
};
|
||||
|
||||
type GetSourceCodeParams = {
|
||||
type GetSourceCodeParams = Identifier & {
|
||||
sourceHandlerPath: string;
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
};
|
||||
|
||||
type CopySourceParams = {
|
||||
type GetBuiltCodeParams = Identifier & {
|
||||
builtHandlerPath: string;
|
||||
};
|
||||
|
||||
type CopySourceParams = Identifier & {
|
||||
fromSourceHandlerPath: string;
|
||||
fromBuiltHandlerPath: string;
|
||||
toSourceHandlerPath: string;
|
||||
fromBuiltHandlerPath: string;
|
||||
toBuiltHandlerPath: string;
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
@@ -81,11 +72,9 @@ export class LogicFunctionResourceService {
|
||||
async seedSourceFiles({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
sourceSubfolder,
|
||||
sourceHandlerPath,
|
||||
builtHandlerPath,
|
||||
}: SeedSourceFilesParams): Promise<SeedSourceFilesResult> {
|
||||
const sourceHandlerPath = `${sourceSubfolder}/${DEFAULT_SOURCE_HANDLER_PATH}`;
|
||||
const builtHandlerPath = `${sourceSubfolder}/${DEFAULT_BUILT_HANDLER_PATH}`;
|
||||
|
||||
const seedProjectFiles = await getLogicFunctionSeedProjectFiles();
|
||||
|
||||
const sourceFiles = seedProjectFiles.filter(
|
||||
@@ -138,40 +127,28 @@ export class LogicFunctionResourceService {
|
||||
|
||||
return {
|
||||
handlerName: 'main',
|
||||
sourceHandlerPath,
|
||||
builtHandlerPath,
|
||||
checksum,
|
||||
};
|
||||
}
|
||||
|
||||
async updateSourceFiles({
|
||||
async uploadSourceFile({
|
||||
sourceHandlerPath,
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
code,
|
||||
sourceHandlerCode,
|
||||
}: UpdateSourceFilesParams): Promise<void> {
|
||||
const temporaryDirManager = new TemporaryDirManager();
|
||||
|
||||
try {
|
||||
const { sourceTemporaryDir } = await temporaryDirManager.init();
|
||||
|
||||
await this.writeSourcesToLocalFolder(code, sourceTemporaryDir);
|
||||
|
||||
const baseFolderPath = getLogicFunctionBaseFolderPath(sourceHandlerPath);
|
||||
|
||||
await this.fileStorageService.uploadFolder({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: baseFolderPath,
|
||||
localPath: sourceTemporaryDir,
|
||||
});
|
||||
} finally {
|
||||
await temporaryDirManager.clean();
|
||||
}
|
||||
await this.fileStorageService.writeFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: sourceHandlerPath,
|
||||
sourceFile: sourceHandlerCode,
|
||||
settings: { isTemporaryFile: false, toDelete: false },
|
||||
mimeType: 'application/typescript',
|
||||
});
|
||||
}
|
||||
|
||||
async buildFromSource({
|
||||
async buildFromSourceFile({
|
||||
sourceHandlerPath,
|
||||
builtHandlerPath,
|
||||
workspaceId,
|
||||
@@ -182,29 +159,18 @@ export class LogicFunctionResourceService {
|
||||
try {
|
||||
const { sourceTemporaryDir } = await temporaryDirManager.init();
|
||||
|
||||
const baseFolderPath = getLogicFunctionBaseFolderPath(sourceHandlerPath);
|
||||
|
||||
await this.fileStorageService.downloadFolder({
|
||||
await this.fileStorageService.downloadFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: baseFolderPath,
|
||||
localPath: sourceTemporaryDir,
|
||||
resourcePath: sourceHandlerPath,
|
||||
localPath: join(sourceTemporaryDir, sourceHandlerPath),
|
||||
});
|
||||
|
||||
const relativeSourcePath = getRelativePathFromBase(
|
||||
sourceHandlerPath,
|
||||
baseFolderPath,
|
||||
);
|
||||
const relativeBuiltPath = getRelativePathFromBase(
|
||||
builtHandlerPath,
|
||||
baseFolderPath,
|
||||
);
|
||||
|
||||
const builtBundleFilePath = await this.buildInMemory({
|
||||
sourceTemporaryDir,
|
||||
sourceHandlerPath: relativeSourcePath,
|
||||
builtHandlerPath: relativeBuiltPath,
|
||||
sourceHandlerPath,
|
||||
builtHandlerPath,
|
||||
});
|
||||
|
||||
const builtFile = await fs.readFile(builtBundleFilePath, 'utf-8');
|
||||
@@ -230,20 +196,22 @@ export class LogicFunctionResourceService {
|
||||
}
|
||||
}
|
||||
|
||||
async getSourceCode({
|
||||
async getSourceFile({
|
||||
sourceHandlerPath,
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
}: GetSourceCodeParams): Promise<Sources | null> {
|
||||
const baseFolderPath = getLogicFunctionBaseFolderPath(sourceHandlerPath);
|
||||
|
||||
}: GetSourceCodeParams): Promise<string | null> {
|
||||
try {
|
||||
return await this.fileStorageService.readFolder({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: baseFolderPath,
|
||||
});
|
||||
return (
|
||||
await streamToBuffer(
|
||||
await this.fileStorageService.readFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: sourceHandlerPath,
|
||||
}),
|
||||
)
|
||||
).toString('utf-8');
|
||||
} catch (error) {
|
||||
if (
|
||||
isDefined(error) &&
|
||||
@@ -259,34 +227,24 @@ export class LogicFunctionResourceService {
|
||||
|
||||
async copyResources({
|
||||
fromSourceHandlerPath,
|
||||
fromBuiltHandlerPath,
|
||||
toSourceHandlerPath,
|
||||
fromBuiltHandlerPath,
|
||||
toBuiltHandlerPath,
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
}: CopySourceParams): Promise<void> {
|
||||
const fromSourceBaseFolderPath = getLogicFunctionBaseFolderPath(
|
||||
fromSourceHandlerPath,
|
||||
);
|
||||
const toSourceBaseFolderPath =
|
||||
getLogicFunctionBaseFolderPath(toSourceHandlerPath);
|
||||
const fromBuiltBaseFolderPath =
|
||||
getLogicFunctionBaseFolderPath(fromBuiltHandlerPath);
|
||||
const toBuiltBaseFolderPath =
|
||||
getLogicFunctionBaseFolderPath(toBuiltHandlerPath);
|
||||
|
||||
await this.fileStorageService.copy({
|
||||
from: {
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: fromSourceBaseFolderPath,
|
||||
resourcePath: fromSourceHandlerPath,
|
||||
},
|
||||
to: {
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: toSourceBaseFolderPath,
|
||||
resourcePath: toSourceHandlerPath,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -295,13 +253,13 @@ export class LogicFunctionResourceService {
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.BuiltLogicFunction,
|
||||
resourcePath: fromBuiltBaseFolderPath,
|
||||
resourcePath: fromBuiltHandlerPath,
|
||||
},
|
||||
to: {
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.BuiltLogicFunction,
|
||||
resourcePath: toBuiltBaseFolderPath,
|
||||
resourcePath: toBuiltHandlerPath,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -337,11 +295,7 @@ export class LogicFunctionResourceService {
|
||||
builtHandlerPath,
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
}: {
|
||||
builtHandlerPath: string;
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
}): Promise<string> {
|
||||
}: GetBuiltCodeParams): Promise<string> {
|
||||
return (
|
||||
await streamToBuffer(
|
||||
await this.fileStorageService.readFile({
|
||||
@@ -359,36 +313,20 @@ export class LogicFunctionResourceService {
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
inMemoryDestinationPath,
|
||||
}: {
|
||||
builtHandlerPath: string;
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
}: GetBuiltCodeParams & {
|
||||
inMemoryDestinationPath: string;
|
||||
}): Promise<void> {
|
||||
}): Promise<string> {
|
||||
const localPath = join(inMemoryDestinationPath, builtHandlerPath);
|
||||
|
||||
await this.fileStorageService.downloadFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.BuiltLogicFunction,
|
||||
resourcePath: builtHandlerPath,
|
||||
localPath: inMemoryDestinationPath,
|
||||
localPath,
|
||||
});
|
||||
}
|
||||
|
||||
async writeSourcesToLocalFolder(
|
||||
sources: Sources,
|
||||
localPath: string,
|
||||
): Promise<void> {
|
||||
for (const key of Object.keys(sources)) {
|
||||
const filePath = join(localPath, key);
|
||||
const value = sources[key];
|
||||
|
||||
if (isObject(value)) {
|
||||
await this.writeSourcesToLocalFolder(value as Sources, filePath);
|
||||
continue;
|
||||
}
|
||||
await fs.mkdir(dirname(filePath), { recursive: true });
|
||||
await fs.writeFile(filePath, value);
|
||||
}
|
||||
return localPath;
|
||||
}
|
||||
|
||||
private async buildInMemory({
|
||||
|
||||
+1
@@ -84,6 +84,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
|
||||
"handlerName",
|
||||
"toolInputSchema",
|
||||
"isTool",
|
||||
"isBuildUpToDate",
|
||||
"deletedAt",
|
||||
"cronTriggerSettings",
|
||||
"databaseEventTriggerSettings",
|
||||
+1
@@ -149,6 +149,7 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
handlerName: { toStringify: false, universalProperty: undefined },
|
||||
toolInputSchema: { toStringify: true, universalProperty: undefined },
|
||||
isTool: { toStringify: false, universalProperty: undefined },
|
||||
isBuildUpToDate: { toStringify: false, universalProperty: undefined },
|
||||
deletedAt: { toStringify: false, universalProperty: undefined },
|
||||
cronTriggerSettings: { toStringify: true, universalProperty: undefined },
|
||||
databaseEventTriggerSettings: {
|
||||
|
||||
+2
-1
@@ -12,4 +12,5 @@ export const FLAT_LOGIC_FUNCTION_EDITABLE_PROPERTIES = [
|
||||
'cronTriggerSettings',
|
||||
'databaseEventTriggerSettings',
|
||||
'httpRouteTriggerSettings',
|
||||
] as const satisfies (MetadataEntityPropertyName<'logicFunction'> | 'code')[];
|
||||
'isBuildUpToDate',
|
||||
] as const satisfies MetadataEntityPropertyName<'logicFunction'>[];
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export const DEFAULT_BUILT_HANDLER_PATH = 'src/index.mjs';
|
||||
export const DEFAULT_SOURCE_HANDLER_PATH = 'src/index.ts';
|
||||
export const DEFAULT_HANDLER_NAME = 'main';
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
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;
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
Max,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import graphqlTypeJson from 'graphql-type-json';
|
||||
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';
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { LogicFunctionSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-source.input';
|
||||
|
||||
@InputType()
|
||||
export class CreateLogicFunctionFromSourceInput {
|
||||
@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: true })
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
toolInputSchema?: object;
|
||||
|
||||
@IsBoolean()
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
isTool?: boolean;
|
||||
|
||||
@IsObject()
|
||||
@Field(() => graphqlTypeJson, { nullable: true })
|
||||
@IsOptional()
|
||||
source?: JsonbProperty<LogicFunctionSourceInput>;
|
||||
|
||||
@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>;
|
||||
}
|
||||
+58
-6
@@ -1,6 +1,16 @@
|
||||
import { Field, HideField, InputType } from '@nestjs/graphql';
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsObject, IsOptional, IsString } from 'class-validator';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
Max,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import graphqlTypeJson from 'graphql-type-json';
|
||||
import {
|
||||
CronTriggerSettings,
|
||||
@@ -9,12 +19,54 @@ import {
|
||||
} from 'twenty-shared/application';
|
||||
|
||||
import type { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
|
||||
import { CreateDefaultLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.input';
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class CreateLogicFunctionInput extends CreateDefaultLogicFunctionInput {
|
||||
@HideField()
|
||||
checksum: string;
|
||||
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: object;
|
||||
|
||||
@IsBoolean()
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
isTool?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@Field({ nullable: false })
|
||||
isBuildUpToDate: boolean;
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
checksum?: string;
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: false })
|
||||
|
||||
+1
-15
@@ -1,12 +1,6 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
import { IsNotEmpty, IsObject, IsUUID } from 'class-validator';
|
||||
import graphqlTypeJson from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
@@ -25,12 +19,4 @@ export class ExecuteOneLogicFunctionInput {
|
||||
})
|
||||
@IsObject()
|
||||
payload: JSON;
|
||||
|
||||
@Field(() => Boolean, {
|
||||
description: 'Force rebuild from source before executing',
|
||||
nullable: true,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
forceRebuild?: boolean;
|
||||
}
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsObject, IsString } from 'class-validator';
|
||||
import graphqlTypeJson from 'graphql-type-json';
|
||||
|
||||
@InputType()
|
||||
export class LogicFunctionSourceInput {
|
||||
@IsString()
|
||||
@Field({ nullable: false })
|
||||
sourceHandlerCode: string;
|
||||
|
||||
@Field(() => graphqlTypeJson, { nullable: false })
|
||||
@IsObject()
|
||||
toolInputSchema: object;
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: false })
|
||||
handlerName: string;
|
||||
}
|
||||
-4
@@ -62,10 +62,6 @@ export class LogicFunctionDTO {
|
||||
@Field()
|
||||
sourceHandlerPath: string;
|
||||
|
||||
@IsString()
|
||||
@Field()
|
||||
builtHandlerPath: string;
|
||||
|
||||
@IsString()
|
||||
@Field()
|
||||
handlerName: string;
|
||||
|
||||
+21
-16
@@ -24,9 +24,9 @@ import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/
|
||||
import type { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
|
||||
|
||||
@InputType()
|
||||
class UpdateLogicFunctionInputUpdates {
|
||||
class UpdateLogicFunctionFromSourceInputUpdates {
|
||||
@IsString()
|
||||
@Field()
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
name?: string;
|
||||
|
||||
@@ -42,6 +42,16 @@ class UpdateLogicFunctionInputUpdates {
|
||||
@IsOptional()
|
||||
timeoutSeconds?: number;
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
sourceHandlerCode?: string;
|
||||
|
||||
@Field(() => graphqlTypeJson, { nullable: true })
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
toolInputSchema?: object;
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
@@ -52,21 +62,16 @@ class UpdateLogicFunctionInputUpdates {
|
||||
@IsOptional()
|
||||
sourceHandlerPath?: string;
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
builtHandlerPath?: string;
|
||||
|
||||
@Field(() => graphqlTypeJson, { nullable: true })
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
toolInputSchema?: object;
|
||||
|
||||
@IsBoolean()
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
isTool?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
isBuildUpToDate?: boolean;
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
@@ -89,7 +94,7 @@ class UpdateLogicFunctionInputUpdates {
|
||||
}
|
||||
|
||||
@InputType()
|
||||
export class UpdateLogicFunctionSourceInput {
|
||||
export class UpdateLogicFunctionFromSourceInput {
|
||||
@Field(() => UUIDScalarType, {
|
||||
description: 'Id of the logic function to update',
|
||||
})
|
||||
@@ -97,10 +102,10 @@ export class UpdateLogicFunctionSourceInput {
|
||||
@IsUUID()
|
||||
id: string;
|
||||
|
||||
@Type(() => UpdateLogicFunctionInputUpdates)
|
||||
@Type(() => UpdateLogicFunctionFromSourceInputUpdates)
|
||||
@ValidateNested()
|
||||
@Field(() => UpdateLogicFunctionInputUpdates, {
|
||||
@Field(() => UpdateLogicFunctionFromSourceInputUpdates, {
|
||||
description: 'The logic function updates',
|
||||
})
|
||||
update: UpdateLogicFunctionInputUpdates;
|
||||
update: UpdateLogicFunctionFromSourceInputUpdates;
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsObject, IsUUID } from 'class-validator';
|
||||
import graphqlTypeJson from 'graphql-type-json';
|
||||
|
||||
import type { Sources } from 'twenty-shared/types';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class UpdateLogicFunctionSourceInput {
|
||||
@Field(() => UUIDScalarType, {
|
||||
description: 'The id of the logic function.',
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsUUID()
|
||||
id!: string;
|
||||
|
||||
@Field(() => graphqlTypeJson, {
|
||||
description:
|
||||
'The source code (Sources) to write. Only updates source files.',
|
||||
})
|
||||
@IsObject()
|
||||
code!: Sources;
|
||||
}
|
||||
+6
-7
@@ -24,10 +24,6 @@ export enum LogicFunctionRuntime {
|
||||
NODE22 = 'nodejs22.x',
|
||||
}
|
||||
|
||||
export const DEFAULT_SOURCE_HANDLER_PATH = 'src/index.ts';
|
||||
export const DEFAULT_BUILT_HANDLER_PATH = 'src/index.mjs';
|
||||
export const DEFAULT_HANDLER_NAME = 'main';
|
||||
|
||||
@Entity('logicFunction')
|
||||
@Index('IDX_LOGIC_FUNCTION_ID_DELETED_AT', ['id', 'deletedAt'])
|
||||
export class LogicFunctionEntity
|
||||
@@ -40,13 +36,13 @@ export class LogicFunctionEntity
|
||||
@Column({ nullable: false })
|
||||
name: string;
|
||||
|
||||
@Column({ nullable: false, default: DEFAULT_SOURCE_HANDLER_PATH })
|
||||
@Column({ nullable: false })
|
||||
sourceHandlerPath: string;
|
||||
|
||||
@Column({ nullable: false, default: DEFAULT_BUILT_HANDLER_PATH })
|
||||
@Column({ nullable: false })
|
||||
builtHandlerPath: string;
|
||||
|
||||
@Column({ nullable: false, default: DEFAULT_HANDLER_NAME })
|
||||
@Column({ nullable: false })
|
||||
handlerName: string;
|
||||
|
||||
@Column({ nullable: true, type: 'varchar' })
|
||||
@@ -68,6 +64,9 @@ export class LogicFunctionEntity
|
||||
@Column({ nullable: false, default: false })
|
||||
isTool: boolean;
|
||||
|
||||
@Column({ nullable: false, type: 'boolean', default: true })
|
||||
isBuildUpToDate: boolean;
|
||||
|
||||
@Column({ nullable: true, type: 'jsonb' })
|
||||
cronTriggerSettings: JsonbProperty<CronTriggerSettings> | null;
|
||||
|
||||
|
||||
+3
-5
@@ -3,7 +3,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { ApplicationLayerModule } from 'src/engine/core-modules/application-layer/application-layer.module';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
||||
@@ -19,7 +18,7 @@ import { LogicFunctionLayerModule } from 'src/engine/metadata-modules/logic-func
|
||||
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 { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/services/logic-function.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';
|
||||
import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module';
|
||||
@@ -32,7 +31,6 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
TypeOrmModule.forFeature([ApplicationEntity, FeatureFlagEntity]),
|
||||
ThrottlerModule,
|
||||
ApplicationModule,
|
||||
ApplicationLayerModule,
|
||||
AuditModule,
|
||||
FeatureFlagModule,
|
||||
PermissionsModule,
|
||||
@@ -46,10 +44,10 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
],
|
||||
providers: [
|
||||
LogicFunctionMetadataService,
|
||||
LogicFunctionService,
|
||||
LogicFunctionFromSourceService,
|
||||
LogicFunctionResolver,
|
||||
WorkspaceFlatLogicFunctionMapCacheService,
|
||||
],
|
||||
exports: [LogicFunctionMetadataService, LogicFunctionService],
|
||||
exports: [LogicFunctionMetadataService, LogicFunctionFromSourceService],
|
||||
})
|
||||
export class LogicFunctionModule {}
|
||||
|
||||
+30
-21
@@ -14,18 +14,19 @@ 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 { CreateDefaultLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.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 { 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 { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service';
|
||||
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()
|
||||
@@ -33,7 +34,8 @@ import { SubscriptionService } from 'src/engine/subscriptions/subscription.servi
|
||||
@UseFilters(PreventNestToAutoLogGraphqlErrorsFilter)
|
||||
export class LogicFunctionResolver {
|
||||
constructor(
|
||||
private readonly logicFunctionService: LogicFunctionService,
|
||||
private readonly logicFunctionFromSourceService: LogicFunctionFromSourceService,
|
||||
private readonly logicFunctionMetadataService: LogicFunctionMetadataService,
|
||||
private readonly subscriptionService: SubscriptionService,
|
||||
) {}
|
||||
|
||||
@@ -43,7 +45,10 @@ export class LogicFunctionResolver {
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<LogicFunctionDTO> {
|
||||
try {
|
||||
return await this.logicFunctionService.findOne({ id, workspaceId });
|
||||
return await this.logicFunctionMetadataService.findOne({
|
||||
id,
|
||||
workspaceId,
|
||||
});
|
||||
} catch (error) {
|
||||
return logicFunctionGraphQLApiExceptionHandler(error);
|
||||
}
|
||||
@@ -54,7 +59,9 @@ export class LogicFunctionResolver {
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<LogicFunctionDTO[]> {
|
||||
try {
|
||||
return await this.logicFunctionService.findMany({ workspaceId });
|
||||
return await this.logicFunctionMetadataService.findMany({
|
||||
workspaceId,
|
||||
});
|
||||
} catch (error) {
|
||||
return logicFunctionGraphQLApiExceptionHandler(error);
|
||||
}
|
||||
@@ -67,8 +74,8 @@ export class LogicFunctionResolver {
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
) {
|
||||
try {
|
||||
return await this.logicFunctionService.getAvailablePackages({
|
||||
id,
|
||||
return await this.logicFunctionMetadataService.getAvailablePackages({
|
||||
logicFunctionId: id,
|
||||
workspaceId,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -83,7 +90,10 @@ export class LogicFunctionResolver {
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<LogicFunctionDTO> {
|
||||
try {
|
||||
return await this.logicFunctionService.deleteOne({ id, workspaceId });
|
||||
return await this.logicFunctionFromSourceService.deleteOne({
|
||||
id,
|
||||
workspaceId,
|
||||
});
|
||||
} catch (error) {
|
||||
return logicFunctionGraphQLApiExceptionHandler(error);
|
||||
}
|
||||
@@ -91,12 +101,12 @@ export class LogicFunctionResolver {
|
||||
|
||||
@Mutation(() => LogicFunctionDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.WORKFLOWS))
|
||||
async createDefaultLogicFunction(
|
||||
@Args('input') input: CreateDefaultLogicFunctionInput,
|
||||
async createOneLogicFunction(
|
||||
@Args('input') input: CreateLogicFunctionFromSourceInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<LogicFunctionDTO> {
|
||||
try {
|
||||
return await this.logicFunctionService.createDefault({
|
||||
return await this.logicFunctionFromSourceService.createOne({
|
||||
input,
|
||||
workspaceId,
|
||||
});
|
||||
@@ -108,14 +118,13 @@ export class LogicFunctionResolver {
|
||||
@Mutation(() => LogicFunctionExecutionResultDTO)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.WORKFLOWS))
|
||||
async executeOneLogicFunction(
|
||||
@Args('input') { id, payload, forceRebuild }: ExecuteOneLogicFunctionInput,
|
||||
@Args('input') { id, payload }: ExecuteOneLogicFunctionInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<LogicFunctionExecutionResultDTO> {
|
||||
try {
|
||||
return await this.logicFunctionService.executeOne({
|
||||
return await this.logicFunctionFromSourceService.executeOne({
|
||||
id,
|
||||
payload,
|
||||
forceRebuild,
|
||||
workspaceId,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -123,14 +132,14 @@ export class LogicFunctionResolver {
|
||||
}
|
||||
}
|
||||
|
||||
@Query(() => graphqlTypeJson, { nullable: true })
|
||||
@Query(() => String, { nullable: true })
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.WORKFLOWS))
|
||||
async getLogicFunctionSourceCode(
|
||||
@Args('input') { id }: LogicFunctionIdInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
) {
|
||||
try {
|
||||
return await this.logicFunctionService.getSourceCode({
|
||||
return await this.logicFunctionFromSourceService.getSourceCode({
|
||||
id,
|
||||
workspaceId,
|
||||
});
|
||||
@@ -141,14 +150,14 @@ export class LogicFunctionResolver {
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@UseGuards(SettingsPermissionGuard(PermissionFlagType.WORKFLOWS))
|
||||
async updateLogicFunctionSource(
|
||||
@Args('input') { id, code }: UpdateLogicFunctionSourceInput,
|
||||
async updateOneLogicFunction(
|
||||
@Args('input') { id, update }: UpdateLogicFunctionFromSourceInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await this.logicFunctionService.updateLogicFunctionSource({
|
||||
await this.logicFunctionFromSourceService.updateOne({
|
||||
id,
|
||||
code,
|
||||
update,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
|
||||
+134
-112
@@ -1,93 +1,37 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { Sources } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { join } from 'path';
|
||||
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ApplicationLayerService } from 'src/engine/core-modules/application-layer/application-layer.service';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
|
||||
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 { CreateDefaultLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.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 { 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 { CreateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function-from-source.input';
|
||||
import { SEED_LOGIC_FUNCTION_INPUT_SCHEMA } from 'src/engine/core-modules/logic-function/logic-function-resource/constants/seed-logic-function-input-schema';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class LogicFunctionService {
|
||||
export class LogicFunctionFromSourceService {
|
||||
constructor(
|
||||
private readonly logicFunctionExecutorService: LogicFunctionExecutorService,
|
||||
private readonly logicFunctionMetadataService: LogicFunctionMetadataService,
|
||||
private readonly logicFunctionResourceService: LogicFunctionResourceService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly applicationLayerService: ApplicationLayerService,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
) {}
|
||||
|
||||
async findOne({
|
||||
id,
|
||||
workspaceId,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
}): Promise<LogicFunctionDTO> {
|
||||
const { flatLogicFunctionMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatLogicFunctionMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatLogicFunction = findFlatLogicFunctionOrThrow({
|
||||
id,
|
||||
flatLogicFunctionMaps,
|
||||
});
|
||||
|
||||
return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction });
|
||||
}
|
||||
|
||||
async findMany({
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
}): Promise<LogicFunctionDTO[]> {
|
||||
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 getAvailablePackages({
|
||||
id,
|
||||
workspaceId,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
return this.applicationLayerService.getAvailablePackages({
|
||||
logicFunctionId: id,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteOne({
|
||||
id,
|
||||
workspaceId,
|
||||
@@ -104,11 +48,27 @@ export class LogicFunctionService {
|
||||
return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction });
|
||||
}
|
||||
|
||||
async createDefault({
|
||||
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({
|
||||
input,
|
||||
workspaceId,
|
||||
}: {
|
||||
input: CreateDefaultLogicFunctionInput;
|
||||
input: CreateLogicFunctionFromSourceInput;
|
||||
workspaceId: string;
|
||||
}): Promise<LogicFunctionDTO> {
|
||||
const { workspaceCustomFlatApplication } =
|
||||
@@ -118,9 +78,40 @@ export class LogicFunctionService {
|
||||
|
||||
const logicFunctionId = input.id ?? v4();
|
||||
|
||||
const { sourceHandlerPath, builtHandlerPath, handlerName, checksum } =
|
||||
const { sourceHandlerPath, builtHandlerPath } =
|
||||
this.getHandlerPaths(logicFunctionId);
|
||||
|
||||
if (input.source) {
|
||||
await this.logicFunctionResourceService.uploadSourceFile({
|
||||
sourceHandlerPath,
|
||||
sourceHandlerCode: input.source.sourceHandlerCode,
|
||||
applicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const flatLogicFunction =
|
||||
await this.logicFunctionMetadataService.createOne({
|
||||
input: {
|
||||
...input,
|
||||
handlerName: input.source.handlerName,
|
||||
toolInputSchema: input.source.toolInputSchema,
|
||||
sourceHandlerPath,
|
||||
builtHandlerPath,
|
||||
id: logicFunctionId,
|
||||
isBuildUpToDate: false,
|
||||
},
|
||||
workspaceId,
|
||||
ownerFlatApplication: workspaceCustomFlatApplication,
|
||||
});
|
||||
|
||||
return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction });
|
||||
}
|
||||
|
||||
const { handlerName, checksum } =
|
||||
await this.logicFunctionResourceService.seedSourceFiles({
|
||||
sourceSubfolder: logicFunctionId,
|
||||
sourceHandlerPath,
|
||||
builtHandlerPath,
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
@@ -135,6 +126,8 @@ export class LogicFunctionService {
|
||||
builtHandlerPath,
|
||||
handlerName,
|
||||
checksum,
|
||||
toolInputSchema: SEED_LOGIC_FUNCTION_INPUT_SCHEMA,
|
||||
isBuildUpToDate: true,
|
||||
},
|
||||
workspaceId,
|
||||
ownerFlatApplication: workspaceCustomFlatApplication,
|
||||
@@ -144,35 +137,84 @@ export class LogicFunctionService {
|
||||
return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction });
|
||||
}
|
||||
|
||||
// TODO: remove forceRebuild parameter and add a column called shouldRebuild or isBuiltUpToDate
|
||||
async updateOne({
|
||||
id,
|
||||
update,
|
||||
workspaceId,
|
||||
}: {
|
||||
id: string;
|
||||
update: UpdateLogicFunctionFromSourceInput['update'];
|
||||
workspaceId: string;
|
||||
}): Promise<void> {
|
||||
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,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
formattedUpdate = {
|
||||
...formattedUpdate,
|
||||
isBuildUpToDate: false,
|
||||
};
|
||||
}
|
||||
|
||||
await this.logicFunctionMetadataService.updateOne({
|
||||
id,
|
||||
update: formattedUpdate,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
async buildOneFromSource({
|
||||
id,
|
||||
workspaceId,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
}): Promise<void> {
|
||||
const { flatLogicFunction, applicationUniversalIdentifier } =
|
||||
await this.getLogicFunctionContext({ id, workspaceId });
|
||||
|
||||
const { checksum } =
|
||||
await this.logicFunctionResourceService.buildFromSourceFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
sourceHandlerPath: flatLogicFunction.sourceHandlerPath,
|
||||
builtHandlerPath: flatLogicFunction.builtHandlerPath,
|
||||
});
|
||||
|
||||
await this.logicFunctionMetadataService.updateOne({
|
||||
id,
|
||||
update: { checksum, isBuildUpToDate: true },
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
async executeOne({
|
||||
id,
|
||||
payload,
|
||||
forceRebuild,
|
||||
workspaceId,
|
||||
}: {
|
||||
id: string;
|
||||
payload: object;
|
||||
forceRebuild?: boolean;
|
||||
workspaceId: string;
|
||||
}): Promise<LogicFunctionExecutionResultDTO> {
|
||||
if (forceRebuild) {
|
||||
const { flatLogicFunction, applicationUniversalIdentifier } =
|
||||
await this.getLogicFunctionContext({ id, workspaceId });
|
||||
const { flatLogicFunction } = await this.getLogicFunctionContext({
|
||||
id,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const { checksum } =
|
||||
await this.logicFunctionResourceService.buildFromSource({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
sourceHandlerPath: flatLogicFunction.sourceHandlerPath,
|
||||
builtHandlerPath: flatLogicFunction.builtHandlerPath,
|
||||
});
|
||||
|
||||
await this.logicFunctionMetadataService.updateChecksum({
|
||||
id,
|
||||
checksum,
|
||||
workspaceId,
|
||||
});
|
||||
if (!flatLogicFunction.isBuildUpToDate) {
|
||||
await this.buildOneFromSource({ workspaceId, id });
|
||||
}
|
||||
|
||||
const result = await this.logicFunctionExecutorService.execute({
|
||||
@@ -204,37 +246,17 @@ export class LogicFunctionService {
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
}): Promise<Sources | null> {
|
||||
}): Promise<string | null> {
|
||||
const { flatLogicFunction, applicationUniversalIdentifier } =
|
||||
await this.getLogicFunctionContext({ id, workspaceId });
|
||||
|
||||
return this.logicFunctionResourceService.getSourceCode({
|
||||
return this.logicFunctionResourceService.getSourceFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
sourceHandlerPath: flatLogicFunction.sourceHandlerPath,
|
||||
});
|
||||
}
|
||||
|
||||
async updateLogicFunctionSource({
|
||||
id,
|
||||
code,
|
||||
workspaceId,
|
||||
}: {
|
||||
id: string;
|
||||
code: Sources;
|
||||
workspaceId: string;
|
||||
}): Promise<void> {
|
||||
const { flatLogicFunction, applicationUniversalIdentifier } =
|
||||
await this.getLogicFunctionContext({ id, workspaceId });
|
||||
|
||||
await this.logicFunctionResourceService.updateSourceFiles({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
sourceHandlerPath: flatLogicFunction.sourceHandlerPath,
|
||||
code,
|
||||
});
|
||||
}
|
||||
|
||||
private async getLogicFunctionContext({
|
||||
id,
|
||||
workspaceId,
|
||||
+90
-63
@@ -7,18 +7,20 @@ import { type FlatApplication } from 'src/engine/core-modules/application/types/
|
||||
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 { UpdateLogicFunctionSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function.input';
|
||||
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-input-to-flat-logic-function.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 {
|
||||
@@ -28,12 +30,59 @@ export class LogicFunctionMetadataService {
|
||||
private readonly applicationService: ApplicationService,
|
||||
) {}
|
||||
|
||||
async findOne({
|
||||
id,
|
||||
workspaceId,
|
||||
}: {
|
||||
id: string;
|
||||
workspaceId: string;
|
||||
}): Promise<LogicFunctionDTO> {
|
||||
const { flatLogicFunctionMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatLogicFunctionMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatLogicFunction = findFlatLogicFunctionOrThrow({
|
||||
id,
|
||||
flatLogicFunctionMaps,
|
||||
});
|
||||
|
||||
return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction });
|
||||
}
|
||||
|
||||
async findMany({
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
}): Promise<LogicFunctionDTO[]> {
|
||||
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: Omit<CreateLogicFunctionInput, 'applicationId'>;
|
||||
input: CreateLogicFunction;
|
||||
ownerFlatApplication: FlatApplication;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
@@ -86,7 +135,7 @@ export class LogicFunctionMetadataService {
|
||||
ownerFlatApplication,
|
||||
}: {
|
||||
id: string;
|
||||
update: UpdateLogicFunctionSourceInput['update'];
|
||||
update: Omit<UpdateLogicFunctionFromSourceInput['update'], 'source'>;
|
||||
workspaceId: string;
|
||||
ownerFlatApplication?: FlatApplication;
|
||||
}) {
|
||||
@@ -142,64 +191,6 @@ export class LogicFunctionMetadataService {
|
||||
);
|
||||
}
|
||||
|
||||
async updateChecksum({
|
||||
id,
|
||||
checksum,
|
||||
workspaceId,
|
||||
}: {
|
||||
id: string;
|
||||
checksum: string;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
const resolvedOwnerFlatApplication = (
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
)
|
||||
).workspaceCustomFlatApplication;
|
||||
|
||||
const { flatLogicFunctionMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatLogicFunctionMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatLogicFunction = findFlatLogicFunctionOrThrow({
|
||||
id,
|
||||
flatLogicFunctionMaps,
|
||||
});
|
||||
|
||||
const optimisticallyUpdatedFlatLogicFunction = {
|
||||
...flatLogicFunction,
|
||||
checksum,
|
||||
};
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
logicFunction: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [optimisticallyUpdatedFlatLogicFunction],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
isSystemBuild: false,
|
||||
applicationUniversalIdentifier:
|
||||
resolvedOwnerFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndBuildResult)) {
|
||||
throw new WorkspaceMigrationBuilderException(
|
||||
validateAndBuildResult,
|
||||
'Multiple validation errors occurred while updating logic function checksum',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async getFlatLogicFunctionAfterUpdate(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
@@ -285,4 +276,40 @@ export class LogicFunctionMetadataService {
|
||||
|
||||
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 ?? {};
|
||||
}
|
||||
}
|
||||
|
||||
+1
-5
@@ -1,8 +1,4 @@
|
||||
import { type Sources } from 'twenty-shared/types';
|
||||
|
||||
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
|
||||
import { type LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
|
||||
export type FlatLogicFunction = FlatEntityFrom<LogicFunctionEntity> & {
|
||||
code?: Sources;
|
||||
};
|
||||
export type FlatLogicFunction = FlatEntityFrom<LogicFunctionEntity>;
|
||||
|
||||
+8
-16
@@ -1,15 +1,12 @@
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { type CreateLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function.input';
|
||||
import {
|
||||
DEFAULT_HANDLER_NAME,
|
||||
LogicFunctionRuntime,
|
||||
} from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
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: Omit<CreateLogicFunctionInput, 'applicationId'>;
|
||||
createLogicFunctionInput: CreateLogicFunction;
|
||||
workspaceId: string;
|
||||
ownerFlatApplication: FlatApplication;
|
||||
};
|
||||
@@ -22,14 +19,9 @@ export const fromCreateLogicFunctionInputToFlatLogicFunction = ({
|
||||
const id = rawCreateLogicFunctionInput.id ?? v4();
|
||||
const currentDate = new Date();
|
||||
|
||||
const sourceHandlerPath = rawCreateLogicFunctionInput.sourceHandlerPath;
|
||||
const builtHandlerPath = rawCreateLogicFunctionInput.builtHandlerPath;
|
||||
|
||||
const universalIdentifier =
|
||||
rawCreateLogicFunctionInput.universalIdentifier ?? v4();
|
||||
|
||||
const checksum = rawCreateLogicFunctionInput.checksum;
|
||||
|
||||
return {
|
||||
id,
|
||||
cronTriggerSettings:
|
||||
@@ -40,10 +32,9 @@ export const fromCreateLogicFunctionInputToFlatLogicFunction = ({
|
||||
rawCreateLogicFunctionInput.httpRouteTriggerSettings ?? null,
|
||||
name: rawCreateLogicFunctionInput.name,
|
||||
description: rawCreateLogicFunctionInput.description ?? null,
|
||||
sourceHandlerPath,
|
||||
handlerName:
|
||||
rawCreateLogicFunctionInput.handlerName ?? DEFAULT_HANDLER_NAME,
|
||||
builtHandlerPath,
|
||||
sourceHandlerPath: rawCreateLogicFunctionInput.sourceHandlerPath,
|
||||
handlerName: rawCreateLogicFunctionInput.handlerName,
|
||||
builtHandlerPath: rawCreateLogicFunctionInput.builtHandlerPath,
|
||||
universalIdentifier,
|
||||
createdAt: currentDate.toISOString(),
|
||||
updatedAt: currentDate.toISOString(),
|
||||
@@ -52,9 +43,10 @@ export const fromCreateLogicFunctionInputToFlatLogicFunction = ({
|
||||
runtime: LogicFunctionRuntime.NODE22,
|
||||
timeoutSeconds: rawCreateLogicFunctionInput.timeoutSeconds ?? 300,
|
||||
workspaceId,
|
||||
checksum,
|
||||
checksum: rawCreateLogicFunctionInput.checksum ?? null,
|
||||
toolInputSchema: rawCreateLogicFunctionInput.toolInputSchema ?? null,
|
||||
isTool: rawCreateLogicFunctionInput?.isTool ?? false,
|
||||
isBuildUpToDate: rawCreateLogicFunctionInput.isBuildUpToDate,
|
||||
applicationUniversalIdentifier: ownerFlatApplication.universalIdentifier,
|
||||
};
|
||||
};
|
||||
-1
@@ -14,7 +14,6 @@ export const fromFlatLogicFunctionToLogicFunctionDto = ({
|
||||
runtime: flatLogicFunction.runtime,
|
||||
timeoutSeconds: flatLogicFunction.timeoutSeconds,
|
||||
sourceHandlerPath: flatLogicFunction.sourceHandlerPath,
|
||||
builtHandlerPath: flatLogicFunction.builtHandlerPath,
|
||||
handlerName: flatLogicFunction.handlerName,
|
||||
toolInputSchema: flatLogicFunction.toolInputSchema ?? undefined,
|
||||
isTool: flatLogicFunction.isTool,
|
||||
|
||||
+2
-2
@@ -2,16 +2,16 @@ import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'tw
|
||||
|
||||
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 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 { 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: UpdateLogicFunctionSourceInput;
|
||||
updateLogicFunctionInput: UpdateLogicFunctionFromSourceInput;
|
||||
flatLogicFunctionMaps: MetadataFlatEntityMaps<'logicFunction'>;
|
||||
}): FlatLogicFunction => {
|
||||
const { id: logicFunctionToUpdateId } =
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export const getLogicFunctionSubfolderForFromSource = (
|
||||
logicFunctionId: string,
|
||||
) => {
|
||||
return logicFunctionId;
|
||||
};
|
||||
+1
-5
@@ -1,11 +1,7 @@
|
||||
import { type Sources } from 'twenty-shared/types';
|
||||
|
||||
import { type LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
import { type UniversalFlatEntityFrom } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
|
||||
|
||||
export type UniversalFlatLogicFunction = UniversalFlatEntityFrom<
|
||||
LogicFunctionEntity,
|
||||
'logicFunction'
|
||||
> & {
|
||||
code?: Sources;
|
||||
};
|
||||
>;
|
||||
|
||||
+1
-5
@@ -1,5 +1,3 @@
|
||||
import { type Sources } from 'twenty-shared/types';
|
||||
|
||||
import { type BaseFlatCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-create-workspace-migration-action.type';
|
||||
import { type BaseFlatDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-delete-workspace-migration-action.type';
|
||||
import { type BaseFlatUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-update-workspace-migration-action.type';
|
||||
@@ -14,9 +12,7 @@ export type UniversalCreateLogicFunctionAction =
|
||||
BaseUniversalCreateWorkspaceMigrationAction<'logicFunction'>;
|
||||
|
||||
export type FlatUpdateLogicFunctionAction =
|
||||
BaseFlatUpdateWorkspaceMigrationAction<'logicFunction'> & {
|
||||
code?: Sources;
|
||||
};
|
||||
BaseFlatUpdateWorkspaceMigrationAction<'logicFunction'>;
|
||||
|
||||
export type UniversalUpdateLogicFunctionAction =
|
||||
BaseUniversalUpdateWorkspaceMigrationAction<'logicFunction'>;
|
||||
|
||||
+1
-15
@@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
|
||||
|
||||
import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
import { UniversalUpdateLogicFunctionAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/logic-function/types/workspace-migration-logic-function-action.type';
|
||||
import {
|
||||
ValidateAndBuildArgs,
|
||||
@@ -27,7 +26,6 @@ export class WorkspaceMigrationLogicFunctionActionsBuilderService extends Worksp
|
||||
public async validateAndBuild(
|
||||
args: ValidateAndBuildArgs<typeof ALL_METADATA_NAME.logicFunction>,
|
||||
): ValidateAndBuildReturnType<typeof ALL_METADATA_NAME.logicFunction> {
|
||||
const { to: toFlatEntityMaps } = args;
|
||||
const baseResult = await super.validateAndBuild(args);
|
||||
|
||||
if (baseResult.status === 'fail') {
|
||||
@@ -35,19 +33,7 @@ export class WorkspaceMigrationLogicFunctionActionsBuilderService extends Worksp
|
||||
}
|
||||
|
||||
const updatedActions = baseResult.actions.update.map((action) => {
|
||||
if (action.type !== 'update') {
|
||||
return action;
|
||||
}
|
||||
|
||||
const toLogicFunction = findFlatEntityByUniversalIdentifier({
|
||||
universalIdentifier: action.universalIdentifier,
|
||||
flatEntityMaps: toFlatEntityMaps,
|
||||
});
|
||||
|
||||
return {
|
||||
...action,
|
||||
code: toLogicFunction?.code,
|
||||
};
|
||||
return action;
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
+1
-27
@@ -1,16 +1,10 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
import {
|
||||
LogicFunctionException,
|
||||
LogicFunctionExceptionCode,
|
||||
} from 'src/engine/metadata-modules/logic-function/logic-function.exception';
|
||||
import {
|
||||
FlatCreateLogicFunctionAction,
|
||||
UniversalCreateLogicFunctionAction,
|
||||
@@ -25,10 +19,6 @@ export class CreateLogicFunctionActionHandlerService extends WorkspaceMigrationR
|
||||
'create',
|
||||
'logicFunction',
|
||||
) {
|
||||
constructor(private readonly fileStorageService: FileStorageService) {
|
||||
super();
|
||||
}
|
||||
|
||||
override async transpileUniversalActionToFlatAction({
|
||||
action,
|
||||
flatApplication,
|
||||
@@ -48,25 +38,9 @@ export class CreateLogicFunctionActionHandlerService extends WorkspaceMigrationR
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerContext<FlatCreateLogicFunctionAction>,
|
||||
): Promise<void> {
|
||||
const { flatAction, queryRunner, workspaceId, flatApplication } = context;
|
||||
const { flatAction, queryRunner, workspaceId } = context;
|
||||
const { flatEntity: logicFunction } = flatAction;
|
||||
|
||||
const applicationUniversalIdentifier = flatApplication.universalIdentifier;
|
||||
|
||||
const builtExists = await this.fileStorageService.checkFileExists({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.BuiltLogicFunction,
|
||||
resourcePath: logicFunction.builtHandlerPath,
|
||||
});
|
||||
|
||||
if (!builtExists) {
|
||||
throw new LogicFunctionException(
|
||||
'Logic function built file missing before create',
|
||||
LogicFunctionExceptionCode.LOGIC_FUNCTION_CREATE_FAILED,
|
||||
);
|
||||
}
|
||||
|
||||
const logicFunctionRepository =
|
||||
queryRunner.manager.getRepository<LogicFunctionEntity>(
|
||||
LogicFunctionEntity,
|
||||
|
||||
+10
@@ -15,6 +15,7 @@ import {
|
||||
WorkspaceMigrationActionRunnerArgs,
|
||||
WorkspaceMigrationActionRunnerContext,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
|
||||
import { getLogicFunctionSubfolderForFromSource } from 'src/engine/metadata-modules/logic-function/utils/get-logic-function-subfolder-for-from-source';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteLogicFunctionActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
|
||||
@@ -59,6 +60,15 @@ export class DeleteLogicFunctionActionHandlerService extends WorkspaceMigrationR
|
||||
|
||||
const applicationUniversalIdentifier = flatApplication.universalIdentifier;
|
||||
|
||||
await this.fileStorageService.delete({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.Source,
|
||||
resourcePath: getLogicFunctionSubfolderForFromSource(
|
||||
flatLogicFunction.id,
|
||||
),
|
||||
});
|
||||
|
||||
await this.fileStorageService.delete({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
|
||||
+1
-50
@@ -1,18 +1,9 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
|
||||
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
|
||||
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
|
||||
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 { resolveUniversalUpdateRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-update-relation-identifiers-to-ids.util';
|
||||
import {
|
||||
FlatUpdateLogicFunctionAction,
|
||||
@@ -28,10 +19,6 @@ export class UpdateLogicFunctionActionHandlerService extends WorkspaceMigrationR
|
||||
'update',
|
||||
'logicFunction',
|
||||
) {
|
||||
constructor(private readonly fileStorageService: FileStorageService) {
|
||||
super();
|
||||
}
|
||||
|
||||
override async transpileUniversalActionToFlatAction(
|
||||
context: WorkspaceMigrationActionRunnerArgs<UniversalUpdateLogicFunctionAction>,
|
||||
): Promise<FlatUpdateLogicFunctionAction> {
|
||||
@@ -59,7 +46,7 @@ export class UpdateLogicFunctionActionHandlerService extends WorkspaceMigrationR
|
||||
async executeForMetadata(
|
||||
context: WorkspaceMigrationActionRunnerContext<FlatUpdateLogicFunctionAction>,
|
||||
): Promise<void> {
|
||||
const { flatAction, queryRunner, flatApplication, workspaceId } = context;
|
||||
const { flatAction, queryRunner, workspaceId } = context;
|
||||
const { entityId, update } = flatAction;
|
||||
|
||||
const logicFunctionRepository =
|
||||
@@ -68,41 +55,5 @@ export class UpdateLogicFunctionActionHandlerService extends WorkspaceMigrationR
|
||||
);
|
||||
|
||||
await logicFunctionRepository.update({ id: entityId, workspaceId }, update);
|
||||
|
||||
const flatLogicFunction = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: entityId,
|
||||
flatEntityMaps: context.allFlatEntityMaps.flatLogicFunctionMaps,
|
||||
});
|
||||
|
||||
const applicationUniversalIdentifier = flatApplication.universalIdentifier;
|
||||
|
||||
if (update.checksum) {
|
||||
await this.verifySourceAndBuiltFilesExist({
|
||||
flatLogicFunction,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async verifySourceAndBuiltFilesExist({
|
||||
flatLogicFunction,
|
||||
applicationUniversalIdentifier,
|
||||
}: {
|
||||
flatLogicFunction: FlatLogicFunction;
|
||||
applicationUniversalIdentifier: string;
|
||||
}): Promise<void> {
|
||||
const builtExists = await this.fileStorageService.checkFileExists({
|
||||
workspaceId: flatLogicFunction.workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.BuiltLogicFunction,
|
||||
resourcePath: flatLogicFunction.builtHandlerPath,
|
||||
});
|
||||
|
||||
if (!builtExists) {
|
||||
throw new LogicFunctionException(
|
||||
'Logic function source or built file missing before update',
|
||||
LogicFunctionExceptionCode.LOGIC_FUNCTION_NOT_READY,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user