Migrate serverless function service to v2 (#17285)
# Introduction In this PR we're migrating the serverless function service that was using the SF repo directly to the v2 build and runner. The whole serverless engine now deals with flat entities only ## Resolvers Refactored the resolvers ( serverlessFunction, route, database and cron trigger) : - return types to `dto` - Standardized the flat to dto transpilation within the resolvers - Find and findMany passing by the cached data ## Services Refactored the services ( serverlessFunction, route, database and cron trigger) : - return type to be `flat` - always calling v2 and computing cache ## New additional caches - application variables ( cf https://github.com/twentyhq/core-team-issues/issues/2116 ) - serverless function layer ## What to test: - CRUD ( database trigger ✅ , route trigger, cron trigger, serverless function through workflows ✅ ) - Duplicating a workflow with a serverless function code node ✅ ## Concerns We need to implement the cron that will hard delete soft deleted s3 serverless functions, not in this PR though ( cf https://github.com/twentyhq/twenty/pull/17285#discussion_r2709168570 and https://github.com/twentyhq/core-team-issues/issues/2118 )
This commit is contained in:
+65
-26
@@ -1,9 +1,8 @@
|
||||
import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
@@ -11,13 +10,19 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { CreateRouteTriggerInput } from 'src/engine/metadata-modules/route-trigger/dtos/create-route-trigger.input';
|
||||
import { RouteTriggerIdInput } from 'src/engine/metadata-modules/route-trigger/dtos/route-trigger-id.input';
|
||||
import { RouteTriggerDTO } from 'src/engine/metadata-modules/route-trigger/dtos/route-trigger.dto';
|
||||
import { UpdateRouteTriggerInput } from 'src/engine/metadata-modules/route-trigger/dtos/update-route-trigger.input';
|
||||
import { RouteTriggerEntity } from 'src/engine/metadata-modules/route-trigger/route-trigger.entity';
|
||||
import {
|
||||
RouteTriggerException,
|
||||
RouteTriggerExceptionCode,
|
||||
} from 'src/engine/metadata-modules/route-trigger/exceptions/route-trigger.exception';
|
||||
import { RouteTriggerV2Service } from 'src/engine/metadata-modules/route-trigger/services/route-trigger-v2.service';
|
||||
import { fromFlatRouteTriggerToRouteTriggerDto } from 'src/engine/metadata-modules/route-trigger/utils/from-flat-route-trigger-to-route-trigger-dto.util';
|
||||
import { routeTriggerGraphQLApiExceptionHandler } from 'src/engine/metadata-modules/route-trigger/utils/route-trigger-graphql-api-exception-handler.utils';
|
||||
|
||||
@UseGuards(
|
||||
@@ -33,37 +38,59 @@ import { routeTriggerGraphQLApiExceptionHandler } from 'src/engine/metadata-modu
|
||||
export class RouteTriggerResolver {
|
||||
constructor(
|
||||
private readonly routeV2Service: RouteTriggerV2Service,
|
||||
@InjectRepository(RouteTriggerEntity)
|
||||
private readonly routeTriggerRepository: Repository<RouteTriggerEntity>,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
) {}
|
||||
|
||||
@Query(() => RouteTriggerDTO)
|
||||
async findOneRouteTrigger(
|
||||
@Args('input') { id }: RouteTriggerIdInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
) {
|
||||
): Promise<RouteTriggerDTO> {
|
||||
try {
|
||||
return await this.routeTriggerRepository.findOneOrFail({
|
||||
where: {
|
||||
id,
|
||||
workspaceId,
|
||||
},
|
||||
const { flatRouteTriggerMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatRouteTriggerMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const flatRouteTrigger = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: id,
|
||||
flatEntityMaps: flatRouteTriggerMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(flatRouteTrigger)) {
|
||||
throw new RouteTriggerException(
|
||||
`Route trigger with id ${id} not found`,
|
||||
RouteTriggerExceptionCode.ROUTE_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return fromFlatRouteTriggerToRouteTriggerDto(flatRouteTrigger);
|
||||
} catch (error) {
|
||||
routeTriggerGraphQLApiExceptionHandler(error);
|
||||
return routeTriggerGraphQLApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
@Query(() => [RouteTriggerDTO])
|
||||
async findManyRouteTriggers(
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
) {
|
||||
): Promise<RouteTriggerDTO[]> {
|
||||
try {
|
||||
return await this.routeTriggerRepository.find({
|
||||
where: { workspaceId },
|
||||
});
|
||||
const { flatRouteTriggerMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatRouteTriggerMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
return Object.values(flatRouteTriggerMaps.byId)
|
||||
.filter(isDefined)
|
||||
.map(fromFlatRouteTriggerToRouteTriggerDto);
|
||||
} catch (error) {
|
||||
routeTriggerGraphQLApiExceptionHandler(error);
|
||||
return routeTriggerGraphQLApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,14 +98,16 @@ export class RouteTriggerResolver {
|
||||
async deleteOneRouteTrigger(
|
||||
@Args('input') input: RouteTriggerIdInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
) {
|
||||
): Promise<RouteTriggerDTO> {
|
||||
try {
|
||||
return await this.routeV2Service.destroyOne({
|
||||
const flatRouteTrigger = await this.routeV2Service.destroyOne({
|
||||
destroyRouteTriggerInput: input,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatRouteTriggerToRouteTriggerDto(flatRouteTrigger);
|
||||
} catch (error) {
|
||||
routeTriggerGraphQLApiExceptionHandler(error);
|
||||
return routeTriggerGraphQLApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,11 +116,16 @@ export class RouteTriggerResolver {
|
||||
@Args('input')
|
||||
input: UpdateRouteTriggerInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
) {
|
||||
): Promise<RouteTriggerDTO> {
|
||||
try {
|
||||
return await this.routeV2Service.updateOne(input, workspaceId);
|
||||
const flatRouteTrigger = await this.routeV2Service.updateOne(
|
||||
input,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return fromFlatRouteTriggerToRouteTriggerDto(flatRouteTrigger);
|
||||
} catch (error) {
|
||||
routeTriggerGraphQLApiExceptionHandler(error);
|
||||
return routeTriggerGraphQLApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,11 +134,16 @@ export class RouteTriggerResolver {
|
||||
@Args('input')
|
||||
input: CreateRouteTriggerInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
) {
|
||||
): Promise<RouteTriggerDTO> {
|
||||
try {
|
||||
return await this.routeV2Service.createOne(input, workspaceId);
|
||||
const flatRouteTrigger = await this.routeV2Service.createOne(
|
||||
input,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return fromFlatRouteTriggerToRouteTriggerDto(flatRouteTrigger);
|
||||
} catch (error) {
|
||||
routeTriggerGraphQLApiExceptionHandler(error);
|
||||
return routeTriggerGraphQLApiExceptionHandler(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@ import {
|
||||
RouteTriggerException,
|
||||
RouteTriggerExceptionCode,
|
||||
} from 'src/engine/metadata-modules/route-trigger/exceptions/route-trigger.exception';
|
||||
import { FlatRouteTrigger } from 'src/engine/metadata-modules/route-trigger/types/flat-route-trigger.type';
|
||||
import { type FlatRouteTrigger } from 'src/engine/metadata-modules/route-trigger/types/flat-route-trigger.type';
|
||||
import { fromCreateRouteTriggerInputToFlatRouteTrigger } from 'src/engine/metadata-modules/route-trigger/utils/from-create-route-trigger-input-to-flat-route-trigger.util';
|
||||
import { fromUpdateRouteTriggerInputToFlatRouteTriggerToUpdateOrThrow } from 'src/engine/metadata-modules/route-trigger/utils/from-update-route-trigger-input-to-flat-route-trigger-to-update-or-throw.util';
|
||||
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
|
||||
@@ -34,7 +34,7 @@ export class RouteTriggerV2Service {
|
||||
* when interacting with another application than workspace custom one
|
||||
* */
|
||||
applicationId?: string,
|
||||
) {
|
||||
): Promise<FlatRouteTrigger> {
|
||||
const { workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{
|
||||
@@ -89,7 +89,7 @@ export class RouteTriggerV2Service {
|
||||
async updateOne(
|
||||
routeTriggerInput: UpdateRouteTriggerInput,
|
||||
workspaceId: string,
|
||||
) {
|
||||
): Promise<FlatRouteTrigger> {
|
||||
const { flatRouteTriggerMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { type RouteTriggerDTO } from 'src/engine/metadata-modules/route-trigger/dtos/route-trigger.dto';
|
||||
import { type FlatRouteTrigger } from 'src/engine/metadata-modules/route-trigger/types/flat-route-trigger.type';
|
||||
|
||||
export const fromFlatRouteTriggerToRouteTriggerDto = (
|
||||
flatRouteTrigger: FlatRouteTrigger,
|
||||
): RouteTriggerDTO => ({
|
||||
id: flatRouteTrigger.id,
|
||||
path: flatRouteTrigger.path,
|
||||
isAuthRequired: flatRouteTrigger.isAuthRequired,
|
||||
httpMethod: flatRouteTrigger.httpMethod,
|
||||
forwardedRequestHeaders: flatRouteTrigger.forwardedRequestHeaders,
|
||||
createdAt: new Date(flatRouteTrigger.createdAt),
|
||||
updatedAt: new Date(flatRouteTrigger.updatedAt),
|
||||
});
|
||||
+1
-1
@@ -7,7 +7,7 @@ import {
|
||||
RouteTriggerExceptionCode,
|
||||
} from 'src/engine/metadata-modules/route-trigger/exceptions/route-trigger.exception';
|
||||
|
||||
export const routeTriggerGraphQLApiExceptionHandler = (error: Error): void => {
|
||||
export const routeTriggerGraphQLApiExceptionHandler = (error: Error): never => {
|
||||
if (error instanceof RouteTriggerException) {
|
||||
switch (error.code) {
|
||||
case RouteTriggerExceptionCode.ROUTE_NOT_FOUND:
|
||||
|
||||
Reference in New Issue
Block a user