1573 extensibility twenty cli handle custom layers for serverless functions of applications (#14779)
- allow specific layers for serverless functions - add a serverlessFunctionLayer table - sync application layer
This commit is contained in:
+50
-18
@@ -4,7 +4,6 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
AgentManifest,
|
||||
AppManifest,
|
||||
ObjectManifest,
|
||||
} from 'src/engine/core-modules/application/types/application.types';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
@@ -17,6 +16,8 @@ import {
|
||||
ApplicationException,
|
||||
ApplicationExceptionCode,
|
||||
} from 'src/engine/core-modules/application/application.exception';
|
||||
import { ServerlessFunctionLayerService } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.service';
|
||||
import { ApplicationInput } from 'src/engine/core-modules/application/dtos/application.input';
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationSyncService {
|
||||
@@ -24,17 +25,27 @@ export class ApplicationSyncService {
|
||||
|
||||
constructor(
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly serverlessFunctionLayerService: ServerlessFunctionLayerService,
|
||||
private readonly objectMetadataServiceV2: ObjectMetadataServiceV2,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly agentService: AgentService,
|
||||
) {}
|
||||
|
||||
public async synchronizeFromManifest(
|
||||
workspaceId: string,
|
||||
manifest: AppManifest,
|
||||
) {
|
||||
const applicationId = await this.syncApplication(manifest, workspaceId);
|
||||
public async synchronizeFromManifest({
|
||||
workspaceId,
|
||||
manifest,
|
||||
packageJson,
|
||||
yarnLock,
|
||||
}: ApplicationInput & {
|
||||
workspaceId: string;
|
||||
}) {
|
||||
const applicationId = await this.syncApplication({
|
||||
workspaceId,
|
||||
manifest,
|
||||
packageJson,
|
||||
yarnLock,
|
||||
});
|
||||
|
||||
await this.syncAgents({
|
||||
agentsToSync: manifest.agents,
|
||||
@@ -51,32 +62,53 @@ export class ApplicationSyncService {
|
||||
this.logger.log('✅ Application sync from manifest completed');
|
||||
}
|
||||
|
||||
private async syncApplication(
|
||||
applicationToSync: AppManifest,
|
||||
workspaceId: string,
|
||||
): Promise<string> {
|
||||
private async syncApplication({
|
||||
workspaceId,
|
||||
manifest,
|
||||
packageJson,
|
||||
yarnLock,
|
||||
}: ApplicationInput & {
|
||||
workspaceId: string;
|
||||
}): Promise<string> {
|
||||
const application = await this.applicationService.findByStandardId(
|
||||
applicationToSync.standardId,
|
||||
manifest.standardId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!isDefined(application)) {
|
||||
const serverlessFunctionLayer =
|
||||
await this.serverlessFunctionLayerService.create(
|
||||
{
|
||||
packageJson,
|
||||
yarnLock,
|
||||
},
|
||||
workspaceId,
|
||||
);
|
||||
const createdApplication = await this.applicationService.create({
|
||||
standardId: applicationToSync.standardId,
|
||||
label: applicationToSync.label,
|
||||
description: applicationToSync.description,
|
||||
version: applicationToSync.version,
|
||||
standardId: manifest.standardId,
|
||||
label: manifest.label,
|
||||
description: manifest.description,
|
||||
version: manifest.version,
|
||||
sourcePath: 'cli-sync', // Placeholder for CLI-synced apps
|
||||
serverlessFunctionLayerId: serverlessFunctionLayer.id,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return createdApplication.id;
|
||||
}
|
||||
|
||||
await this.serverlessFunctionLayerService.update(
|
||||
application.serverlessFunctionLayerId,
|
||||
{
|
||||
packageJson,
|
||||
yarnLock,
|
||||
},
|
||||
);
|
||||
|
||||
await this.applicationService.update(application.id, {
|
||||
label: applicationToSync.label,
|
||||
description: applicationToSync.description,
|
||||
version: applicationToSync.version,
|
||||
label: manifest.label,
|
||||
description: manifest.description,
|
||||
version: manifest.version,
|
||||
});
|
||||
|
||||
return application.id;
|
||||
|
||||
@@ -6,12 +6,18 @@ import {
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Relation,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity';
|
||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ServerlessFunctionLayerEntity } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.entity';
|
||||
|
||||
@Entity({ name: 'application', schema: 'core' })
|
||||
@Index('IDX_APPLICATION_WORKSPACE_ID', ['workspaceId'])
|
||||
@@ -48,6 +54,38 @@ export class ApplicationEntity {
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
workspaceId: string;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
serverlessFunctionLayerId: string;
|
||||
|
||||
@OneToOne(
|
||||
() => ServerlessFunctionLayerEntity,
|
||||
(serverlessFunctionLayer) => serverlessFunctionLayer.application,
|
||||
{
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
)
|
||||
@JoinColumn({ name: 'serverlessFunctionLayerId' })
|
||||
serverlessFunctionLayer: Relation<ServerlessFunctionLayerEntity>;
|
||||
|
||||
@OneToMany(() => AgentEntity, (agent) => agent.application, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
agents: Relation<AgentEntity[]>;
|
||||
|
||||
@OneToMany(
|
||||
() => ServerlessFunctionEntity,
|
||||
(serverlessFunction) => serverlessFunction.application,
|
||||
{
|
||||
onDelete: 'CASCADE',
|
||||
},
|
||||
)
|
||||
serverlessFunctions: Relation<ServerlessFunctionEntity[]>;
|
||||
|
||||
@OneToMany(() => ObjectMetadataEntity, (object) => object.application, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
objects: Relation<ObjectMetadataEntity[]>;
|
||||
|
||||
@ManyToOne(() => Workspace, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
|
||||
@@ -11,6 +11,7 @@ import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/core-mod
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { AgentModule } from 'src/engine/metadata-modules/agent/agent.module';
|
||||
import { ServerlessFunctionLayerModule } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -19,6 +20,7 @@ import { AgentModule } from 'src/engine/metadata-modules/agent/agent.module';
|
||||
ObjectMetadataModule,
|
||||
DataSourceModule,
|
||||
AgentModule,
|
||||
ServerlessFunctionLayerModule,
|
||||
],
|
||||
providers: [ApplicationResolver, ApplicationService, ApplicationSyncService],
|
||||
})
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { AppManifest } from 'src/engine/core-modules/application/types/application.types';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-sync.service';
|
||||
import { ApplicationInput } from 'src/engine/core-modules/application/dtos/application.input';
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@Resolver()
|
||||
@@ -18,14 +16,15 @@ export class ApplicationResolver {
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async syncApplication(
|
||||
@Args('manifest', { type: () => GraphQLJSON })
|
||||
manifest: AppManifest,
|
||||
@Args() { manifest, packageJson, yarnLock }: ApplicationInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
await this.applicationSyncService.synchronizeFromManifest(
|
||||
await this.applicationSyncService.synchronizeFromManifest({
|
||||
workspaceId,
|
||||
manifest,
|
||||
);
|
||||
yarnLock,
|
||||
packageJson,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { PackageJson } from 'src/engine/core-modules/application/types/application.types';
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationService {
|
||||
@@ -32,6 +33,7 @@ export class ApplicationService {
|
||||
label: string;
|
||||
description?: string;
|
||||
version?: string;
|
||||
serverlessFunctionLayerId: string;
|
||||
sourcePath: string;
|
||||
workspaceId: string;
|
||||
}): Promise<ApplicationEntity> {
|
||||
@@ -50,6 +52,9 @@ export class ApplicationService {
|
||||
description?: string;
|
||||
version?: string;
|
||||
sourcePath?: string;
|
||||
packageJson?: PackageJson;
|
||||
yarnLock?: string;
|
||||
packageChecksum?: string;
|
||||
},
|
||||
): Promise<ApplicationEntity> {
|
||||
await this.applicationRepository.update({ id }, data);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ArgsType, Field } from '@nestjs/graphql';
|
||||
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import {
|
||||
AppManifest,
|
||||
PackageJson,
|
||||
} from 'src/engine/core-modules/application/types/application.types';
|
||||
|
||||
@ArgsType()
|
||||
export class ApplicationInput {
|
||||
@Field(() => GraphQLJSON, { nullable: false })
|
||||
manifest: AppManifest;
|
||||
|
||||
@Field(() => GraphQLJSON, { nullable: false })
|
||||
packageJson: PackageJson;
|
||||
|
||||
@Field(() => String, { nullable: false })
|
||||
yarnLock: string;
|
||||
}
|
||||
@@ -3,6 +3,11 @@ export type PackageJson = {
|
||||
standardId: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
engines: {
|
||||
node: string;
|
||||
npm: string;
|
||||
yarn: string;
|
||||
};
|
||||
icon?: string;
|
||||
version: string;
|
||||
dependencies?: object;
|
||||
|
||||
Reference in New Issue
Block a user