From 2daebc6d0f2dd3a969c72977f7dfe8ff0b1bdf43 Mon Sep 17 00:00:00 2001 From: Weiko Date: Tue, 27 Jan 2026 18:24:51 +0100 Subject: [PATCH] Add WorkspaceAuthContextMiddleware (#17487) ## Context Introduces a middleware that automatically sets the workspace auth context in AsyncLocalStorage for HTTP requests, making it available throughout the request lifecycle without explicit parameter passing. The motivation behind this change is to reduce boilerplate and simplify the developer experience when working with workspace data in HTTP request handlers. The Problem (Before) Every HTTP request handler that needed to access workspace data had to: - Extract auth-related info from decorators (@AuthWorkspace(), @AuthUserWorkspaceId(), etc.) in controller/resolver and pass down to services - Build or pass the authContext explicitly (sometimes with type assertion which was flaky) Then call executeInWorkspaceContext(authContext, async () => { ... }) ## Changes - Add WorkspaceAuthContextMiddleware that extracts auth context from the request and stores it in AsyncLocalStorage - Register middleware for GraphQL, metadata, and REST routes (runs after hydration middlewares) - Simplify executeInWorkspaceContext signature: fn is now the first parameter, authContext is optional second - If authContext is not provided, it's automatically retrieved from the storage (set by middleware) - Update all callers (~120 files) to use the new parameter order - Fixes a bug in search where system auth context was used, bypassing RLS feature. --- packages/twenty-server/src/app.module.ts | 13 +- .../__tests__/upgrade.command-runner.spec.ts | 2 +- .../workspaces-migration.command-runner.ts | 2 +- .../common-base-query-runner.service.ts | 2 +- .../actor-from-auth-context.service.spec.ts | 2 +- .../actor-from-auth-context.service.ts | 2 +- .../approved-access-domain.resolver.ts | 2 +- .../workspace-auth-context.middleware.ts | 33 ++ .../create-calendar-channel.service.ts | 2 +- .../create-connected-account.service.ts | 43 +- .../create-message-channel.service.ts | 2 +- .../auth/services/google-apis.service.spec.ts | 2 +- .../auth/services/google-apis.service.ts | 2 +- .../services/microsoft-apis.service.spec.ts | 2 +- .../auth/services/microsoft-apis.service.ts | 2 +- ...-connected-account-on-reconnect.service.ts | 41 +- .../storage/workspace-auth-context.storage.ts | 25 + .../services/access-token.service.spec.ts | 2 +- .../token/services/access-token.service.ts | 2 +- .../jobs/update-subscription-quantity.job.ts | 51 +-- .../timeline-calendar-event.service.spec.ts | 2 +- .../timeline-calendar-event.service.ts | 6 +- .../imap-smtp-caldav-connection.service.ts | 2 +- .../services/get-messages.service.ts | 4 +- .../services/timeline-messaging.service.ts | 6 +- .../__tests__/record-position.service.spec.ts | 2 +- .../services/record-position.service.ts | 33 +- .../search/services/search.service.ts | 4 - .../tools/send-email-tool/send-email-tool.ts | 4 +- .../user-workspace.service.spec.ts | 4 +- .../user-workspace/user-workspace.service.ts | 79 ++-- .../jobs/update-workspace-member-email.job.ts | 24 +- .../user/services/user.service.spec.ts | 2 +- .../user/services/user.service.ts | 33 +- ...flat-workspace-member-map-cache.service.ts | 2 +- .../engine/core-modules/user/user.resolver.ts | 2 +- .../workflow-trigger.controller.ts | 2 +- .../resolvers/workflow-trigger.resolver.ts | 2 +- .../workspace-invitation.resolver.ts | 4 +- .../services/agent-actor-context.service.ts | 2 +- .../navigation-menu-item.service.ts | 2 +- .../object-metadata.service.ts | 27 +- .../services/page-layout.service.ts | 35 +- .../user-role/user-role.service.ts | 2 +- .../__tests__/trash-cleanup.service.spec.ts | 2 +- .../services/trash-cleanup.service.ts | 2 +- .../global-workspace-orm.manager.ts | 6 +- .../twenty-standard-application.service.ts | 29 +- .../services/blocklist-validation.service.ts | 4 +- .../repositories/blocklist.repository.ts | 4 +- ...ocklist-item-delete-calendar-events.job.ts | 207 +++++---- .../blocklist-reimport-calendar-events.job.ts | 53 +-- .../calendar-event-cleaner.service.ts | 57 ++- ...lendar-trigger-event-list-fetch.command.ts | 103 ++--- .../jobs/calendar-event-list-fetch.job.ts | 93 ++-- .../jobs/calendar-events-import.job.ts | 91 ++-- .../jobs/calendar-ongoing-stale.job.ts | 103 ++--- ...ar-relaunch-failed-calendar-channel.job.ts | 60 ++- ...-event-import-exception-handler.service.ts | 35 +- .../calendar-events-import.service.ts | 196 ++++---- .../services/calendar-fetch-events.service.ts | 181 ++++---- .../services/calendar-save-events.service.ts | 425 +++++++++-------- .../calendar-event-participant.service.ts | 228 +++++----- ...ts-visibility-restrictions.service.spec.ts | 2 +- ...-events-visibility-restrictions.service.ts | 2 +- .../calendar-channel-sync-status.service.ts | 285 ++++++------ .../services/channel-sync.service.ts | 114 +++-- .../email-alias-manager.service.spec.ts | 2 +- .../services/email-alias-manager.service.ts | 29 +- ...workspace-member-connected-accounts.job.ts | 23 +- .../listeners/connected-account.listener.ts | 50 +- ...ected-account-delete-one.pre-query.hook.ts | 2 +- ...ted-account-refresh-tokens.service.spec.ts | 2 +- ...onnected-account-refresh-tokens.service.ts | 31 +- .../imap-smtp-caldav-apis.service.spec.ts | 2 +- .../services/imap-smtp-caldav-apis.service.ts | 4 +- .../__tests__/create-company.service.spec.ts | 2 +- .../create-company-and-contact.service.ts | 45 +- .../services/create-company.service.ts | 2 +- .../services/create-person.service.ts | 4 +- .../services/dashboard-sync.service.ts | 2 +- .../services/dashboard-duplication.service.ts | 2 +- .../dashboard-to-page-layout-sync.service.ts | 49 +- .../tools/create-complete-dashboard.tool.ts | 35 +- .../dashboard/tools/get-dashboard.tool.ts | 2 +- .../dashboard/tools/list-dashboards.tool.ts | 2 +- .../listeners/favorite-folder.listener.ts | 29 +- .../services/favorite-deletion.service.ts | 115 +++-- .../match-participant.service.ts | 141 +++--- ...ging-blocklist-item-delete-messages.job.ts | 219 +++++---- ...ssaging-blocklist-reimport-messages.job.ts | 51 +-- ...es-visibility-restrictions.service.spec.ts | 2 +- ...essages-visibility-restrictions.service.ts | 2 +- ...ssage-channel-update-one.pre-query.hook.ts | 2 +- .../message-channel-sync-status.service.ts | 358 +++++++-------- .../messaging-reset-channel.command.ts | 71 ++- .../messaging-message-cleaner.service.ts | 348 +++++++------- .../sync-message-folders.service.spec.ts | 4 +- .../services/sync-message-folders.service.ts | 2 +- ...ging-trigger-message-list-fetch.command.ts | 101 ++--- .../jobs/messaging-message-list-fetch.job.ts | 127 +++--- .../jobs/messaging-messages-import.job.ts | 99 ++-- .../jobs/messaging-ongoing-stale.job.ts | 103 ++--- ...ing-relaunch-failed-message-channel.job.ts | 59 ++- ...ssaging-message-list-fetch.service.spec.ts | 2 +- .../messaging-messages-import.service.spec.ts | 2 +- .../services/messaging-cursor.service.ts | 95 ++-- ...ing-delete-group-email-messages.service.ts | 2 +- ...saging-import-exception-handler.service.ts | 31 +- .../messaging-message-list-fetch.service.ts | 428 +++++++++--------- .../services/messaging-message.service.ts | 2 +- .../messaging-messages-import.service.ts | 299 ++++++------ ...essaging-process-folder-actions.service.ts | 2 +- ...ing-process-group-email-actions.service.ts | 130 +++--- ...d-enqueue-contact-creation.service.spec.ts | 2 +- ...es-and-enqueue-contact-creation.service.ts | 2 +- .../messaging-message-participant.service.ts | 101 ++--- ...channel-sync-status-monitoring.cron.job.ts | 2 +- .../note-post-query-hook.service.ts | 46 +- .../task-post-query-hook.service.ts | 46 +- ...meline-activity-from-internal-event.job.ts | 51 +-- .../timeline-activity.repository.ts | 129 +++--- .../services/timeline-activity.service.ts | 4 +- .../workflow-create-many.post-query.hook.ts | 59 ++- .../workflow-create-one.post-query.hook.ts | 45 +- .../workflow-common.workspace-service.ts | 123 +++-- ...ow-version-validation.workspace-service.ts | 107 +++-- ...low-version-edge.workspace-service.spec.ts | 4 +- ...workflow-version-edge.workspace-service.ts | 4 +- ...low-version-step.workspace-service.spec.ts | 2 +- ...-version-step-helpers.workspace-service.ts | 35 +- ...rsion-step-operations.workspace-service.ts | 6 +- .../workflow-version.workspace-service.ts | 96 ++-- .../delay/jobs/resume-delayed-workflow.job.ts | 123 +++-- .../workflow-runner/jobs/run-workflow.job.ts | 41 +- .../workflow-clean-workflow-runs.cron.job.ts | 37 +- ...ow-handle-staled-runs.workspace-service.ts | 65 ++- .../workflow-run-enqueue.workspace-service.ts | 2 +- .../workflow-throttling.workspace-service.ts | 4 +- .../command/delete-workflow-runs.command.ts | 59 ++- .../workflow-run.workspace-service.ts | 57 ++- .../workflow-statuses-update.job.spec.ts | 2 +- .../jobs/workflow-statuses-update.job.ts | 57 ++- .../tools/create-complete-workflow.tool.ts | 139 +++--- .../get-workflow-current-version.tool.ts | 2 +- .../automated-trigger.workspace-service.ts | 46 +- ...ow-database-event-trigger.listener.spec.ts | 2 +- ...orkflow-database-event-trigger.listener.ts | 179 ++++---- .../jobs/workflow-trigger.job.ts | 143 +++--- .../workflow-trigger.workspace-service.ts | 4 +- ...space-member-delete-one.post-query.hook.ts | 2 +- 151 files changed, 3743 insertions(+), 4002 deletions(-) create mode 100644 packages/twenty-server/src/engine/core-modules/auth/middlewares/workspace-auth-context.middleware.ts create mode 100644 packages/twenty-server/src/engine/core-modules/auth/storage/workspace-auth-context.storage.ts diff --git a/packages/twenty-server/src/app.module.ts b/packages/twenty-server/src/app.module.ts index d06fa2c5a56..33510d31a2d 100644 --- a/packages/twenty-server/src/app.module.ts +++ b/packages/twenty-server/src/app.module.ts @@ -23,6 +23,7 @@ import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module'; import { DataloaderModule } from 'src/engine/dataloaders/dataloader.module'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; +import { WorkspaceAuthContextMiddleware } from 'src/engine/core-modules/auth/middlewares/workspace-auth-context.middleware'; import { GraphQLHydrateRequestFromTokenMiddleware } from 'src/engine/middlewares/graphql-hydrate-request-from-token.middleware'; import { MiddlewareModule } from 'src/engine/middlewares/middleware.module'; import { RestCoreMiddleware } from 'src/engine/middlewares/rest-core.middleware'; @@ -111,16 +112,22 @@ export class AppModule { configure(consumer: MiddlewareConsumer) { consumer - .apply(GraphQLHydrateRequestFromTokenMiddleware) + .apply( + GraphQLHydrateRequestFromTokenMiddleware, + WorkspaceAuthContextMiddleware, + ) .forRoutes({ path: 'graphql', method: RequestMethod.ALL }); consumer - .apply(GraphQLHydrateRequestFromTokenMiddleware) + .apply( + GraphQLHydrateRequestFromTokenMiddleware, + WorkspaceAuthContextMiddleware, + ) .forRoutes({ path: 'metadata', method: RequestMethod.ALL }); for (const method of MIGRATED_REST_METHODS) { consumer - .apply(RestCoreMiddleware) + .apply(RestCoreMiddleware, WorkspaceAuthContextMiddleware) .forRoutes({ path: 'rest/*path', method }); } } diff --git a/packages/twenty-server/src/database/commands/command-runners/__tests__/upgrade.command-runner.spec.ts b/packages/twenty-server/src/database/commands/command-runners/__tests__/upgrade.command-runner.spec.ts index 69144983924..3d4ad1513d0 100644 --- a/packages/twenty-server/src/database/commands/command-runners/__tests__/upgrade.command-runner.spec.ts +++ b/packages/twenty-server/src/database/commands/command-runners/__tests__/upgrade.command-runner.spec.ts @@ -123,7 +123,7 @@ const buildUpgradeCommandModule = async ({ getDataSourceForWorkspace: jest.fn(), executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }, }, { diff --git a/packages/twenty-server/src/database/commands/command-runners/workspaces-migration.command-runner.ts b/packages/twenty-server/src/database/commands/command-runners/workspaces-migration.command-runner.ts index f5a8baa0db1..25093235106 100644 --- a/packages/twenty-server/src/database/commands/command-runners/workspaces-migration.command-runner.ts +++ b/packages/twenty-server/src/database/commands/command-runners/workspaces-migration.command-runner.ts @@ -142,7 +142,6 @@ export abstract class WorkspacesMigrationCommandRunner< const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceHasDataSource = await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceId( @@ -161,6 +160,7 @@ export abstract class WorkspacesMigrationCommandRunner< total: workspaceIdsToProcess.length, }); }, + authContext, ); this.migrationReport.success.push({ diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts index 32f29b5c4de..39ed915222a 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts @@ -141,7 +141,6 @@ export abstract class CommonBaseQueryRunnerService< } as CommonExtendedInput; return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => this.executeQueryAndEnrichResults( processedArgs, @@ -149,6 +148,7 @@ export abstract class CommonBaseQueryRunnerService< queryRunnerContext, commonQueryParser, ), + authContext, ); } diff --git a/packages/twenty-server/src/engine/core-modules/actor/services/__tests__/actor-from-auth-context.service.spec.ts b/packages/twenty-server/src/engine/core-modules/actor/services/__tests__/actor-from-auth-context.service.spec.ts index b7fed676657..8c93ff16cdb 100644 --- a/packages/twenty-server/src/engine/core-modules/actor/services/__tests__/actor-from-auth-context.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/actor/services/__tests__/actor-from-auth-context.service.spec.ts @@ -45,7 +45,7 @@ describe('ActorFromAuthContextService', () => { getRepository: jest.fn().mockResolvedValue(mockWorkspaceMemberRepository), executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }; beforeEach(async () => { diff --git a/packages/twenty-server/src/engine/core-modules/actor/services/actor-from-auth-context.service.ts b/packages/twenty-server/src/engine/core-modules/actor/services/actor-from-auth-context.service.ts index ebc72673a9a..ba2cdab51f6 100644 --- a/packages/twenty-server/src/engine/core-modules/actor/services/actor-from-auth-context.service.ts +++ b/packages/twenty-server/src/engine/core-modules/actor/services/actor-from-auth-context.service.ts @@ -165,7 +165,6 @@ export class ActorFromAuthContextService { if (isDefined(user)) { return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext as WorkspaceAuthContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -187,6 +186,7 @@ export class ActorFromAuthContextService { workspaceMemberId: workspaceMember.id, }); }, + authContext as WorkspaceAuthContext, ); } diff --git a/packages/twenty-server/src/engine/core-modules/approved-access-domain/approved-access-domain.resolver.ts b/packages/twenty-server/src/engine/core-modules/approved-access-domain/approved-access-domain.resolver.ts index debd49f3a16..0e9e5654d63 100644 --- a/packages/twenty-server/src/engine/core-modules/approved-access-domain/approved-access-domain.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/approved-access-domain/approved-access-domain.resolver.ts @@ -47,7 +47,6 @@ export class ApprovedAccessDomainResolver { const workspaceMember = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -62,6 +61,7 @@ export class ApprovedAccessDomainResolver { }, }); }, + authContext, ); return this.approvedAccessDomainService.createApprovedAccessDomain( diff --git a/packages/twenty-server/src/engine/core-modules/auth/middlewares/workspace-auth-context.middleware.ts b/packages/twenty-server/src/engine/core-modules/auth/middlewares/workspace-auth-context.middleware.ts new file mode 100644 index 00000000000..28a554ec490 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/auth/middlewares/workspace-auth-context.middleware.ts @@ -0,0 +1,33 @@ +import { Injectable, type NestMiddleware } from '@nestjs/common'; + +import { type NextFunction, type Request, type Response } from 'express'; +import { isDefined } from 'twenty-shared/utils'; + +import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; + +import { withWorkspaceAuthContext } from 'src/engine/core-modules/auth/storage/workspace-auth-context.storage'; + +@Injectable() +export class WorkspaceAuthContextMiddleware implements NestMiddleware { + use(req: Request, _res: Response, next: NextFunction) { + if (!isDefined(req.workspace)) { + next(); + + return; + } + + const authContext: WorkspaceAuthContext = { + user: req.user, + workspace: req.workspace, + workspaceMemberId: req.workspaceMemberId, + workspaceMember: req.workspaceMember, + userWorkspaceId: req.userWorkspaceId, + apiKey: req.apiKey, + application: req.application, + } as WorkspaceAuthContext; + + withWorkspaceAuthContext(authContext, () => { + next(); + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/create-calendar-channel.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/create-calendar-channel.service.ts index 9e2fd4b406f..e7ce8c8d21f 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/create-calendar-channel.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/create-calendar-channel.service.ts @@ -42,7 +42,6 @@ export class CreateCalendarChannelService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const calendarChannelRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -70,6 +69,7 @@ export class CreateCalendarChannelService { return newCalendarChannel.id; }, + authContext, ); } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/create-connected-account.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/create-connected-account.service.ts index 7c9af5d358a..b174a8c01ae 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/create-connected-account.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/create-connected-account.service.ts @@ -42,29 +42,26 @@ export class CreateConnectedAccountService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const connectedAccountRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'connectedAccount', - ); - - await connectedAccountRepository.save( - { - id: connectedAccountId, - handle, - provider, - accessToken, - refreshToken, - accountOwnerId, - scopes, - }, - {}, - manager, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const connectedAccountRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'connectedAccount', ); - }, - ); + + await connectedAccountRepository.save( + { + id: connectedAccountId, + handle, + provider, + accessToken, + refreshToken, + accountOwnerId, + scopes, + }, + {}, + manager, + ); + }, authContext); } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/create-message-channel.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/create-message-channel.service.ts index 42468d24588..09b14bcda9c 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/create-message-channel.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/create-message-channel.service.ts @@ -47,7 +47,6 @@ export class CreateMessageChannelService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const messageChannelRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -93,6 +92,7 @@ export class CreateMessageChannelService { return newMessageChannelId; }, + authContext, ); } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.spec.ts index 8be530dc6bf..af5ef9f2575 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.spec.ts @@ -98,7 +98,7 @@ describe('GoogleAPIsService', () => { .mockResolvedValue(mockWorkspaceDataSource), executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }, }, { diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts index b1ce2005bed..b880c4791ab 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts @@ -100,7 +100,6 @@ export class GoogleAPIsService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const connectedAccountRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -258,6 +257,7 @@ export class GoogleAPIsService { return newOrExistingConnectedAccountId; }, + authContext, ); } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.spec.ts index 7c27cdb3c77..ba5114910a1 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.spec.ts @@ -97,7 +97,7 @@ describe('MicrosoftAPIsService', () => { .mockResolvedValue(mockWorkspaceDataSource), executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }, }, { diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.ts index 0e928fb9c9c..f637168ecc5 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.ts @@ -81,7 +81,6 @@ export class MicrosoftAPIsService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const connectedAccountRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -247,6 +246,7 @@ export class MicrosoftAPIsService { return newOrExistingConnectedAccountId; }, + authContext, ); } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service.ts index 022dc9b3f4f..2f6c72cec65 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service.ts @@ -35,28 +35,25 @@ export class UpdateConnectedAccountOnReconnectService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const connectedAccountRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'connectedAccount', - ); - - await connectedAccountRepository.update( - { - id: connectedAccountId, - }, - { - accessToken, - refreshToken, - scopes, - authFailedAt: null, - }, - manager, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const connectedAccountRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'connectedAccount', ); - }, - ); + + await connectedAccountRepository.update( + { + id: connectedAccountId, + }, + { + accessToken, + refreshToken, + scopes, + authFailedAt: null, + }, + manager, + ); + }, authContext); } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/storage/workspace-auth-context.storage.ts b/packages/twenty-server/src/engine/core-modules/auth/storage/workspace-auth-context.storage.ts new file mode 100644 index 00000000000..cb2a19bb4ec --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/auth/storage/workspace-auth-context.storage.ts @@ -0,0 +1,25 @@ +import { AsyncLocalStorage } from 'async_hooks'; + +import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; + +export const workspaceAuthContextStorage = + new AsyncLocalStorage(); + +export const getWorkspaceAuthContext = (): WorkspaceAuthContext => { + const context = workspaceAuthContextStorage.getStore(); + + if (!context) { + throw new Error( + 'Workspace auth context not set. Operations must be wrapped with withWorkspaceAuthContext()', + ); + } + + return context; +}; + +export const withWorkspaceAuthContext = ( + context: WorkspaceAuthContext, + fn: () => T | Promise, +): T | Promise => { + return workspaceAuthContextStorage.run(context, fn); +}; diff --git a/packages/twenty-server/src/engine/core-modules/auth/token/services/access-token.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/token/services/access-token.service.spec.ts index 08426f27967..fbb271f8b0a 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/token/services/access-token.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/token/services/access-token.service.spec.ts @@ -82,7 +82,7 @@ describe('AccessTokenService', () => { getRepository: jest.fn(), executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }, }, ], diff --git a/packages/twenty-server/src/engine/core-modules/auth/token/services/access-token.service.ts b/packages/twenty-server/src/engine/core-modules/auth/token/services/access-token.service.ts index 0314994aa64..8f6c3c4340e 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/token/services/access-token.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/token/services/access-token.service.ts @@ -84,7 +84,6 @@ export class AccessTokenService { tokenWorkspaceMemberId = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -112,6 +111,7 @@ export class AccessTokenService { return workspaceMember.id; }, + authContext, ); } const userWorkspace = await this.userWorkspaceRepository.findOne({ diff --git a/packages/twenty-server/src/engine/core-modules/billing/jobs/update-subscription-quantity.job.ts b/packages/twenty-server/src/engine/core-modules/billing/jobs/update-subscription-quantity.job.ts index f64a9fac609..0de58a42629 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/jobs/update-subscription-quantity.job.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/jobs/update-subscription-quantity.job.ts @@ -29,36 +29,33 @@ export class UpdateSubscriptionQuantityJob { async handle(data: UpdateSubscriptionQuantityJobData): Promise { const authContext = buildSystemAuthContext(data.workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workspaceMemberRepository = - await this.globalWorkspaceOrmManager.getRepository( - data.workspaceId, - 'workspaceMember', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workspaceMemberRepository = + await this.globalWorkspaceOrmManager.getRepository( + data.workspaceId, + 'workspaceMember', + ); - const workspaceMembersCount = await workspaceMemberRepository.count(); + const workspaceMembersCount = await workspaceMemberRepository.count(); - if (!workspaceMembersCount || workspaceMembersCount <= 0) { - return; - } + if (!workspaceMembersCount || workspaceMembersCount <= 0) { + return; + } - try { - await this.billingSubscriptionUpdateService.changeSeats( - data.workspaceId, - workspaceMembersCount, - ); + try { + await this.billingSubscriptionUpdateService.changeSeats( + data.workspaceId, + workspaceMembersCount, + ); - this.logger.log( - `Updating workspace ${data.workspaceId} subscription quantity to ${workspaceMembersCount} members`, - ); - } catch (e) { - this.logger.warn( - `Failed to update workspace ${data.workspaceId} subscription quantity to ${workspaceMembersCount} members. Error: ${e}`, - ); - } - }, - ); + this.logger.log( + `Updating workspace ${data.workspaceId} subscription quantity to ${workspaceMembersCount} members`, + ); + } catch (e) { + this.logger.warn( + `Failed to update workspace ${data.workspaceId} subscription quantity to ${workspaceMembersCount} members. Error: ${e}`, + ); + } + }, authContext); } } diff --git a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.spec.ts b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.spec.ts index f1aa453f543..e0a02404c10 100644 --- a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.spec.ts @@ -40,7 +40,7 @@ describe('TimelineCalendarEventService', () => { getRepository: jest.fn().mockResolvedValue(mockCalendarEventRepository), executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }; const module: TestingModule = await Test.createTestingModule({ diff --git a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts index 7a6355f21f0..f2e9ea1d11a 100644 --- a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts +++ b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts @@ -35,7 +35,6 @@ export class TimelineCalendarEventService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const offset = (page - 1) * pageSize; @@ -166,6 +165,7 @@ export class TimelineCalendarEventService { timelineCalendarEvents, }; }, + authContext, ); } @@ -185,7 +185,6 @@ export class TimelineCalendarEventService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const personRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -222,6 +221,7 @@ export class TimelineCalendarEventService { return calendarEvents; }, + authContext, ); } @@ -241,7 +241,6 @@ export class TimelineCalendarEventService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const opportunityRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -276,6 +275,7 @@ export class TimelineCalendarEventService { return calendarEvents; }, + authContext, ); } } diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts index 24d8c9196a8..448384a5273 100644 --- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts +++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts @@ -187,7 +187,6 @@ export class ImapSmtpCaldavService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const connectedAccountRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -204,6 +203,7 @@ export class ImapSmtpCaldavService { return connectedAccount; }, + authContext, ); } } diff --git a/packages/twenty-server/src/engine/core-modules/messaging/services/get-messages.service.ts b/packages/twenty-server/src/engine/core-modules/messaging/services/get-messages.service.ts index 492a329f9b3..9967ae8e2c1 100644 --- a/packages/twenty-server/src/engine/core-modules/messaging/services/get-messages.service.ts +++ b/packages/twenty-server/src/engine/core-modules/messaging/services/get-messages.service.ts @@ -77,7 +77,6 @@ export class GetMessagesService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const personRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -113,6 +112,7 @@ export class GetMessagesService { return messageThreads; }, + authContext, ); } @@ -126,7 +126,6 @@ export class GetMessagesService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const opportunityRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -161,6 +160,7 @@ export class GetMessagesService { return messageThreads; }, + authContext, ); } } diff --git a/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts b/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts index 203868a9ea4..28555ec8e23 100644 --- a/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts +++ b/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts @@ -35,7 +35,6 @@ export class TimelineMessagingService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const messageThreadRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -99,6 +98,7 @@ export class TimelineMessagingService { totalNumberOfThreads, }; }, + authContext, ); } @@ -111,7 +111,6 @@ export class TimelineMessagingService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const messageParticipantRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -198,6 +197,7 @@ export class TimelineMessagingService { {}, ); }, + authContext, ); } @@ -211,7 +211,6 @@ export class TimelineMessagingService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const messageThreadRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -267,6 +266,7 @@ export class TimelineMessagingService { return threadVisibilityByThreadId; }, + authContext, ); } } diff --git a/packages/twenty-server/src/engine/core-modules/record-position/services/__tests__/record-position.service.spec.ts b/packages/twenty-server/src/engine/core-modules/record-position/services/__tests__/record-position.service.spec.ts index 996d0ed4de1..c883e7763ba 100644 --- a/packages/twenty-server/src/engine/core-modules/record-position/services/__tests__/record-position.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/record-position/services/__tests__/record-position.service.spec.ts @@ -20,7 +20,7 @@ describe('RecordPositionService', () => { getRepository: jest.fn().mockResolvedValue(mockRepository), executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), } as unknown as jest.Mocked; const module: TestingModule = await Test.createTestingModule({ diff --git a/packages/twenty-server/src/engine/core-modules/record-position/services/record-position.service.ts b/packages/twenty-server/src/engine/core-modules/record-position/services/record-position.service.ts index cc102cd3c4d..2abca782c78 100644 --- a/packages/twenty-server/src/engine/core-modules/record-position/services/record-position.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-position/services/record-position.service.ts @@ -163,7 +163,6 @@ export class RecordPositionService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const repository = await this.globalWorkspaceOrmManager.getRepository( workspaceId, @@ -179,6 +178,7 @@ export class RecordPositionService { return record ? { id: record.id, position: record.position } : null; }, + authContext, ); } @@ -190,22 +190,19 @@ export class RecordPositionService { ): Promise { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const repository = await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - objectMetadata.nameSingular, - { - shouldBypassPermissionChecks: true, - }, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const repository = await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + objectMetadata.nameSingular, + { + shouldBypassPermissionChecks: true, + }, + ); - await repository.update(recordId, { - position: positionValue, - }); - }, - ); + await repository.update(recordId, { + position: positionValue, + }); + }, authContext); } private async findMinPosition( @@ -216,7 +213,6 @@ export class RecordPositionService { const result = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const repository = await this.globalWorkspaceOrmManager.getRepository( workspaceId, @@ -228,6 +224,7 @@ export class RecordPositionService { return await repository.minimum('position'); }, + authContext, ); return sanitizeNumber(result); @@ -241,7 +238,6 @@ export class RecordPositionService { const result = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const repository = await this.globalWorkspaceOrmManager.getRepository( workspaceId, @@ -253,6 +249,7 @@ export class RecordPositionService { return await repository.maximum('position'); }, + authContext, ); return sanitizeNumber(result); diff --git a/packages/twenty-server/src/engine/core-modules/search/services/search.service.ts b/packages/twenty-server/src/engine/core-modules/search/services/search.service.ts index 46cb552895b..21630733041 100644 --- a/packages/twenty-server/src/engine/core-modules/search/services/search.service.ts +++ b/packages/twenty-server/src/engine/core-modules/search/services/search.service.ts @@ -33,7 +33,6 @@ import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-me import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; -import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; type LastRanks = { tsRankCD: number; tsRank: number }; @@ -82,13 +81,10 @@ export class SearchService { OBJECT_METADATA_ITEMS_CHUNK_SIZE, ); - const authContext = buildSystemAuthContext(workspaceId); - for (const objectMetadataItemChunk of filteredObjectMetadataItemsChunks) { const recordsWithObjectMetadataItems = await Promise.all( objectMetadataItemChunk.map(async (flatObjectMetadata) => { return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const repository = await this.globalWorkspaceOrmManager.getRepository( diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.ts index 638dfc842ee..b7505cdff1c 100644 --- a/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.ts +++ b/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.ts @@ -65,7 +65,6 @@ export class SendEmailTool implements Tool { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const connectedAccountRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -91,6 +90,7 @@ export class SendEmailTool implements Tool { return connectedAccount; }, + authContext, ); } @@ -100,7 +100,6 @@ export class SendEmailTool implements Tool { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const connectedAccountRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -118,6 +117,7 @@ export class SendEmailTool implements Tool { return allAccounts[0].id; }, + authContext, ); } diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts index 90cb9602756..32dc54311aa 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts @@ -106,7 +106,9 @@ describe('UserWorkspaceService', () => { useValue: { executeInWorkspaceContext: jest .fn() - .mockImplementation(async (_authContext, callback) => callback()), + .mockImplementation( + async (callback: () => any, _authContext?: any) => callback(), + ), getRepository: jest.fn(), }, }, diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts index a9e5f7f352f..0bce22a49df 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts @@ -95,47 +95,44 @@ export class UserWorkspaceService extends TypeOrmQueryService { - const workspaceMemberRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workspaceMember', - { shouldBypassPermissionChecks: true }, - ); - - const userWorkspace = await this.userWorkspaceRepository.findOneOrFail({ - where: { - userId: user.id, - workspaceId, - }, - }); - - await workspaceMemberRepository.insert({ - name: { - firstName: user.firstName, - lastName: user.lastName, - }, - colorScheme: 'System', - userId: user.id, - userEmail: user.email, - avatarUrl: userWorkspace.defaultAvatarUrl ?? '', - locale: (user.locale ?? SOURCE_LOCALE) as keyof typeof APP_LOCALES, - }); - - const workspaceMember = await workspaceMemberRepository.find({ - where: { - userId: user.id, - }, - }); - - assert( - workspaceMember?.length === 1, - `Error while creating workspace member ${user.email} on workspace ${workspaceId}`, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workspaceMemberRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); - }, - ); + + const userWorkspace = await this.userWorkspaceRepository.findOneOrFail({ + where: { + userId: user.id, + workspaceId, + }, + }); + + await workspaceMemberRepository.insert({ + name: { + firstName: user.firstName, + lastName: user.lastName, + }, + colorScheme: 'System', + userId: user.id, + userEmail: user.email, + avatarUrl: userWorkspace.defaultAvatarUrl ?? '', + locale: (user.locale ?? SOURCE_LOCALE) as keyof typeof APP_LOCALES, + }); + + const workspaceMember = await workspaceMemberRepository.find({ + where: { + userId: user.id, + }, + }); + + assert( + workspaceMember?.length === 1, + `Error while creating workspace member ${user.email} on workspace ${workspaceId}`, + ); + }, authContext); } async addUserToWorkspaceIfUserNotInWorkspace( @@ -356,7 +353,6 @@ export class UserWorkspaceService extends TypeOrmQueryService { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -377,6 +373,7 @@ export class UserWorkspaceService extends TypeOrmQueryService { - const workspaceMemberRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspace.id, - 'workspaceMember', - { shouldBypassPermissionChecks: true }, - ); - - await workspaceMemberRepository.update( - { userId }, - { userEmail: email }, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workspaceMemberRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspace.id, + 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); - }, - ); + + await workspaceMemberRepository.update({ userId }, { userEmail: email }); + }, authContext); } } diff --git a/packages/twenty-server/src/engine/core-modules/user/services/user.service.spec.ts b/packages/twenty-server/src/engine/core-modules/user/services/user.service.spec.ts index 8a9c835ebc3..edbe483234a 100644 --- a/packages/twenty-server/src/engine/core-modules/user/services/user.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/user/services/user.service.spec.ts @@ -76,7 +76,7 @@ describe('UserService', () => { executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }, }, { diff --git a/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts b/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts index c469a0b904c..1dc9abd5b9f 100644 --- a/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts +++ b/packages/twenty-server/src/engine/core-modules/user/services/user.service.ts @@ -66,7 +66,6 @@ export class UserService extends TypeOrmQueryService { const authContext = buildSystemAuthContext(workspace.id); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -81,6 +80,7 @@ export class UserService extends TypeOrmQueryService { }, }); }, + authContext, ); } @@ -92,7 +92,6 @@ export class UserService extends TypeOrmQueryService { const authContext = buildSystemAuthContext(workspace.id); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -105,6 +104,7 @@ export class UserService extends TypeOrmQueryService { withDeleted: withDeleted, }); }, + authContext, ); } @@ -116,7 +116,6 @@ export class UserService extends TypeOrmQueryService { const authContext = buildSystemAuthContext(workspace.id); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -130,6 +129,7 @@ export class UserService extends TypeOrmQueryService { withDeleted: true, }); }, + authContext, ); } @@ -202,7 +202,6 @@ export class UserService extends TypeOrmQueryService { const workspaceMembers = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -213,6 +212,7 @@ export class UserService extends TypeOrmQueryService { return workspaceMemberRepository.find(); }, + authContext, ); const userWorkspaceId = userWorkspace.id; @@ -255,21 +255,18 @@ export class UserService extends TypeOrmQueryService { assert(workspaceMember, 'WorkspaceMember not found'); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workspaceMemberRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workspaceMember', - { shouldBypassPermissionChecks: true }, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workspaceMemberRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workspaceMember', + { shouldBypassPermissionChecks: true }, + ); - await workspaceMemberRepository.delete({ - userId: userWorkspace.userId, - }); - }, - ); + await workspaceMemberRepository.delete({ + userId: userWorkspace.userId, + }); + }, authContext); await this.userWorkspaceService.deleteUserWorkspace({ userWorkspaceId, diff --git a/packages/twenty-server/src/engine/core-modules/user/services/workspace-flat-workspace-member-map-cache.service.ts b/packages/twenty-server/src/engine/core-modules/user/services/workspace-flat-workspace-member-map-cache.service.ts index 75060ca168b..fb4c2e4354f 100644 --- a/packages/twenty-server/src/engine/core-modules/user/services/workspace-flat-workspace-member-map-cache.service.ts +++ b/packages/twenty-server/src/engine/core-modules/user/services/workspace-flat-workspace-member-map-cache.service.ts @@ -20,7 +20,6 @@ export class WorkspaceFlatWorkspaceMemberMapCacheService extends WorkspaceCacheP async computeForCache(workspaceId: string): Promise { const flatWorkspaceMemberMaps = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - buildSystemAuthContext(workspaceId), async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -45,6 +44,7 @@ export class WorkspaceFlatWorkspaceMemberMapCacheService extends WorkspaceCacheP return flatWorkspaceMemberMaps; }, + buildSystemAuthContext(workspaceId), ); return flatWorkspaceMemberMaps; diff --git a/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts b/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts index d5a638563cd..bad9bacb0c1 100644 --- a/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts @@ -391,7 +391,6 @@ export class UserResolver { const workspaceMemberToDelete = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -406,6 +405,7 @@ export class UserResolver { }, }); }, + authContext, ); if (!isDefined(workspaceMemberToDelete)) { diff --git a/packages/twenty-server/src/engine/core-modules/workflow/controllers/workflow-trigger.controller.ts b/packages/twenty-server/src/engine/core-modules/workflow/controllers/workflow-trigger.controller.ts index e9fdd9922cc..829c728ce2e 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/controllers/workflow-trigger.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/controllers/workflow-trigger.controller.ts @@ -93,7 +93,6 @@ export class WorkflowTriggerController { const { workflow } = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -156,6 +155,7 @@ export class WorkflowTriggerController { return { workflow, workflowVersion }; }, + authContext, ); const { workflowRunId } = diff --git a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts index 9913dd94d2d..ece7ac691f9 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts @@ -77,7 +77,6 @@ export class WorkflowTriggerResolver { const workspaceMember = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -92,6 +91,7 @@ export class WorkflowTriggerResolver { }, }); }, + authContext, ); return this.workflowTriggerWorkspaceService.runWorkflowVersion({ diff --git a/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts b/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts index 612c521b547..2c22934528f 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts @@ -60,7 +60,6 @@ export class WorkspaceInvitationResolver { const workspaceMember = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -75,6 +74,7 @@ export class WorkspaceInvitationResolver { }, }); }, + authContext, ); return this.workspaceInvitationService.resendWorkspaceInvitation( @@ -100,7 +100,6 @@ export class WorkspaceInvitationResolver { const workspaceMember = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -115,6 +114,7 @@ export class WorkspaceInvitationResolver { }, }); }, + authContext, ); return await this.workspaceInvitationService.sendInvitations( diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-actor-context.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-actor-context.service.ts index b0f4b436d3c..4c8e658ddf6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-actor-context.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-actor-context.service.ts @@ -46,7 +46,6 @@ export class AgentActorContextService { const workspaceMember = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -61,6 +60,7 @@ export class AgentActorContextService { }, }); }, + authContext, ); if (!workspaceMember) { diff --git a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts index d0f559b1a3c..b0a13d0c7e1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts @@ -427,7 +427,6 @@ export class NavigationMenuItemService { const record = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const repository = await this.globalWorkspaceOrmManager.getRepository( @@ -462,6 +461,7 @@ export class NavigationMenuItemService { return formattedRecord; }, + authContext, ); if (!isDefined(record)) { diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts index 2f97cd5c340..cbe1a35773c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts @@ -526,23 +526,20 @@ export class ObjectMetadataService extends TypeOrmQueryService { - const favoriteRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'favorite', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const favoriteRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'favorite', + ); - const favoriteCount = await favoriteRepository.count(); + const favoriteCount = await favoriteRepository.count(); - await favoriteRepository.insert({ - viewId: view.id, - position: favoriteCount, - }); - }, - ); + await favoriteRepository.insert({ + viewId: view.id, + position: favoriteCount, + }); + }, authContext); } public async deleteWorkspaceAllObjectMetadata({ diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout.service.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout.service.ts index cb822dc46d8..904c48697ad 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout.service.ts @@ -410,26 +410,23 @@ export class PageLayoutService { }): Promise { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const dashboardRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'dashboard', - { shouldBypassPermissionChecks: true }, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const dashboardRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'dashboard', + { shouldBypassPermissionChecks: true }, + ); - const dashboards = await dashboardRepository.find({ - where: { - pageLayoutId, - }, - }); + const dashboards = await dashboardRepository.find({ + where: { + pageLayoutId, + }, + }); - for (const dashboard of dashboards) { - await dashboardRepository.delete(dashboard.id); - } - }, - ); + for (const dashboard of dashboards) { + await dashboardRepository.delete(dashboard.id); + } + }, authContext); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts b/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts index 4000b6553f1..6ed0f6758ee 100644 --- a/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts @@ -154,7 +154,6 @@ export class UserRoleService { ); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -171,6 +170,7 @@ export class UserRoleService { return workspaceMembers; }, + authContext, ); } diff --git a/packages/twenty-server/src/engine/trash-cleanup/services/__tests__/trash-cleanup.service.spec.ts b/packages/twenty-server/src/engine/trash-cleanup/services/__tests__/trash-cleanup.service.spec.ts index fc6520e1fba..11944d0a795 100644 --- a/packages/twenty-server/src/engine/trash-cleanup/services/__tests__/trash-cleanup.service.spec.ts +++ b/packages/twenty-server/src/engine/trash-cleanup/services/__tests__/trash-cleanup.service.spec.ts @@ -18,7 +18,7 @@ describe('TrashCleanupService', () => { getRepository: jest.fn(), executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }; const module: TestingModule = await Test.createTestingModule({ diff --git a/packages/twenty-server/src/engine/trash-cleanup/services/trash-cleanup.service.ts b/packages/twenty-server/src/engine/trash-cleanup/services/trash-cleanup.service.ts index 22a76eaa40a..04f38bf5ce1 100644 --- a/packages/twenty-server/src/engine/trash-cleanup/services/trash-cleanup.service.ts +++ b/packages/twenty-server/src/engine/trash-cleanup/services/trash-cleanup.service.ts @@ -100,7 +100,6 @@ export class TrashCleanupService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const repository = await this.globalWorkspaceOrmManager.getRepository( workspaceId, @@ -137,6 +136,7 @@ export class TrashCleanupService { return deleted; }, + authContext, ); } diff --git a/packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager.ts b/packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager.ts index 2f6627697b2..3f9bee83892 100644 --- a/packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager.ts +++ b/packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager.ts @@ -4,6 +4,7 @@ import { type ObjectLiteral } from 'typeorm'; import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; +import { getWorkspaceAuthContext } from 'src/engine/core-modules/auth/storage/workspace-auth-context.storage'; import { buildObjectIdByNameMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/build-object-id-by-name-maps.util'; import { GlobalWorkspaceDataSource } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource'; import { GlobalWorkspaceDataSourceService } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource.service'; @@ -67,10 +68,11 @@ export class GlobalWorkspaceOrmManager { } async executeInWorkspaceContext( - authContext: WorkspaceAuthContext, fn: () => T | Promise, + authContext?: WorkspaceAuthContext, ): Promise { - const context = await this.loadWorkspaceContext(authContext); + const resolvedAuthContext = authContext ?? getWorkspaceAuthContext(); + const context = await this.loadWorkspaceContext(resolvedAuthContext); return withWorkspaceContext(context, fn); } diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/services/twenty-standard-application.service.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/services/twenty-standard-application.service.ts index d8264fff1fc..e31f1259392 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/services/twenty-standard-application.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/services/twenty-standard-application.service.ts @@ -41,24 +41,21 @@ export class TwentyStandardApplicationService { }) { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const favoriteRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'favorite', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const favoriteRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'favorite', + ); - const favoriteCount = await favoriteRepository.count(); - const favoriteToCreate = flatViews.map((flatView, index) => ({ - viewId: flatView.id, - position: favoriteCount + index, - })); + const favoriteCount = await favoriteRepository.count(); + const favoriteToCreate = flatViews.map((flatView, index) => ({ + viewId: flatView.id, + position: favoriteCount + index, + })); - await favoriteRepository.insert(favoriteToCreate); - }, - ); + await favoriteRepository.insert(favoriteToCreate); + }, authContext); } private async createManyNavigationMenuItem({ diff --git a/packages/twenty-server/src/modules/blocklist/blocklist-validation-manager/services/blocklist-validation.service.ts b/packages/twenty-server/src/modules/blocklist/blocklist-validation-manager/services/blocklist-validation.service.ts index 33aee25cfdd..efab0c63703 100644 --- a/packages/twenty-server/src/modules/blocklist/blocklist-validation-manager/services/blocklist-validation.service.ts +++ b/packages/twenty-server/src/modules/blocklist/blocklist-validation-manager/services/blocklist-validation.service.ts @@ -88,7 +88,6 @@ export class BlocklistValidationService { const currentWorkspaceMember = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -100,6 +99,7 @@ export class BlocklistValidationService { userId, }); }, + authContext, ); const currentBlocklist = @@ -145,7 +145,6 @@ export class BlocklistValidationService { const currentWorkspaceMember = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -157,6 +156,7 @@ export class BlocklistValidationService { userId, }); }, + authContext, ); const currentBlocklist = diff --git a/packages/twenty-server/src/modules/blocklist/repositories/blocklist.repository.ts b/packages/twenty-server/src/modules/blocklist/repositories/blocklist.repository.ts index 4b9545122c0..8af5f143777 100644 --- a/packages/twenty-server/src/modules/blocklist/repositories/blocklist.repository.ts +++ b/packages/twenty-server/src/modules/blocklist/repositories/blocklist.repository.ts @@ -17,7 +17,6 @@ export class BlocklistRepository { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const blockListRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -32,6 +31,7 @@ export class BlocklistRepository { id, }); }, + authContext, ); } @@ -42,7 +42,6 @@ export class BlocklistRepository { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const blockListRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -56,6 +55,7 @@ export class BlocklistRepository { }, }); }, + authContext, ); } } diff --git a/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-item-delete-calendar-events.job.ts b/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-item-delete-calendar-events.job.ts index 718e922f562..e64e15ff7b4 100644 --- a/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-item-delete-calendar-events.job.ts +++ b/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-item-delete-calendar-events.job.ts @@ -35,126 +35,123 @@ export class BlocklistItemDeleteCalendarEventsJob { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const blocklistItemIds = data.events.map( - (eventPayload) => eventPayload.recordId, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const blocklistItemIds = data.events.map( + (eventPayload) => eventPayload.recordId, + ); + + const blocklistRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'blocklist', ); - const blocklistRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'blocklist', - ); + const blocklist = await blocklistRepository.find({ + where: { + id: Any(blocklistItemIds), + }, + }); - const blocklist = await blocklistRepository.find({ - where: { - id: Any(blocklistItemIds), + const handlesToDeleteByWorkspaceMemberIdMap = blocklist.reduce( + (acc, blocklistItem) => { + const { handle, workspaceMemberId } = blocklistItem; + + if (!acc.has(workspaceMemberId)) { + acc.set(workspaceMemberId, []); + } + + if (!isDefined(handle)) { + return acc; + } + + acc.get(workspaceMemberId)?.push(handle); + + return acc; + }, + new Map(), + ); + + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', + ); + + const calendarChannelEventAssociationRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannelEventAssociation', + ); + + for (const workspaceMemberId of handlesToDeleteByWorkspaceMemberIdMap.keys()) { + const handles = + handlesToDeleteByWorkspaceMemberIdMap.get(workspaceMemberId); + + if (!handles) { + continue; + } + + const calendarChannels = await calendarChannelRepository.find({ + select: { + id: true, + handle: true, + connectedAccount: { + handleAliases: true, + }, }, + where: { + connectedAccount: { + accountOwnerId: workspaceMemberId, + }, + }, + relations: ['connectedAccount'], }); - const handlesToDeleteByWorkspaceMemberIdMap = blocklist.reduce( - (acc, blocklistItem) => { - const { handle, workspaceMemberId } = blocklistItem; + for (const calendarChannel of calendarChannels) { + const calendarChannelHandles = [calendarChannel.handle]; - if (!acc.has(workspaceMemberId)) { - acc.set(workspaceMemberId, []); - } + if (calendarChannel.connectedAccount.handleAliases) { + calendarChannelHandles.push( + ...calendarChannel.connectedAccount.handleAliases.split(','), + ); + } - if (!isDefined(handle)) { - return acc; - } + const handleConditions = handles.map((handle) => { + const isHandleDomain = handle.startsWith('@'); - acc.get(workspaceMemberId)?.push(handle); + return isHandleDomain + ? { + handle: And( + Or(ILike(`%${handle}`), ILike(`%.${handle.slice(1)}`)), + Not(In(calendarChannelHandles)), + ), + } + : { handle }; + }); - return acc; - }, - new Map(), - ); + const calendarEventsAssociationsToDelete = + await calendarChannelEventAssociationRepository.find({ + where: { + calendarChannelId: calendarChannel.id, + calendarEvent: { + calendarEventParticipants: handleConditions, + }, + }, + }); - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); - - const calendarChannelEventAssociationRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannelEventAssociation', - ); - - for (const workspaceMemberId of handlesToDeleteByWorkspaceMemberIdMap.keys()) { - const handles = - handlesToDeleteByWorkspaceMemberIdMap.get(workspaceMemberId); - - if (!handles) { + if (calendarEventsAssociationsToDelete.length === 0) { continue; } - const calendarChannels = await calendarChannelRepository.find({ - select: { - id: true, - handle: true, - connectedAccount: { - handleAliases: true, - }, - }, - where: { - connectedAccount: { - accountOwnerId: workspaceMemberId, - }, - }, - relations: ['connectedAccount'], - }); - - for (const calendarChannel of calendarChannels) { - const calendarChannelHandles = [calendarChannel.handle]; - - if (calendarChannel.connectedAccount.handleAliases) { - calendarChannelHandles.push( - ...calendarChannel.connectedAccount.handleAliases.split(','), - ); - } - - const handleConditions = handles.map((handle) => { - const isHandleDomain = handle.startsWith('@'); - - return isHandleDomain - ? { - handle: And( - Or(ILike(`%${handle}`), ILike(`%.${handle.slice(1)}`)), - Not(In(calendarChannelHandles)), - ), - } - : { handle }; - }); - - const calendarEventsAssociationsToDelete = - await calendarChannelEventAssociationRepository.find({ - where: { - calendarChannelId: calendarChannel.id, - calendarEvent: { - calendarEventParticipants: handleConditions, - }, - }, - }); - - if (calendarEventsAssociationsToDelete.length === 0) { - continue; - } - - await calendarChannelEventAssociationRepository.delete( - calendarEventsAssociationsToDelete.map(({ id }) => id), - ); - } + await calendarChannelEventAssociationRepository.delete( + calendarEventsAssociationsToDelete.map(({ id }) => id), + ); } + } - await this.calendarEventCleanerService.cleanWorkspaceCalendarEvents( - workspaceId, - ); - }, - ); + await this.calendarEventCleanerService.cleanWorkspaceCalendarEvents( + workspaceId, + ); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-reimport-calendar-events.job.ts b/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-reimport-calendar-events.job.ts index 6cc56b34798..9c6a77dc1c9 100644 --- a/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-reimport-calendar-events.job.ts +++ b/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-reimport-calendar-events.job.ts @@ -36,37 +36,34 @@ export class BlocklistReimportCalendarEventsJob { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', + ); - for (const eventPayload of data.events) { - const workspaceMemberId = - eventPayload.properties.before.workspaceMemberId; + for (const eventPayload of data.events) { + const workspaceMemberId = + eventPayload.properties.before.workspaceMemberId; - const calendarChannels = await calendarChannelRepository.find({ - select: ['id'], - where: { - connectedAccount: { - accountOwnerId: workspaceMemberId, - }, - syncStage: Not( - CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, - ), + const calendarChannels = await calendarChannelRepository.find({ + select: ['id'], + where: { + connectedAccount: { + accountOwnerId: workspaceMemberId, }, - }); + syncStage: Not( + CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, + ), + }, + }); - await this.calendarChannelSyncStatusService.resetAndMarkAsCalendarEventListFetchPending( - calendarChannels.map((calendarChannel) => calendarChannel.id), - workspaceId, - ); - } - }, - ); + await this.calendarChannelSyncStatusService.resetAndMarkAsCalendarEventListFetchPending( + calendarChannels.map((calendarChannel) => calendarChannel.id), + workspaceId, + ); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/services/calendar-event-cleaner.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/services/calendar-event-cleaner.service.ts index e5373fdfe5e..ac689389e01 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/services/calendar-event-cleaner.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/services/calendar-event-cleaner.service.ts @@ -15,37 +15,34 @@ export class CalendarEventCleanerService { public async cleanWorkspaceCalendarEvents(workspaceId: string) { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarEventRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarEvent', - ); - - await deleteUsingPagination( + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarEventRepository = + await this.globalWorkspaceOrmManager.getRepository( workspaceId, - 500, - async (limit, offset) => { - const nonAssociatedCalendarEvents = - await calendarEventRepository.find({ - where: { - calendarChannelEventAssociations: { - id: IsNull(), - }, - }, - take: limit, - skip: offset, - }); - - return nonAssociatedCalendarEvents.map(({ id }) => id); - }, - async (ids) => { - await calendarEventRepository.delete({ id: Any(ids) }); - }, + 'calendarEvent', ); - }, - ); + + await deleteUsingPagination( + workspaceId, + 500, + async (limit, offset) => { + const nonAssociatedCalendarEvents = + await calendarEventRepository.find({ + where: { + calendarChannelEventAssociations: { + id: IsNull(), + }, + }, + take: limit, + skip: offset, + }); + + return nonAssociatedCalendarEvents.map(({ id }) => id); + }, + async (ids) => { + await calendarEventRepository.delete({ id: Any(ids) }); + }, + ); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/commands/calendar-trigger-event-list-fetch.command.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/commands/calendar-trigger-event-list-fetch.command.ts index 2d65c265387..98ebe02d1f9 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/commands/calendar-trigger-event-list-fetch.command.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/commands/calendar-trigger-event-list-fetch.command.ts @@ -51,64 +51,61 @@ export class CalendarTriggerEventListFetchCommand extends CommandRunner { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', + ); + + const whereCondition: Record = { + isSyncEnabled: true, + syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, + }; + + if (calendarChannelId) { + whereCondition.id = calendarChannelId; + } + + const calendarChannels = + await calendarChannelRepository.find(whereCondition); + + if (calendarChannels.length === 0) { + this.logger.warn( + 'No calendar channels found with CALENDAR_EVENT_LIST_FETCH_PENDING status', + ); + + return; + } + + this.logger.log( + `Found ${calendarChannels.length} calendar channel(s) to process`, + ); + + for (const calendarChannel of calendarChannels) { + await calendarChannelRepository.update(calendarChannel.id, { + syncStage: + CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED, + syncStageStartedAt: new Date().toISOString(), + }); + + await this.messageQueueService.add( + CalendarEventListFetchJob.name, + { + calendarChannelId: calendarChannel.id, workspaceId, - 'calendarChannel', - ); - - const whereCondition: Record = { - isSyncEnabled: true, - syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, - }; - - if (calendarChannelId) { - whereCondition.id = calendarChannelId; - } - - const calendarChannels = - await calendarChannelRepository.find(whereCondition); - - if (calendarChannels.length === 0) { - this.logger.warn( - 'No calendar channels found with CALENDAR_EVENT_LIST_FETCH_PENDING status', - ); - - return; - } - - this.logger.log( - `Found ${calendarChannels.length} calendar channel(s) to process`, + }, ); - for (const calendarChannel of calendarChannels) { - await calendarChannelRepository.update(calendarChannel.id, { - syncStage: - CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED, - syncStageStartedAt: new Date().toISOString(), - }); - - await this.messageQueueService.add( - CalendarEventListFetchJob.name, - { - calendarChannelId: calendarChannel.id, - workspaceId, - }, - ); - - this.logger.log( - `Triggered fetch for calendar channel ${calendarChannel.id}`, - ); - } - this.logger.log( - `Successfully triggered ${calendarChannels.length} calendar event list fetch job(s)`, + `Triggered fetch for calendar channel ${calendarChannel.id}`, ); - }, - ); + } + + this.logger.log( + `Successfully triggered ${calendarChannels.length} calendar event list fetch job(s)`, + ); + }, authContext); } @Option({ diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job.ts index 82dd387a8b8..e66e46d9d9f 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job.ts @@ -35,55 +35,52 @@ export class CalendarEventListFetchJob { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); - - const calendarChannel = await calendarChannelRepository.findOne({ - where: { - id: calendarChannelId, - isSyncEnabled: true, - }, - relations: ['connectedAccount'], - }); - - if (!calendarChannel) { - return; - } - - if ( - calendarChannel.syncStage !== - CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED - ) { - return; - } - - if ( - isThrottled( - calendarChannel.syncStageStartedAt, - calendarChannel.throttleFailureCount, - ) - ) { - await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending( - [calendarChannel.id], - workspaceId, - true, - ); - - return; - } - - await this.calendarFetchEventsService.fetchCalendarEvents( - calendarChannel, - calendarChannel.connectedAccount, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( workspaceId, + 'calendarChannel', ); - }, - ); + + const calendarChannel = await calendarChannelRepository.findOne({ + where: { + id: calendarChannelId, + isSyncEnabled: true, + }, + relations: ['connectedAccount'], + }); + + if (!calendarChannel) { + return; + } + + if ( + calendarChannel.syncStage !== + CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED + ) { + return; + } + + if ( + isThrottled( + calendarChannel.syncStageStartedAt, + calendarChannel.throttleFailureCount, + ) + ) { + await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending( + [calendarChannel.id], + workspaceId, + true, + ); + + return; + } + + await this.calendarFetchEventsService.fetchCalendarEvents( + calendarChannel, + calendarChannel.connectedAccount, + workspaceId, + ); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-events-import.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-events-import.job.ts index c59ebfeb7fa..c9aaa509e3c 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-events-import.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-events-import.job.ts @@ -35,54 +35,51 @@ export class CalendarEventsImportJob { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); - const calendarChannel = await calendarChannelRepository.findOne({ - where: { - id: calendarChannelId, - isSyncEnabled: true, - }, - relations: ['connectedAccount'], - }); - - if (!calendarChannel?.isSyncEnabled) { - return; - } - - if ( - calendarChannel.syncStage !== - CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED - ) { - return; - } - - if ( - isThrottled( - calendarChannel.syncStageStartedAt, - calendarChannel.throttleFailureCount, - ) - ) { - await this.calendarChannelSyncStatusService.markAsCalendarEventsImportPending( - [calendarChannel.id], - workspaceId, - true, - ); - - return; - } - - await this.calendarEventsImportService.processCalendarEventsImport( - calendarChannel, - calendarChannel.connectedAccount, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( workspaceId, + 'calendarChannel', ); - }, - ); + const calendarChannel = await calendarChannelRepository.findOne({ + where: { + id: calendarChannelId, + isSyncEnabled: true, + }, + relations: ['connectedAccount'], + }); + + if (!calendarChannel?.isSyncEnabled) { + return; + } + + if ( + calendarChannel.syncStage !== + CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED + ) { + return; + } + + if ( + isThrottled( + calendarChannel.syncStageStartedAt, + calendarChannel.throttleFailureCount, + ) + ) { + await this.calendarChannelSyncStatusService.markAsCalendarEventsImportPending( + [calendarChannel.id], + workspaceId, + true, + ); + + return; + } + + await this.calendarEventsImportService.processCalendarEventsImport( + calendarChannel, + calendarChannel.connectedAccount, + workspaceId, + ); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-ongoing-stale.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-ongoing-stale.job.ts index 088ea2ae63c..cae524cbd2f 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-ongoing-stale.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-ongoing-stale.job.ts @@ -35,63 +35,60 @@ export class CalendarOngoingStaleJob { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', + ); + + const calendarChannels = await calendarChannelRepository.find({ + where: { + syncStage: In([ + CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING, + CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING, + CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED, + CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED, + ]), + }, + }); + + for (const calendarChannel of calendarChannels) { + if ( + calendarChannel.syncStageStartedAt && + isSyncStale(calendarChannel.syncStageStartedAt) + ) { + await this.calendarChannelSyncStatusService.resetSyncStageStartedAt( + [calendarChannel.id], workspaceId, - 'calendarChannel', ); - const calendarChannels = await calendarChannelRepository.find({ - where: { - syncStage: In([ - CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING, - CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING, - CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED, - CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED, - ]), - }, - }); - - for (const calendarChannel of calendarChannels) { - if ( - calendarChannel.syncStageStartedAt && - isSyncStale(calendarChannel.syncStageStartedAt) - ) { - await this.calendarChannelSyncStatusService.resetSyncStageStartedAt( - [calendarChannel.id], - workspaceId, - ); - - switch (calendarChannel.syncStage) { - case CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING: - case CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED: - this.logger.log( - `Sync for calendar channel ${calendarChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to CALENDAR_EVENT_LIST_FETCH_PENDING`, - ); - await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending( - [calendarChannel.id], - workspaceId, - ); - break; - case CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING: - case CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED: - this.logger.log( - `Sync for calendar channel ${calendarChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to CALENDAR_EVENTS_IMPORT_PENDING`, - ); - await this.calendarChannelSyncStatusService.markAsCalendarEventsImportPending( - [calendarChannel.id], - workspaceId, - ); - break; - default: - break; - } + switch (calendarChannel.syncStage) { + case CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING: + case CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED: + this.logger.log( + `Sync for calendar channel ${calendarChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to CALENDAR_EVENT_LIST_FETCH_PENDING`, + ); + await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending( + [calendarChannel.id], + workspaceId, + ); + break; + case CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING: + case CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED: + this.logger.log( + `Sync for calendar channel ${calendarChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to CALENDAR_EVENTS_IMPORT_PENDING`, + ); + await this.calendarChannelSyncStatusService.markAsCalendarEventsImportPending( + [calendarChannel.id], + workspaceId, + ); + break; + default: + break; } } - }, - ); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-relaunch-failed-calendar-channel.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-relaunch-failed-calendar-channel.job.ts index 08dfa62ff4d..72a5e697448 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-relaunch-failed-calendar-channel.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-relaunch-failed-calendar-channel.job.ts @@ -31,41 +31,37 @@ export class CalendarRelaunchFailedCalendarChannelJob { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - { shouldBypassPermissionChecks: true }, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', + { shouldBypassPermissionChecks: true }, + ); - const calendarChannel = await calendarChannelRepository.findOne({ - where: { - id: calendarChannelId, + const calendarChannel = await calendarChannelRepository.findOne({ + where: { + id: calendarChannelId, + }, + relations: { + connectedAccount: { + accountOwner: true, }, - relations: { - connectedAccount: { - accountOwner: true, - }, - }, - }); + }, + }); - if ( - !calendarChannel || - calendarChannel.syncStage !== CalendarChannelSyncStage.FAILED || - calendarChannel.syncStatus !== - CalendarChannelSyncStatus.FAILED_UNKNOWN - ) { - return; - } + if ( + !calendarChannel || + calendarChannel.syncStage !== CalendarChannelSyncStage.FAILED || + calendarChannel.syncStatus !== CalendarChannelSyncStatus.FAILED_UNKNOWN + ) { + return; + } - await calendarChannelRepository.update(calendarChannelId, { - syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, - syncStatus: CalendarChannelSyncStatus.ACTIVE, - }); - }, - ); + await calendarChannelRepository.update(calendarChannelId, { + syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, + syncStatus: CalendarChannelSyncStatus.ACTIVE, + }); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts index e7fc0a2f81d..e170a8067a8 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts @@ -133,26 +133,23 @@ export class CalendarEventImportErrorHandlerService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); - - await calendarChannelRepository.increment( - { - id: calendarChannel.id, - }, - 'throttleFailureCount', - 1, - undefined, - ['throttleFailureCount', 'id'], + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', ); - }, - ); + + await calendarChannelRepository.increment( + { + id: calendarChannel.id, + }, + 'throttleFailureCount', + 1, + undefined, + ['throttleFailureCount', 'id'], + ); + }, authContext); switch (syncStep) { case CalendarEventImportSyncStep.CALENDAR_EVENT_LIST_FETCH: diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service.ts index 6c3eaabe134..cc4109ac30c 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service.ts @@ -58,121 +58,117 @@ export class CalendarEventsImportService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - let calendarEvents: FetchedCalendarEvent[] = []; + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + let calendarEvents: FetchedCalendarEvent[] = []; - try { - if (fetchedCalendarEvents) { - calendarEvents = fetchedCalendarEvents; - } else { - const eventIdsToFetch: string[] = await this.cacheStorage.setPop( - `calendar-events-to-import:${workspaceId}:${calendarChannel.id}`, - CALENDAR_EVENT_IMPORT_BATCH_SIZE, - ); + try { + if (fetchedCalendarEvents) { + calendarEvents = fetchedCalendarEvents; + } else { + const eventIdsToFetch: string[] = await this.cacheStorage.setPop( + `calendar-events-to-import:${workspaceId}:${calendarChannel.id}`, + CALENDAR_EVENT_IMPORT_BATCH_SIZE, + ); - if (!eventIdsToFetch || eventIdsToFetch.length === 0) { - await this.calendarChannelSyncStatusService.markAsCompletedAndMarkAsCalendarEventListFetchPending( - [calendarChannel.id], - workspaceId, - ); - - return; - } - - switch (connectedAccount.provider) { - case 'microsoft': - calendarEvents = - await this.microsoftCalendarImportEventService.getCalendarEvents( - connectedAccount, - eventIdsToFetch, - ); - break; - default: - break; - } - } - - if (!calendarEvents || calendarEvents?.length === 0) { - await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending( + if (!eventIdsToFetch || eventIdsToFetch.length === 0) { + await this.calendarChannelSyncStatusService.markAsCompletedAndMarkAsCalendarEventListFetchPending( [calendarChannel.id], workspaceId, ); + + return; } - const blocklist = - await this.blocklistRepository.getByWorkspaceMemberId( - connectedAccount.accountOwnerId, - workspaceId, - ); - - if ( - !isDefined(connectedAccount.handleAliases) || - !isDefined(calendarChannel.handle) - ) { - throw new CalendarEventImportDriverException( - 'Calendar channel handle or Handle aliases are required', - CalendarEventImportDriverExceptionCode.CHANNEL_MISCONFIGURED, - ); + switch (connectedAccount.provider) { + case 'microsoft': + calendarEvents = + await this.microsoftCalendarImportEventService.getCalendarEvents( + connectedAccount, + eventIdsToFetch, + ); + break; + default: + break; } + } - const { filteredEvents, cancelledEvents } = - filterEventsAndReturnCancelledEvents( - [ - calendarChannel.handle, - ...connectedAccount.handleAliases.split(','), - ], - calendarEvents, - blocklist.map((blocklist) => blocklist.handle ?? ''), - ); - - const cancelledEventExternalIds = cancelledEvents.map( - (event) => event.id, - ); - - const BATCH_SIZE = 1000; - - for (let i = 0; i < filteredEvents.length; i = i + BATCH_SIZE) { - const eventsBatch = filteredEvents.slice(i, i + BATCH_SIZE); - - await this.calendarSaveEventsService.saveCalendarEventsAndEnqueueContactCreationJob( - eventsBatch, - calendarChannel, - connectedAccount, - workspaceId, - ); - } - const calendarChannelEventAssociationRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannelEventAssociation', - ); - - await calendarChannelEventAssociationRepository.delete({ - eventExternalId: Any(cancelledEventExternalIds), - calendarChannel: { - id: calendarChannel.id, - }, - }); - - await this.calendarEventCleanerService.cleanWorkspaceCalendarEvents( - workspaceId, - ); - - await this.calendarChannelSyncStatusService.markAsCompletedAndMarkAsCalendarEventListFetchPending( + if (!calendarEvents || calendarEvents?.length === 0) { + await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending( [calendarChannel.id], workspaceId, ); - } catch (error) { - await this.calendarEventImportErrorHandlerService.handleDriverException( - error, - CalendarEventImportSyncStep.CALENDAR_EVENTS_IMPORT, + } + + const blocklist = await this.blocklistRepository.getByWorkspaceMemberId( + connectedAccount.accountOwnerId, + workspaceId, + ); + + if ( + !isDefined(connectedAccount.handleAliases) || + !isDefined(calendarChannel.handle) + ) { + throw new CalendarEventImportDriverException( + 'Calendar channel handle or Handle aliases are required', + CalendarEventImportDriverExceptionCode.CHANNEL_MISCONFIGURED, + ); + } + + const { filteredEvents, cancelledEvents } = + filterEventsAndReturnCancelledEvents( + [ + calendarChannel.handle, + ...connectedAccount.handleAliases.split(','), + ], + calendarEvents, + blocklist.map((blocklist) => blocklist.handle ?? ''), + ); + + const cancelledEventExternalIds = cancelledEvents.map( + (event) => event.id, + ); + + const BATCH_SIZE = 1000; + + for (let i = 0; i < filteredEvents.length; i = i + BATCH_SIZE) { + const eventsBatch = filteredEvents.slice(i, i + BATCH_SIZE); + + await this.calendarSaveEventsService.saveCalendarEventsAndEnqueueContactCreationJob( + eventsBatch, calendarChannel, + connectedAccount, workspaceId, ); } - }, - ); + const calendarChannelEventAssociationRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannelEventAssociation', + ); + + await calendarChannelEventAssociationRepository.delete({ + eventExternalId: Any(cancelledEventExternalIds), + calendarChannel: { + id: calendarChannel.id, + }, + }); + + await this.calendarEventCleanerService.cleanWorkspaceCalendarEvents( + workspaceId, + ); + + await this.calendarChannelSyncStatusService.markAsCompletedAndMarkAsCalendarEventListFetchPending( + [calendarChannel.id], + workspaceId, + ); + } catch (error) { + await this.calendarEventImportErrorHandlerService.handleDriverException( + error, + CalendarEventImportSyncStep.CALENDAR_EVENTS_IMPORT, + calendarChannel, + workspaceId, + ); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service.ts index 9bf68fcb940..ab67cc4ec0d 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service.ts @@ -48,68 +48,51 @@ export class CalendarFetchEventsService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - try { - const { accessToken, refreshToken } = - await this.calendarAccountAuthenticationService.validateAndRefreshConnectedAccountAuthentication( - { - connectedAccount, - workspaceId, - calendarChannelId: calendarChannel.id, - }, - ); - - const connectedAccountWithFreshTokens = { - ...connectedAccount, - accessToken, - refreshToken, - }; - - if (!isDefined(calendarChannel.syncCursor)) { - throw new CalendarEventImportDriverException( - 'Sync cursor is required', - CalendarEventImportDriverExceptionCode.SYNC_CURSOR_ERROR, - ); - } - - const getCalendarEventsResponse = - await this.getCalendarEventsService.getCalendarEvents( - connectedAccountWithFreshTokens, - calendarChannel.syncCursor, - ); - - const hasFullEvents = getCalendarEventsResponse.fullEvents; - - const calendarEvents = hasFullEvents - ? getCalendarEventsResponse.calendarEvents - : null; - const calendarEventIds = getCalendarEventsResponse.calendarEventIds; - const nextSyncCursor = getCalendarEventsResponse.nextSyncCursor; - - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + try { + const { accessToken, refreshToken } = + await this.calendarAccountAuthenticationService.validateAndRefreshConnectedAccountAuthentication( + { + connectedAccount, workspaceId, - 'calendarChannel', - ); + calendarChannelId: calendarChannel.id, + }, + ); - if (!calendarEvents || calendarEvents?.length === 0) { - await calendarChannelRepository.update( - { - id: calendarChannel.id, - }, - { - syncCursor: nextSyncCursor, - }, - ); + const connectedAccountWithFreshTokens = { + ...connectedAccount, + accessToken, + refreshToken, + }; - await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending( - [calendarChannel.id], - workspaceId, - ); - } + if (!isDefined(calendarChannel.syncCursor)) { + throw new CalendarEventImportDriverException( + 'Sync cursor is required', + CalendarEventImportDriverExceptionCode.SYNC_CURSOR_ERROR, + ); + } + const getCalendarEventsResponse = + await this.getCalendarEventsService.getCalendarEvents( + connectedAccountWithFreshTokens, + calendarChannel.syncCursor, + ); + + const hasFullEvents = getCalendarEventsResponse.fullEvents; + + const calendarEvents = hasFullEvents + ? getCalendarEventsResponse.calendarEvents + : null; + const calendarEventIds = getCalendarEventsResponse.calendarEventIds; + const nextSyncCursor = getCalendarEventsResponse.nextSyncCursor; + + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', + ); + + if (!calendarEvents || calendarEvents?.length === 0) { await calendarChannelRepository.update( { id: calendarChannel.id, @@ -119,42 +102,56 @@ export class CalendarFetchEventsService { }, ); - if (hasFullEvents && calendarEvents) { - await this.calendarEventsImportService.processCalendarEventsImport( - calendarChannel, - connectedAccount, - workspaceId, - calendarEvents, - ); - } else if (!hasFullEvents && calendarEventIds) { - await this.cacheStorage.setAdd( - `calendar-events-to-import:${workspaceId}:${calendarChannel.id}`, - calendarEventIds, - ); - - await this.calendarChannelSyncStatusService.markAsCalendarEventsImportPending( - [calendarChannel.id], - workspaceId, - ); - } else { - throw new CalendarEventImportDriverException( - "Expected 'calendarEvents' or 'calendarEventIds' to be present", - CalendarEventImportDriverExceptionCode.UNKNOWN, - ); - } - } catch (error) { - this.logger.log( - `Calendar event fetch error for workspace ${workspaceId} and calendar channel ${calendarChannel.id}`, - ); - this.logger.error(error); - await this.calendarEventImportErrorHandlerService.handleDriverException( - error, - CalendarEventImportSyncStep.CALENDAR_EVENT_LIST_FETCH, - calendarChannel, + await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending( + [calendarChannel.id], workspaceId, ); } - }, - ); + + await calendarChannelRepository.update( + { + id: calendarChannel.id, + }, + { + syncCursor: nextSyncCursor, + }, + ); + + if (hasFullEvents && calendarEvents) { + await this.calendarEventsImportService.processCalendarEventsImport( + calendarChannel, + connectedAccount, + workspaceId, + calendarEvents, + ); + } else if (!hasFullEvents && calendarEventIds) { + await this.cacheStorage.setAdd( + `calendar-events-to-import:${workspaceId}:${calendarChannel.id}`, + calendarEventIds, + ); + + await this.calendarChannelSyncStatusService.markAsCalendarEventsImportPending( + [calendarChannel.id], + workspaceId, + ); + } else { + throw new CalendarEventImportDriverException( + "Expected 'calendarEvents' or 'calendarEventIds' to be present", + CalendarEventImportDriverExceptionCode.UNKNOWN, + ); + } + } catch (error) { + this.logger.log( + `Calendar event fetch error for workspace ${workspaceId} and calendar channel ${calendarChannel.id}`, + ); + this.logger.error(error); + await this.calendarEventImportErrorHandlerService.handleDriverException( + error, + CalendarEventImportSyncStep.CALENDAR_EVENT_LIST_FETCH, + calendarChannel, + workspaceId, + ); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts index c870978ed37..67704eb16b9 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts @@ -34,248 +34,239 @@ export class CalendarSaveEventsService { ): Promise { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarEventRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarEvent', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarEventRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarEvent', + ); - const calendarChannelEventAssociationRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannelEventAssociation', - ); + const calendarChannelEventAssociationRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannelEventAssociation', + ); - const workspaceDataSource = - await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource(); + const workspaceDataSource = + await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource(); - await workspaceDataSource.transaction( - async (transactionManager: WorkspaceEntityManager) => { - const existingCalendarEvents = await calendarEventRepository.find( - { - where: { - iCalUid: Any( - fetchedCalendarEvents.map( - (event) => event.iCalUid as string, - ), - ), - }, + await workspaceDataSource.transaction( + async (transactionManager: WorkspaceEntityManager) => { + const existingCalendarEvents = await calendarEventRepository.find( + { + where: { + iCalUid: Any( + fetchedCalendarEvents.map((event) => event.iCalUid as string), + ), }, - transactionManager, - ); + }, + transactionManager, + ); - const fetchedCalendarEventsWithDBEvents: FetchedCalendarEventWithDBEvent[] = - fetchedCalendarEvents.map( - (event): FetchedCalendarEventWithDBEvent => { - const existingEventWithSameiCalUid = - existingCalendarEvents.find( - (existingEvent) => - existingEvent.iCalUid === event.iCalUid, - ); - - return { - fetchedCalendarEvent: event, - existingCalendarEvent: existingEventWithSameiCalUid ?? null, - newlyCreatedCalendarEvent: null, - }; - }, - ); - - const newCalendarEventsToInsert = fetchedCalendarEventsWithDBEvents - .filter( - ({ existingCalendarEvent }) => existingCalendarEvent === null, - ) - .map(({ fetchedCalendarEvent }) => ({ - id: uuid(), - iCalUid: fetchedCalendarEvent.iCalUid, - title: fetchedCalendarEvent.title, - description: fetchedCalendarEvent.description, - startsAt: fetchedCalendarEvent.startsAt, - endsAt: fetchedCalendarEvent.endsAt, - location: fetchedCalendarEvent.location, - isFullDay: fetchedCalendarEvent.isFullDay, - isCanceled: fetchedCalendarEvent.isCanceled, - conferenceSolution: fetchedCalendarEvent.conferenceSolution, - conferenceLink: { - primaryLinkLabel: fetchedCalendarEvent.conferenceLinkLabel, - primaryLinkUrl: fetchedCalendarEvent.conferenceLinkUrl, - secondaryLinks: [], - }, - externalCreatedAt: fetchedCalendarEvent.externalCreatedAt, - externalUpdatedAt: fetchedCalendarEvent.externalUpdatedAt, - })); - - if (newCalendarEventsToInsert.length > 0) { - await calendarEventRepository.insert( - newCalendarEventsToInsert, - transactionManager, - ); - } - - const fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents: FetchedCalendarEventWithDBEvent[] = - fetchedCalendarEventsWithDBEvents.map( - ({ fetchedCalendarEvent, existingCalendarEvent }) => { - const savedCalendarEvent = newCalendarEventsToInsert.find( - (inserted) => - inserted.iCalUid === fetchedCalendarEvent.iCalUid, + const fetchedCalendarEventsWithDBEvents: FetchedCalendarEventWithDBEvent[] = + fetchedCalendarEvents.map( + (event): FetchedCalendarEventWithDBEvent => { + const existingEventWithSameiCalUid = + existingCalendarEvents.find( + (existingEvent) => existingEvent.iCalUid === event.iCalUid, ); - return { - fetchedCalendarEvent, - existingCalendarEvent: existingCalendarEvent, - newlyCreatedCalendarEvent: savedCalendarEvent - ? ({ - id: savedCalendarEvent.id, - iCalUid: savedCalendarEvent.iCalUid, - } as CalendarEventWorkspaceEntity) - : null, - }; - }, - ); - - const existingEventsToUpdate = - fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents - .filter( - ({ existingCalendarEvent }) => existingCalendarEvent !== null, - ) - .map(({ fetchedCalendarEvent, existingCalendarEvent }) => { - if (!existingCalendarEvent) { - throw new Error( - `Existing calendar event with iCalUid ${fetchedCalendarEvent.iCalUid} not found - should never happen`, - ); - } - - return { - criteria: existingCalendarEvent.id, - partialEntity: { - iCalUid: fetchedCalendarEvent.iCalUid, - title: fetchedCalendarEvent.title, - description: fetchedCalendarEvent.description, - startsAt: fetchedCalendarEvent.startsAt, - endsAt: fetchedCalendarEvent.endsAt, - location: fetchedCalendarEvent.location, - isFullDay: fetchedCalendarEvent.isFullDay, - isCanceled: fetchedCalendarEvent.isCanceled, - conferenceSolution: - fetchedCalendarEvent.conferenceSolution, - conferenceLink: { - primaryLinkLabel: - fetchedCalendarEvent.conferenceLinkLabel, - primaryLinkUrl: fetchedCalendarEvent.conferenceLinkUrl, - secondaryLinks: [], - }, - externalCreatedAt: fetchedCalendarEvent.externalCreatedAt, - externalUpdatedAt: fetchedCalendarEvent.externalUpdatedAt, - }, - }; - }); - - if (existingEventsToUpdate.length > 0) { - await calendarEventRepository.updateMany( - existingEventsToUpdate, - transactionManager, - ); - } - - const calendarChannelEventAssociationsToSave: Pick< - CalendarChannelEventAssociationWorkspaceEntity, - | 'calendarEventId' - | 'eventExternalId' - | 'calendarChannelId' - | 'recurringEventExternalId' - >[] = fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents.map( - ({ - fetchedCalendarEvent, - existingCalendarEvent, - newlyCreatedCalendarEvent, - }) => { - const calendarEventId = - existingCalendarEvent?.id ?? newlyCreatedCalendarEvent?.id; - - if (!calendarEventId) { - throw new Error( - `Calendar event id not found for event with iCalUid ${fetchedCalendarEvent.iCalUid} - should never happen`, - ); - } - return { - calendarEventId, - eventExternalId: fetchedCalendarEvent.id, - calendarChannelId: calendarChannel.id, - recurringEventExternalId: - fetchedCalendarEvent.recurringEventExternalId ?? '', + fetchedCalendarEvent: event, + existingCalendarEvent: existingEventWithSameiCalUid ?? null, + newlyCreatedCalendarEvent: null, }; }, ); - if (calendarChannelEventAssociationsToSave.length > 0) { - await calendarChannelEventAssociationRepository.insert( - calendarChannelEventAssociationsToSave, - transactionManager, - ); - } + const newCalendarEventsToInsert = fetchedCalendarEventsWithDBEvents + .filter( + ({ existingCalendarEvent }) => existingCalendarEvent === null, + ) + .map(({ fetchedCalendarEvent }) => ({ + id: uuid(), + iCalUid: fetchedCalendarEvent.iCalUid, + title: fetchedCalendarEvent.title, + description: fetchedCalendarEvent.description, + startsAt: fetchedCalendarEvent.startsAt, + endsAt: fetchedCalendarEvent.endsAt, + location: fetchedCalendarEvent.location, + isFullDay: fetchedCalendarEvent.isFullDay, + isCanceled: fetchedCalendarEvent.isCanceled, + conferenceSolution: fetchedCalendarEvent.conferenceSolution, + conferenceLink: { + primaryLinkLabel: fetchedCalendarEvent.conferenceLinkLabel, + primaryLinkUrl: fetchedCalendarEvent.conferenceLinkUrl, + secondaryLinks: [], + }, + externalCreatedAt: fetchedCalendarEvent.externalCreatedAt, + externalUpdatedAt: fetchedCalendarEvent.externalUpdatedAt, + })); - const participantsToCreate = - fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents - .filter( - ({ newlyCreatedCalendarEvent }) => - newlyCreatedCalendarEvent !== null, - ) - .flatMap( - ({ newlyCreatedCalendarEvent, fetchedCalendarEvent }) => { - if (!newlyCreatedCalendarEvent?.id) { - throw new Error( - `Newly created calendar event with iCalUid ${fetchedCalendarEvent.iCalUid} not found - should never happen`, - ); - } + if (newCalendarEventsToInsert.length > 0) { + await calendarEventRepository.insert( + newCalendarEventsToInsert, + transactionManager, + ); + } - return fetchedCalendarEvent.participants.map( - (participant) => ({ - ...participant, - calendarEventId: newlyCreatedCalendarEvent.id, - }), - ); - }, + const fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents: FetchedCalendarEventWithDBEvent[] = + fetchedCalendarEventsWithDBEvents.map( + ({ fetchedCalendarEvent, existingCalendarEvent }) => { + const savedCalendarEvent = newCalendarEventsToInsert.find( + (inserted) => + inserted.iCalUid === fetchedCalendarEvent.iCalUid, ); - // todo: we should prevent duplicate rows on calendarEventAssociation by creating - // an index on calendarChannelId and calendarEventId - const participantsToUpdate = - fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents - .filter( - ({ existingCalendarEvent }) => existingCalendarEvent !== null, - ) - .flatMap(({ fetchedCalendarEvent, existingCalendarEvent }) => { - if (!existingCalendarEvent?.id) { + return { + fetchedCalendarEvent, + existingCalendarEvent: existingCalendarEvent, + newlyCreatedCalendarEvent: savedCalendarEvent + ? ({ + id: savedCalendarEvent.id, + iCalUid: savedCalendarEvent.iCalUid, + } as CalendarEventWorkspaceEntity) + : null, + }; + }, + ); + + const existingEventsToUpdate = + fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents + .filter( + ({ existingCalendarEvent }) => existingCalendarEvent !== null, + ) + .map(({ fetchedCalendarEvent, existingCalendarEvent }) => { + if (!existingCalendarEvent) { + throw new Error( + `Existing calendar event with iCalUid ${fetchedCalendarEvent.iCalUid} not found - should never happen`, + ); + } + + return { + criteria: existingCalendarEvent.id, + partialEntity: { + iCalUid: fetchedCalendarEvent.iCalUid, + title: fetchedCalendarEvent.title, + description: fetchedCalendarEvent.description, + startsAt: fetchedCalendarEvent.startsAt, + endsAt: fetchedCalendarEvent.endsAt, + location: fetchedCalendarEvent.location, + isFullDay: fetchedCalendarEvent.isFullDay, + isCanceled: fetchedCalendarEvent.isCanceled, + conferenceSolution: fetchedCalendarEvent.conferenceSolution, + conferenceLink: { + primaryLinkLabel: + fetchedCalendarEvent.conferenceLinkLabel, + primaryLinkUrl: fetchedCalendarEvent.conferenceLinkUrl, + secondaryLinks: [], + }, + externalCreatedAt: fetchedCalendarEvent.externalCreatedAt, + externalUpdatedAt: fetchedCalendarEvent.externalUpdatedAt, + }, + }; + }); + + if (existingEventsToUpdate.length > 0) { + await calendarEventRepository.updateMany( + existingEventsToUpdate, + transactionManager, + ); + } + + const calendarChannelEventAssociationsToSave: Pick< + CalendarChannelEventAssociationWorkspaceEntity, + | 'calendarEventId' + | 'eventExternalId' + | 'calendarChannelId' + | 'recurringEventExternalId' + >[] = fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents.map( + ({ + fetchedCalendarEvent, + existingCalendarEvent, + newlyCreatedCalendarEvent, + }) => { + const calendarEventId = + existingCalendarEvent?.id ?? newlyCreatedCalendarEvent?.id; + + if (!calendarEventId) { + throw new Error( + `Calendar event id not found for event with iCalUid ${fetchedCalendarEvent.iCalUid} - should never happen`, + ); + } + + return { + calendarEventId, + eventExternalId: fetchedCalendarEvent.id, + calendarChannelId: calendarChannel.id, + recurringEventExternalId: + fetchedCalendarEvent.recurringEventExternalId ?? '', + }; + }, + ); + + if (calendarChannelEventAssociationsToSave.length > 0) { + await calendarChannelEventAssociationRepository.insert( + calendarChannelEventAssociationsToSave, + transactionManager, + ); + } + + const participantsToCreate = + fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents + .filter( + ({ newlyCreatedCalendarEvent }) => + newlyCreatedCalendarEvent !== null, + ) + .flatMap( + ({ newlyCreatedCalendarEvent, fetchedCalendarEvent }) => { + if (!newlyCreatedCalendarEvent?.id) { throw new Error( - `Existing calendar event with iCalUid ${fetchedCalendarEvent.iCalUid} not found - should never happen`, + `Newly created calendar event with iCalUid ${fetchedCalendarEvent.iCalUid} not found - should never happen`, ); } return fetchedCalendarEvent.participants.map( (participant) => ({ ...participant, - calendarEventId: existingCalendarEvent.id, + calendarEventId: newlyCreatedCalendarEvent.id, }), ); - }); + }, + ); - await this.calendarEventParticipantService.upsertAndDeleteCalendarEventParticipants( - { - participantsToCreate, - participantsToUpdate, - transactionManager, - calendarChannel, - connectedAccount, - workspaceId, - }, - ); - }, - ); - }, - ); + // todo: we should prevent duplicate rows on calendarEventAssociation by creating + // an index on calendarChannelId and calendarEventId + const participantsToUpdate = + fetchedCalendarEventsWithDBEventsEnrichedWithSavedEvents + .filter( + ({ existingCalendarEvent }) => existingCalendarEvent !== null, + ) + .flatMap(({ fetchedCalendarEvent, existingCalendarEvent }) => { + if (!existingCalendarEvent?.id) { + throw new Error( + `Existing calendar event with iCalUid ${fetchedCalendarEvent.iCalUid} not found - should never happen`, + ); + } + + return fetchedCalendarEvent.participants.map((participant) => ({ + ...participant, + calendarEventId: existingCalendarEvent.id, + })); + }); + + await this.calendarEventParticipantService.upsertAndDeleteCalendarEventParticipants( + { + participantsToCreate, + participantsToUpdate, + transactionManager, + calendarChannel, + connectedAccount, + workspaceId, + }, + ); + }, + ); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service.ts index a009213b718..2ab7cebe542 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service.ts @@ -58,131 +58,127 @@ export class CalendarEventParticipantService { }): Promise { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const chunkedParticipantsToUpdate = chunk(participantsToUpdate, 200); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const chunkedParticipantsToUpdate = chunk(participantsToUpdate, 200); - const calendarEventParticipantRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarEventParticipant', - ); + const calendarEventParticipantRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarEventParticipant', + ); - for (const participantsToUpdateChunk of chunkedParticipantsToUpdate) { - const existingCalendarEventParticipants = - await calendarEventParticipantRepository.find({ - where: { - calendarEventId: Any( - participantsToUpdateChunk - .map((participant) => participant.calendarEventId) - .filter(isDefined), - ), - }, - }); - - const { - calendarEventParticipantsToUpdate, - newCalendarEventParticipants, - } = participantsToUpdateChunk.reduce<{ - calendarEventParticipantsToUpdate: FetchedCalendarEventParticipantWithCalendarEventIdAndExistingId[]; - newCalendarEventParticipants: FetchedCalendarEventParticipantWithCalendarEventId[]; - }>( - (acc, calendarEventParticipant) => { - const existingCalendarEventParticipant = - existingCalendarEventParticipants.find( - (existingCalendarEventParticipant) => - existingCalendarEventParticipant.handle === - calendarEventParticipant.handle && - existingCalendarEventParticipant.calendarEventId === - calendarEventParticipant.calendarEventId, - ); - - if (existingCalendarEventParticipant) { - acc.calendarEventParticipantsToUpdate.push({ - ...calendarEventParticipant, - id: existingCalendarEventParticipant.id, - }); - } else { - acc.newCalendarEventParticipants.push(calendarEventParticipant); - } - - return acc; - }, - { - calendarEventParticipantsToUpdate: [], - newCalendarEventParticipants: [], - }, - ); - - const calendarEventParticipantsToDelete = differenceWith( - existingCalendarEventParticipants, - participantsToUpdateChunk, - (existingCalendarEventParticipant, participantToUpdate) => - existingCalendarEventParticipant.handle === - participantToUpdate.handle && - existingCalendarEventParticipant.calendarEventId === - participantToUpdate.calendarEventId, - ); - - await calendarEventParticipantRepository.delete( - { - id: Any( - calendarEventParticipantsToDelete.map( - (calendarEventParticipant) => calendarEventParticipant.id, - ), + for (const participantsToUpdateChunk of chunkedParticipantsToUpdate) { + const existingCalendarEventParticipants = + await calendarEventParticipantRepository.find({ + where: { + calendarEventId: Any( + participantsToUpdateChunk + .map((participant) => participant.calendarEventId) + .filter(isDefined), ), }, - transactionManager, - ); + }); - await calendarEventParticipantRepository.updateMany( - calendarEventParticipantsToUpdate.map((participant) => ({ - criteria: participant.id, - partialEntity: participant, - })), - transactionManager, - ); - participantsToCreate.push(...newCalendarEventParticipants); - } + const { + calendarEventParticipantsToUpdate, + newCalendarEventParticipants, + } = participantsToUpdateChunk.reduce<{ + calendarEventParticipantsToUpdate: FetchedCalendarEventParticipantWithCalendarEventIdAndExistingId[]; + newCalendarEventParticipants: FetchedCalendarEventParticipantWithCalendarEventId[]; + }>( + (acc, calendarEventParticipant) => { + const existingCalendarEventParticipant = + existingCalendarEventParticipants.find( + (existingCalendarEventParticipant) => + existingCalendarEventParticipant.handle === + calendarEventParticipant.handle && + existingCalendarEventParticipant.calendarEventId === + calendarEventParticipant.calendarEventId, + ); - const chunkedParticipantsToCreate = chunk(participantsToCreate, 200); - const savedParticipants: CalendarEventParticipantWorkspaceEntity[] = []; + if (existingCalendarEventParticipant) { + acc.calendarEventParticipantsToUpdate.push({ + ...calendarEventParticipant, + id: existingCalendarEventParticipant.id, + }); + } else { + acc.newCalendarEventParticipants.push(calendarEventParticipant); + } - for (const participantsToCreateChunk of chunkedParticipantsToCreate) { - const savedParticipantsChunk = - await calendarEventParticipantRepository.insert( - participantsToCreateChunk, - transactionManager, - ); + return acc; + }, + { + calendarEventParticipantsToUpdate: [], + newCalendarEventParticipants: [], + }, + ); - savedParticipants.push(...savedParticipantsChunk.raw); - } + const calendarEventParticipantsToDelete = differenceWith( + existingCalendarEventParticipants, + participantsToUpdateChunk, + (existingCalendarEventParticipant, participantToUpdate) => + existingCalendarEventParticipant.handle === + participantToUpdate.handle && + existingCalendarEventParticipant.calendarEventId === + participantToUpdate.calendarEventId, + ); - if (calendarChannel.isContactAutoCreationEnabled) { - await this.messageQueueService.add( - CreateCompanyAndContactJob.name, - { - workspaceId, - connectedAccount, - contactsToCreate: savedParticipants.map((participant) => ({ - handle: participant.handle ?? '', - displayName: - participant.displayName ?? participant.handle ?? '', - })), - source: FieldActorSource.CALENDAR, - }, - ); - } - - await this.matchParticipantService.matchParticipants({ - participants: savedParticipants, - objectMetadataName: 'calendarEventParticipant', + await calendarEventParticipantRepository.delete( + { + id: Any( + calendarEventParticipantsToDelete.map( + (calendarEventParticipant) => calendarEventParticipant.id, + ), + ), + }, transactionManager, - matchWith: 'workspaceMemberAndPerson', - workspaceId, - }); - }, - ); + ); + + await calendarEventParticipantRepository.updateMany( + calendarEventParticipantsToUpdate.map((participant) => ({ + criteria: participant.id, + partialEntity: participant, + })), + transactionManager, + ); + participantsToCreate.push(...newCalendarEventParticipants); + } + + const chunkedParticipantsToCreate = chunk(participantsToCreate, 200); + const savedParticipants: CalendarEventParticipantWorkspaceEntity[] = []; + + for (const participantsToCreateChunk of chunkedParticipantsToCreate) { + const savedParticipantsChunk = + await calendarEventParticipantRepository.insert( + participantsToCreateChunk, + transactionManager, + ); + + savedParticipants.push(...savedParticipantsChunk.raw); + } + + if (calendarChannel.isContactAutoCreationEnabled) { + await this.messageQueueService.add( + CreateCompanyAndContactJob.name, + { + workspaceId, + connectedAccount, + contactsToCreate: savedParticipants.map((participant) => ({ + handle: participant.handle ?? '', + displayName: participant.displayName ?? participant.handle ?? '', + })), + source: FieldActorSource.CALENDAR, + }, + ); + } + + await this.matchParticipantService.matchParticipants({ + participants: savedParticipants, + objectMetadataName: 'calendarEventParticipant', + transactionManager, + matchWith: 'workspaceMemberAndPerson', + workspaceId, + }); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.spec.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.spec.ts index d0e2393cf19..59b2379b733 100644 --- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.spec.ts +++ b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.spec.ts @@ -66,7 +66,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { }), executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }; beforeEach(async () => { diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.ts index d127a9809b3..1432fe1f8b7 100644 --- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.ts +++ b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.ts @@ -27,7 +27,6 @@ export class ApplyCalendarEventsVisibilityRestrictionsService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const calendarChannelEventAssociationRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -117,6 +116,7 @@ export class ApplyCalendarEventsVisibilityRestrictionsService { return calendarEvents; }, + authContext, ); } } diff --git a/packages/twenty-server/src/modules/calendar/common/services/calendar-channel-sync-status.service.ts b/packages/twenty-server/src/modules/calendar/common/services/calendar-channel-sync-status.service.ts index 1363c775ab2..166888abae2 100644 --- a/packages/twenty-server/src/modules/calendar/common/services/calendar-channel-sync-status.service.ts +++ b/packages/twenty-server/src/modules/calendar/common/services/calendar-channel-sync-status.service.ts @@ -39,21 +39,18 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', + ); - await calendarChannelRepository.update(calendarChannelIds, { - syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, - ...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}), - }); - }, - ); + await calendarChannelRepository.update(calendarChannelIds, { + syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, + ...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}), + }); + }, authContext); } public async markAsCalendarEventListFetchOngoing( @@ -66,22 +63,19 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', + ); - await calendarChannelRepository.update(calendarChannelIds, { - syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING, - syncStatus: CalendarChannelSyncStatus.ONGOING, - syncStageStartedAt: new Date().toISOString(), - }); - }, - ); + await calendarChannelRepository.update(calendarChannelIds, { + syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING, + syncStatus: CalendarChannelSyncStatus.ONGOING, + syncStageStartedAt: new Date().toISOString(), + }); + }, authContext); } public async resetAndMarkAsCalendarEventListFetchPending( @@ -100,22 +94,19 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', + ); - await calendarChannelRepository.update(calendarChannelIds, { - syncCursor: '', - syncStageStartedAt: null, - throttleFailureCount: 0, - }); - }, - ); + await calendarChannelRepository.update(calendarChannelIds, { + syncCursor: '', + syncStageStartedAt: null, + throttleFailureCount: 0, + }); + }, authContext); await this.markAsCalendarEventListFetchPending( calendarChannelIds, @@ -133,20 +124,17 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', + ); - await calendarChannelRepository.update(calendarChannelIds, { - syncStageStartedAt: null, - }); - }, - ); + await calendarChannelRepository.update(calendarChannelIds, { + syncStageStartedAt: null, + }); + }, authContext); } public async markAsCalendarEventsImportPending( @@ -160,21 +148,18 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', + ); - await calendarChannelRepository.update(calendarChannelIds, { - syncStage: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_PENDING, - ...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}), - }); - }, - ); + await calendarChannelRepository.update(calendarChannelIds, { + syncStage: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_PENDING, + ...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}), + }); + }, authContext); } public async markAsCalendarEventsImportOngoing( @@ -187,21 +172,18 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', + ); - await calendarChannelRepository.update(calendarChannelIds, { - syncStage: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING, - syncStatus: CalendarChannelSyncStatus.ONGOING, - }); - }, - ); + await calendarChannelRepository.update(calendarChannelIds, { + syncStage: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING, + syncStatus: CalendarChannelSyncStatus.ONGOING, + }); + }, authContext); } public async markAsCompletedAndMarkAsCalendarEventListFetchPending( @@ -214,24 +196,21 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', + ); - await calendarChannelRepository.update(calendarChannelIds, { - syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, - syncStatus: CalendarChannelSyncStatus.ACTIVE, - throttleFailureCount: 0, - syncStageStartedAt: null, - syncedAt: new Date().toISOString(), - }); - }, - ); + await calendarChannelRepository.update(calendarChannelIds, { + syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, + syncStatus: CalendarChannelSyncStatus.ACTIVE, + throttleFailureCount: 0, + syncStageStartedAt: null, + syncedAt: new Date().toISOString(), + }); + }, authContext); await this.markAsCalendarEventListFetchPending( calendarChannelIds, @@ -260,21 +239,18 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', + ); - await calendarChannelRepository.update(calendarChannelIds, { - syncStatus: CalendarChannelSyncStatus.FAILED_UNKNOWN, - syncStage: CalendarChannelSyncStage.FAILED, - }); - }, - ); + await calendarChannelRepository.update(calendarChannelIds, { + syncStatus: CalendarChannelSyncStatus.FAILED_UNKNOWN, + syncStage: CalendarChannelSyncStage.FAILED, + }); + }, authContext); await this.metricsService.batchIncrementCounter({ key: MetricsKeys.CalendarEventSyncJobFailedUnknown, @@ -298,48 +274,45 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); - - await calendarChannelRepository.update(calendarChannelIds, { - syncStatus: CalendarChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS, - syncStage: CalendarChannelSyncStage.FAILED, - }); - - const connectedAccountRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'connectedAccount', - ); - - const calendarChannels = await calendarChannelRepository.find({ - select: ['id', 'connectedAccountId'], - where: { id: Any(calendarChannelIds) }, - }); - - const connectedAccountIds = calendarChannels.map( - (calendarChannel) => calendarChannel.connectedAccountId, - ); - - await connectedAccountRepository.update( - { id: Any(connectedAccountIds) }, - { - authFailedAt: new Date(), - }, - ); - - await this.addToAccountsToReconnect( - calendarChannels.map((calendarChannel) => calendarChannel.id), + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( workspaceId, + 'calendarChannel', ); - }, - ); + + await calendarChannelRepository.update(calendarChannelIds, { + syncStatus: CalendarChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS, + syncStage: CalendarChannelSyncStage.FAILED, + }); + + const connectedAccountRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'connectedAccount', + ); + + const calendarChannels = await calendarChannelRepository.find({ + select: ['id', 'connectedAccountId'], + where: { id: Any(calendarChannelIds) }, + }); + + const connectedAccountIds = calendarChannels.map( + (calendarChannel) => calendarChannel.connectedAccountId, + ); + + await connectedAccountRepository.update( + { id: Any(connectedAccountIds) }, + { + authFailedAt: new Date(), + }, + ); + + await this.addToAccountsToReconnect( + calendarChannels.map((calendarChannel) => calendarChannel.id), + workspaceId, + ); + }, authContext); await this.metricsService.batchIncrementCounter({ key: MetricsKeys.CalendarEventSyncJobFailedInsufficientPermissions, diff --git a/packages/twenty-server/src/modules/connected-account/channel-sync/services/channel-sync.service.ts b/packages/twenty-server/src/modules/connected-account/channel-sync/services/channel-sync.service.ts index a284d085fb5..ccbf6c9776c 100644 --- a/packages/twenty-server/src/modules/connected-account/channel-sync/services/channel-sync.service.ts +++ b/packages/twenty-server/src/modules/connected-account/channel-sync/services/channel-sync.service.ts @@ -53,38 +53,35 @@ export class ChannelSyncService { ): Promise { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); - const messageChannels = await messageChannelRepository.find({ - where: { - connectedAccountId, - syncStage: MessageChannelSyncStage.PENDING_CONFIGURATION, + const messageChannels = await messageChannelRepository.find({ + where: { + connectedAccountId, + syncStage: MessageChannelSyncStage.PENDING_CONFIGURATION, + }, + }); + + for (const messageChannel of messageChannels) { + await this.messageChannelSyncStatusService.markAsMessagesListFetchScheduled( + [messageChannel.id], + workspaceId, + ); + + await this.messageQueueService.add( + MessagingMessageListFetchJob.name, + { + workspaceId, + messageChannelId: messageChannel.id, }, - }); - - for (const messageChannel of messageChannels) { - await this.messageChannelSyncStatusService.markAsMessagesListFetchScheduled( - [messageChannel.id], - workspaceId, - ); - - await this.messageQueueService.add( - MessagingMessageListFetchJob.name, - { - workspaceId, - messageChannelId: messageChannel.id, - }, - ); - } - }, - ); + ); + } + }, authContext); } private async startCalendarChannelSync( @@ -93,38 +90,35 @@ export class ChannelSyncService { ): Promise { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const calendarChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const calendarChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'calendarChannel', + ); - const calendarChannels = await calendarChannelRepository.find({ - where: { - connectedAccountId, - syncStage: CalendarChannelSyncStage.PENDING_CONFIGURATION, - }, + const calendarChannels = await calendarChannelRepository.find({ + where: { + connectedAccountId, + syncStage: CalendarChannelSyncStage.PENDING_CONFIGURATION, + }, + }); + + for (const calendarChannel of calendarChannels) { + await calendarChannelRepository.update(calendarChannel.id, { + syncStage: + CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED, + syncStatus: CalendarChannelSyncStatus.ONGOING, }); - for (const calendarChannel of calendarChannels) { - await calendarChannelRepository.update(calendarChannel.id, { - syncStage: - CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED, - syncStatus: CalendarChannelSyncStatus.ONGOING, - }); - - await this.calendarQueueService.add( - CalendarEventListFetchJob.name, - { - workspaceId, - calendarChannelId: calendarChannel.id, - }, - ); - } - }, - ); + await this.calendarQueueService.add( + CalendarEventListFetchJob.name, + { + workspaceId, + calendarChannelId: calendarChannel.id, + }, + ); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts index 18b45138b7b..9f0862a9708 100644 --- a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts @@ -35,7 +35,7 @@ describe('Email Alias Manager Service', () => { .mockResolvedValue(connectedAccountRepository), executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }, }, EmailAliasManagerService, diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts index beb06710b14..04800e1b51b 100644 --- a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts @@ -49,22 +49,19 @@ export class EmailAliasManagerService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const connectedAccountRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'connectedAccount', - ); - - await connectedAccountRepository.update( - { id: connectedAccount.id }, - { - handleAliases: handleAliases.join(','), // TODO: modify handleAliases to be of fieldmetadatatype array - }, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const connectedAccountRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'connectedAccount', ); - }, - ); + + await connectedAccountRepository.update( + { id: connectedAccount.id }, + { + handleAliases: handleAliases.join(','), // TODO: modify handleAliases to be of fieldmetadatatype array + }, + ); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/connected-account/jobs/delete-workspace-member-connected-accounts.job.ts b/packages/twenty-server/src/modules/connected-account/jobs/delete-workspace-member-connected-accounts.job.ts index e926150be56..aebc1b3cd9f 100644 --- a/packages/twenty-server/src/modules/connected-account/jobs/delete-workspace-member-connected-accounts.job.ts +++ b/packages/twenty-server/src/modules/connected-account/jobs/delete-workspace-member-connected-accounts.job.ts @@ -24,19 +24,16 @@ export class DeleteWorkspaceMemberConnectedAccountsCleanupJob { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const connectedAccountRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'connectedAccount', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const connectedAccountRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'connectedAccount', + ); - await connectedAccountRepository.delete({ - accountOwnerId: workspaceMemberId, - }); - }, - ); + await connectedAccountRepository.delete({ + accountOwnerId: workspaceMemberId, + }); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts b/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts index 9e922cc733b..297c7eedcb0 100644 --- a/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts +++ b/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts @@ -27,36 +27,30 @@ export class ConnectedAccountListener { const workspaceId = payload.workspaceId; const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - for (const eventPayload of payload.events) { - const workspaceMemberId = - eventPayload.properties.before.accountOwnerId; + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + for (const eventPayload of payload.events) { + const workspaceMemberId = eventPayload.properties.before.accountOwnerId; - const workspaceMemberRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workspaceMember', - { shouldBypassPermissionChecks: true }, - ); - const workspaceMember = await workspaceMemberRepository.findOneOrFail( - { - where: { id: workspaceMemberId }, - }, - ); - - const userId = workspaceMember.userId; - - const connectedAccountId = eventPayload.properties.before.id; - - await this.accountsToReconnectService.removeAccountToReconnect( - userId, + const workspaceMemberRepository = + await this.globalWorkspaceOrmManager.getRepository( workspaceId, - connectedAccountId, + 'workspaceMember', + { shouldBypassPermissionChecks: true }, ); - } - }, - ); + const workspaceMember = await workspaceMemberRepository.findOneOrFail({ + where: { id: workspaceMemberId }, + }); + + const userId = workspaceMember.userId; + + const connectedAccountId = eventPayload.properties.before.id; + + await this.accountsToReconnectService.removeAccountToReconnect( + userId, + workspaceId, + connectedAccountId, + ); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts b/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts index d7c9cc40aeb..c8ad67843fa 100644 --- a/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts +++ b/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts @@ -41,7 +41,6 @@ export class ConnectedAccountDeleteOnePreQueryHook const messageChannels = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext as WorkspaceAuthContext, async () => { const messageChannelRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -53,6 +52,7 @@ export class ConnectedAccountDeleteOnePreQueryHook connectedAccountId, }); }, + authContext as WorkspaceAuthContext, ); const objectMetadataEntity = diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.spec.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.spec.ts index e7f7b66da16..a182e5a6737 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.spec.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.spec.ts @@ -48,7 +48,7 @@ describe('ConnectedAccountRefreshTokensService', () => { executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }, }, ], diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts index f75b08f56b0..fea8eaaa41b 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts @@ -77,24 +77,21 @@ export class ConnectedAccountRefreshTokensService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const connectedAccountRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'connectedAccount', - ); - - await connectedAccountRepository.update( - { id: connectedAccount.id }, - { - ...connectedAccountTokens, - lastCredentialsRefreshedAt: new Date(), - }, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const connectedAccountRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'connectedAccount', ); - }, - ); + + await connectedAccountRepository.update( + { id: connectedAccount.id }, + { + ...connectedAccountTokens, + lastCredentialsRefreshedAt: new Date(), + }, + ); + }, authContext); return connectedAccountTokens; } diff --git a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.spec.ts b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.spec.ts index 9503f6f3b18..0df4de9c870 100644 --- a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.spec.ts +++ b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.spec.ts @@ -70,7 +70,7 @@ describe('ImapSmtpCalDavAPIService', () => { executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }, }, { diff --git a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts index 074c12bf082..e342939519f 100644 --- a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts +++ b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts @@ -29,7 +29,6 @@ export class ImapSmtpCalDavAPIService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const connectedAccountRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -41,6 +40,7 @@ export class ImapSmtpCalDavAPIService { where: { id, provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV }, }); }, + authContext, ); } @@ -57,7 +57,6 @@ export class ImapSmtpCalDavAPIService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const connectedAccountRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -147,6 +146,7 @@ export class ImapSmtpCalDavAPIService { return newOrExistingAccountId; }, + authContext, ); } } diff --git a/packages/twenty-server/src/modules/contact-creation-manager/services/__tests__/create-company.service.spec.ts b/packages/twenty-server/src/modules/contact-creation-manager/services/__tests__/create-company.service.spec.ts index a6d3c90911a..377fc85ba6f 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/services/__tests__/create-company.service.spec.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/services/__tests__/create-company.service.spec.ts @@ -120,7 +120,7 @@ describe('CreateCompanyService', () => { getRepository: jest.fn().mockResolvedValue(mockCompanyRepository), executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }, }, { diff --git a/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts b/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts index 884ab2439b7..e14e8982ca2 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts @@ -51,7 +51,6 @@ export class CreateCompanyAndPersonService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const personRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -144,6 +143,7 @@ export class CreateCompanyAndPersonService { return { ...createdPeople, ...restoredPeople }; }, + authContext, ); } @@ -160,32 +160,29 @@ export class CreateCompanyAndPersonService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - if (!connectedAccount.accountOwner) { - const workspaceMemberRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - WorkspaceMemberWorkspaceEntity, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + if (!connectedAccount.accountOwner) { + const workspaceMemberRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + WorkspaceMemberWorkspaceEntity, + ); - const workspaceMember = await workspaceMemberRepository.findOne({ - where: { - id: connectedAccount.accountOwnerId, - }, - }); + const workspaceMember = await workspaceMemberRepository.findOne({ + where: { + id: connectedAccount.accountOwnerId, + }, + }); - if (!workspaceMember) { - throw new Error( - `Workspace member with id ${connectedAccount.accountOwnerId} not found in workspace ${workspaceId}`, - ); - } - - connectedAccount.accountOwner = workspaceMember; + if (!workspaceMember) { + throw new Error( + `Workspace member with id ${connectedAccount.accountOwnerId} not found in workspace ${workspaceId}`, + ); } - }, - ); + + connectedAccount.accountOwner = workspaceMember; + } + }, authContext); for (const contactsBatch of contactsBatches) { try { diff --git a/packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts b/packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts index 1c33ac2dc79..1d6eadae827 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts @@ -56,7 +56,6 @@ export class CreateCompanyService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const companyRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -157,6 +156,7 @@ export class CreateCompanyService { : {}), }; }, + authContext, ); } diff --git a/packages/twenty-server/src/modules/contact-creation-manager/services/create-person.service.ts b/packages/twenty-server/src/modules/contact-creation-manager/services/create-person.service.ts index 07189ceb91f..a1c3d0ab9ed 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/services/create-person.service.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/services/create-person.service.ts @@ -22,7 +22,6 @@ export class CreatePersonService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const personRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -47,6 +46,7 @@ export class CreatePersonService { return createdPeople.raw; }, + authContext, ); } @@ -61,7 +61,6 @@ export class CreatePersonService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const personRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -86,6 +85,7 @@ export class CreatePersonService { return restoredPeople.raw; }, + authContext, ); } diff --git a/packages/twenty-server/src/modules/dashboard-sync/services/dashboard-sync.service.ts b/packages/twenty-server/src/modules/dashboard-sync/services/dashboard-sync.service.ts index aae3cb9d091..17dea0213a7 100644 --- a/packages/twenty-server/src/modules/dashboard-sync/services/dashboard-sync.service.ts +++ b/packages/twenty-server/src/modules/dashboard-sync/services/dashboard-sync.service.ts @@ -60,7 +60,6 @@ export class DashboardSyncService { try { await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const dashboardRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -71,6 +70,7 @@ export class DashboardSyncService { await dashboardRepository.update({ pageLayoutId }, { updatedAt }); }, + authContext, ); } catch (error) { this.logger.error( diff --git a/packages/twenty-server/src/modules/dashboard/services/dashboard-duplication.service.ts b/packages/twenty-server/src/modules/dashboard/services/dashboard-duplication.service.ts index a4cb1c91cb2..ce72656ebbc 100644 --- a/packages/twenty-server/src/modules/dashboard/services/dashboard-duplication.service.ts +++ b/packages/twenty-server/src/modules/dashboard/services/dashboard-duplication.service.ts @@ -44,7 +44,6 @@ export class DashboardDuplicationService { const workspaceId = workspace.id; return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext as WorkspaceAuthContext, async () => { const dashboardRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -108,6 +107,7 @@ export class DashboardDuplicationService { throw error; } }, + authContext as WorkspaceAuthContext, ); } diff --git a/packages/twenty-server/src/modules/dashboard/services/dashboard-to-page-layout-sync.service.ts b/packages/twenty-server/src/modules/dashboard/services/dashboard-to-page-layout-sync.service.ts index aec90766cea..398457d8fa8 100644 --- a/packages/twenty-server/src/modules/dashboard/services/dashboard-to-page-layout-sync.service.ts +++ b/packages/twenty-server/src/modules/dashboard/services/dashboard-to-page-layout-sync.service.ts @@ -52,32 +52,29 @@ export class DashboardToPageLayoutSyncService { }): Promise { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const dashboardRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'dashboard', - { shouldBypassPermissionChecks: true }, - ); - - const dashboards = await dashboardRepository.find({ - where: { - id: In(dashboardIds), - }, - withDeleted: true, - }); - - const pageLayoutIds = dashboards - .map((dashboard) => dashboard.pageLayoutId) - .filter(isDefined); - - await this.pageLayoutService.destroyMany({ - ids: pageLayoutIds, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const dashboardRepository = + await this.globalWorkspaceOrmManager.getRepository( workspaceId, - }); - }, - ); + 'dashboard', + { shouldBypassPermissionChecks: true }, + ); + + const dashboards = await dashboardRepository.find({ + where: { + id: In(dashboardIds), + }, + withDeleted: true, + }); + + const pageLayoutIds = dashboards + .map((dashboard) => dashboard.pageLayoutId) + .filter(isDefined); + + await this.pageLayoutService.destroyMany({ + ids: pageLayoutIds, + workspaceId, + }); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/dashboard/tools/create-complete-dashboard.tool.ts b/packages/twenty-server/src/modules/dashboard/tools/create-complete-dashboard.tool.ts index d8c645f6511..2a301a2316e 100644 --- a/packages/twenty-server/src/modules/dashboard/tools/create-complete-dashboard.tool.ts +++ b/packages/twenty-server/src/modules/dashboard/tools/create-complete-dashboard.tool.ts @@ -206,27 +206,24 @@ const createDashboardRecord = async ( ): Promise => { const authContext = buildSystemAuthContext(context.workspaceId); - return deps.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const dashboardRepository = - await deps.globalWorkspaceOrmManager.getRepository( - context.workspaceId, - 'dashboard', - { shouldBypassPermissionChecks: true }, - ); + return deps.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const dashboardRepository = + await deps.globalWorkspaceOrmManager.getRepository( + context.workspaceId, + 'dashboard', + { shouldBypassPermissionChecks: true }, + ); - const position = await deps.recordPositionService.buildRecordPosition({ - value: 'first', - objectMetadata: { isCustom: false, nameSingular: 'dashboard' }, - workspaceId: context.workspaceId, - }); + const position = await deps.recordPositionService.buildRecordPosition({ + value: 'first', + objectMetadata: { isCustom: false, nameSingular: 'dashboard' }, + workspaceId: context.workspaceId, + }); - const dashboard = { id: uuidv4(), title, pageLayoutId, position }; + const dashboard = { id: uuidv4(), title, pageLayoutId, position }; - await dashboardRepository.insert(dashboard); + await dashboardRepository.insert(dashboard); - return dashboard.id; - }, - ); + return dashboard.id; + }, authContext); }; diff --git a/packages/twenty-server/src/modules/dashboard/tools/get-dashboard.tool.ts b/packages/twenty-server/src/modules/dashboard/tools/get-dashboard.tool.ts index 6d79a706782..6abbafae662 100644 --- a/packages/twenty-server/src/modules/dashboard/tools/get-dashboard.tool.ts +++ b/packages/twenty-server/src/modules/dashboard/tools/get-dashboard.tool.ts @@ -27,7 +27,6 @@ export const createGetDashboardTool = ( const dashboard = await deps.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const repo = await deps.globalWorkspaceOrmManager.getRepository( context.workspaceId, @@ -37,6 +36,7 @@ export const createGetDashboardTool = ( return repo.findOne({ where: { id: parameters.dashboardId } }); }, + authContext, ); if (!isDefined(dashboard)) { diff --git a/packages/twenty-server/src/modules/dashboard/tools/list-dashboards.tool.ts b/packages/twenty-server/src/modules/dashboard/tools/list-dashboards.tool.ts index aacd2e869f8..7807ef14a16 100644 --- a/packages/twenty-server/src/modules/dashboard/tools/list-dashboards.tool.ts +++ b/packages/twenty-server/src/modules/dashboard/tools/list-dashboards.tool.ts @@ -30,7 +30,6 @@ export const createListDashboardsTool = ( const dashboards = await deps.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const repo = await deps.globalWorkspaceOrmManager.getRepository( context.workspaceId, @@ -40,6 +39,7 @@ export const createListDashboardsTool = ( return repo.find({ take: limit, order: { position: 'ASC' } }); }, + authContext, ); const dashboardList = dashboards.map((d) => ({ diff --git a/packages/twenty-server/src/modules/favorite-folder/listeners/favorite-folder.listener.ts b/packages/twenty-server/src/modules/favorite-folder/listeners/favorite-folder.listener.ts index eeb7dc63718..c702139240d 100644 --- a/packages/twenty-server/src/modules/favorite-folder/listeners/favorite-folder.listener.ts +++ b/packages/twenty-server/src/modules/favorite-folder/listeners/favorite-folder.listener.ts @@ -25,22 +25,19 @@ export class FavoriteFolderDeletionListener { const workspaceId = payload.workspaceId; const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - for (const eventPayload of payload.events) { - const favoriteRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'favorite', - ); - - await favoriteRepository.update( - { favoriteFolderId: eventPayload.recordId }, - { deletedAt: new Date().toISOString() }, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + for (const eventPayload of payload.events) { + const favoriteRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'favorite', ); - } - }, - ); + + await favoriteRepository.update( + { favoriteFolderId: eventPayload.recordId }, + { deletedAt: new Date().toISOString() }, + ); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/favorite/services/favorite-deletion.service.ts b/packages/twenty-server/src/modules/favorite/services/favorite-deletion.service.ts index 0421db9117f..cf131d875d3 100644 --- a/packages/twenty-server/src/modules/favorite/services/favorite-deletion.service.ts +++ b/packages/twenty-server/src/modules/favorite/services/favorite-deletion.service.ts @@ -28,68 +28,65 @@ export class FavoriteDeletionService { ): Promise { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const favoriteRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'favorite', - ); - - const favoriteObjectMetadata = - await this.objectMetadataRepository.findOne({ - where: { - nameSingular: 'favorite', - workspaceId, - }, - }); - - if (!favoriteObjectMetadata) { - throw new Error('Favorite object metadata not found'); - } - - const favoriteFields = await this.fieldMetadataRepository.find({ - where: { - objectMetadataId: favoriteObjectMetadata.id, - type: FieldMetadataType.RELATION, - }, - }); - - const favoritesToDelete = await favoriteRepository.find({ - select: { - id: true, - }, - where: favoriteFields.map((field) => ({ - [`${field.name}Id`]: In(deletedRecordIds), - })), - withDeleted: true, - }); - - if (favoritesToDelete.length === 0) { - return; - } - - const favoriteIdsToDelete = favoritesToDelete.map( - (favorite) => favorite.id, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const favoriteRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'favorite', ); - const batches: string[][] = []; + const favoriteObjectMetadata = + await this.objectMetadataRepository.findOne({ + where: { + nameSingular: 'favorite', + workspaceId, + }, + }); - for ( - let i = 0; - i < favoriteIdsToDelete.length; - i += FAVORITE_DELETION_BATCH_SIZE - ) { - batches.push( - favoriteIdsToDelete.slice(i, i + FAVORITE_DELETION_BATCH_SIZE), - ); - } + if (!favoriteObjectMetadata) { + throw new Error('Favorite object metadata not found'); + } - for (const batch of batches) { - await favoriteRepository.delete(batch); - } - }, - ); + const favoriteFields = await this.fieldMetadataRepository.find({ + where: { + objectMetadataId: favoriteObjectMetadata.id, + type: FieldMetadataType.RELATION, + }, + }); + + const favoritesToDelete = await favoriteRepository.find({ + select: { + id: true, + }, + where: favoriteFields.map((field) => ({ + [`${field.name}Id`]: In(deletedRecordIds), + })), + withDeleted: true, + }); + + if (favoritesToDelete.length === 0) { + return; + } + + const favoriteIdsToDelete = favoritesToDelete.map( + (favorite) => favorite.id, + ); + + const batches: string[][] = []; + + for ( + let i = 0; + i < favoriteIdsToDelete.length; + i += FAVORITE_DELETION_BATCH_SIZE + ) { + batches.push( + favoriteIdsToDelete.slice(i, i + FAVORITE_DELETION_BATCH_SIZE), + ); + } + + for (const batch of batches) { + await favoriteRepository.delete(batch); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/match-participant/match-participant.service.ts b/packages/twenty-server/src/modules/match-participant/match-participant.service.ts index 7bec88a6aa9..1379a439fe5 100644 --- a/packages/twenty-server/src/modules/match-participant/match-participant.service.ts +++ b/packages/twenty-server/src/modules/match-participant/match-participant.service.ts @@ -210,36 +210,32 @@ export class MatchParticipantService< }: MatchParticipantsForWorkspaceMembersArgs) { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const participantRepository = await this.getParticipantRepository( - workspaceId, - objectMetadataName, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const participantRepository = await this.getParticipantRepository( + workspaceId, + objectMetadataName, + ); - const participants = await participantRepository.find({ - where: { - workspaceMemberId: In(participantMatching.workspaceMemberIds), - }, - }); + const participants = await participantRepository.find({ + where: { + workspaceMemberId: In(participantMatching.workspaceMemberIds), + }, + }); - const tobeRematchedParticipants = participants.map((participant) => { - return { - ...participant, - workspaceMemberId: null, - }; - }); + const tobeRematchedParticipants = participants.map((participant) => { + return { + ...participant, + workspaceMemberId: null, + }; + }); - await this.matchParticipants({ - matchWith: 'workspaceMemberOnly', - participants: - tobeRematchedParticipants as ParticipantWorkspaceEntity[], - objectMetadataName, - workspaceId, - }); - }, - ); + await this.matchParticipants({ + matchWith: 'workspaceMemberOnly', + participants: tobeRematchedParticipants as ParticipantWorkspaceEntity[], + objectMetadataName, + workspaceId, + }); + }, authContext); } public async matchParticipantsForPeople({ @@ -249,56 +245,53 @@ export class MatchParticipantService< }: MatchParticipantsForPeopleArgs) { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const participantRepository = await this.getParticipantRepository( - workspaceId, - objectMetadataName, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const participantRepository = await this.getParticipantRepository( + workspaceId, + objectMetadataName, + ); - let participantsMatchingPersonEmails: ParticipantWorkspaceEntity[] = []; - let participantsMatchingPersonId: ParticipantWorkspaceEntity[] = []; + let participantsMatchingPersonEmails: ParticipantWorkspaceEntity[] = []; + let participantsMatchingPersonId: ParticipantWorkspaceEntity[] = []; - if (participantMatching.personIds.length > 0) { - participantsMatchingPersonId = (await participantRepository.find({ - where: { - personId: In(participantMatching.personIds), - }, - })) as ParticipantWorkspaceEntity[]; - } - - if (participantMatching.personEmails.length > 0) { - participantsMatchingPersonEmails = (await participantRepository.find({ - where: { - handle: In(participantMatching.personEmails), - }, - })) as ParticipantWorkspaceEntity[]; - } - - const uniqueParticipants = [ - ...new Set([ - ...participantsMatchingPersonId, - ...participantsMatchingPersonEmails, - ]), - ]; - - const tobeRematchedParticipants = uniqueParticipants.map( - (participant) => { - return { - ...participant, - personId: null, - }; + if (participantMatching.personIds.length > 0) { + participantsMatchingPersonId = (await participantRepository.find({ + where: { + personId: In(participantMatching.personIds), }, - ); + })) as ParticipantWorkspaceEntity[]; + } - await this.matchParticipants({ - matchWith: 'personOnly', - participants: tobeRematchedParticipants, - objectMetadataName, - workspaceId, - }); - }, - ); + if (participantMatching.personEmails.length > 0) { + participantsMatchingPersonEmails = (await participantRepository.find({ + where: { + handle: In(participantMatching.personEmails), + }, + })) as ParticipantWorkspaceEntity[]; + } + + const uniqueParticipants = [ + ...new Set([ + ...participantsMatchingPersonId, + ...participantsMatchingPersonEmails, + ]), + ]; + + const tobeRematchedParticipants = uniqueParticipants.map( + (participant) => { + return { + ...participant, + personId: null, + }; + }, + ); + + await this.matchParticipants({ + matchWith: 'personOnly', + participants: tobeRematchedParticipants, + objectMetadataName, + workspaceId, + }); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts b/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts index b3839d5fea6..c2858b5fe34 100644 --- a/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts +++ b/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts @@ -36,132 +36,129 @@ export class BlocklistItemDeleteMessagesJob { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const blocklistItemIds = data.events.map( - (eventPayload) => eventPayload.recordId, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const blocklistItemIds = data.events.map( + (eventPayload) => eventPayload.recordId, + ); + + const blocklistRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'blocklist', ); - const blocklistRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'blocklist', - ); + const blocklist = await blocklistRepository.find({ + where: { + id: Any(blocklistItemIds), + }, + }); - const blocklist = await blocklistRepository.find({ - where: { - id: Any(blocklistItemIds), + const handlesToDeleteByWorkspaceMemberIdMap = blocklist.reduce( + (acc, blocklistItem) => { + const { handle, workspaceMemberId } = blocklistItem; + + if (!acc.has(workspaceMemberId)) { + acc.set(workspaceMemberId, []); + } + + if (!isDefined(handle)) { + return acc; + } + + acc.get(workspaceMemberId)?.push(handle); + + return acc; + }, + new Map(), + ); + + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); + + const messageChannelMessageAssociationRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannelMessageAssociation', + ); + + for (const workspaceMemberId of handlesToDeleteByWorkspaceMemberIdMap.keys()) { + const handles = + handlesToDeleteByWorkspaceMemberIdMap.get(workspaceMemberId); + + if (!handles) { + continue; + } + + const rolesToDelete = [ + MessageParticipantRole.FROM, + MessageParticipantRole.TO, + ] as const; + + const messageChannels = await messageChannelRepository.find({ + select: { + id: true, + handle: true, + connectedAccount: { + handleAliases: true, + }, }, + where: { + connectedAccount: { + accountOwnerId: workspaceMemberId, + }, + }, + relations: ['connectedAccount'], }); - const handlesToDeleteByWorkspaceMemberIdMap = blocklist.reduce( - (acc, blocklistItem) => { - const { handle, workspaceMemberId } = blocklistItem; + for (const messageChannel of messageChannels) { + const messageChannelHandles = [messageChannel.handle]; - if (!acc.has(workspaceMemberId)) { - acc.set(workspaceMemberId, []); - } + if (messageChannel.connectedAccount.handleAliases) { + messageChannelHandles.push( + ...messageChannel.connectedAccount.handleAliases.split(','), + ); + } - if (!isDefined(handle)) { - return acc; - } + const handleConditions = handles.map((handle) => { + const isHandleDomain = handle.startsWith('@'); - acc.get(workspaceMemberId)?.push(handle); + return isHandleDomain + ? { + handle: And( + Or(ILike(`%${handle}`), ILike(`%.${handle.slice(1)}`)), + Not(In(messageChannelHandles)), + ), + role: In(rolesToDelete), + } + : { handle, role: In(rolesToDelete) }; + }); - return acc; - }, - new Map(), - ); + const messageChannelMessageAssociationsToDelete = + await messageChannelMessageAssociationRepository.find({ + where: { + messageChannelId: messageChannel.id, + message: { + messageParticipants: handleConditions, + }, + }, + }); - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); - - const messageChannelMessageAssociationRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannelMessageAssociation', - ); - - for (const workspaceMemberId of handlesToDeleteByWorkspaceMemberIdMap.keys()) { - const handles = - handlesToDeleteByWorkspaceMemberIdMap.get(workspaceMemberId); - - if (!handles) { + if (messageChannelMessageAssociationsToDelete.length === 0) { continue; } - const rolesToDelete = [ - MessageParticipantRole.FROM, - MessageParticipantRole.TO, - ] as const; - - const messageChannels = await messageChannelRepository.find({ - select: { - id: true, - handle: true, - connectedAccount: { - handleAliases: true, - }, - }, - where: { - connectedAccount: { - accountOwnerId: workspaceMemberId, - }, - }, - relations: ['connectedAccount'], - }); - - for (const messageChannel of messageChannels) { - const messageChannelHandles = [messageChannel.handle]; - - if (messageChannel.connectedAccount.handleAliases) { - messageChannelHandles.push( - ...messageChannel.connectedAccount.handleAliases.split(','), - ); - } - - const handleConditions = handles.map((handle) => { - const isHandleDomain = handle.startsWith('@'); - - return isHandleDomain - ? { - handle: And( - Or(ILike(`%${handle}`), ILike(`%.${handle.slice(1)}`)), - Not(In(messageChannelHandles)), - ), - role: In(rolesToDelete), - } - : { handle, role: In(rolesToDelete) }; - }); - - const messageChannelMessageAssociationsToDelete = - await messageChannelMessageAssociationRepository.find({ - where: { - messageChannelId: messageChannel.id, - message: { - messageParticipants: handleConditions, - }, - }, - }); - - if (messageChannelMessageAssociationsToDelete.length === 0) { - continue; - } - - await messageChannelMessageAssociationRepository.delete( - messageChannelMessageAssociationsToDelete.map(({ id }) => id), - ); - } + await messageChannelMessageAssociationRepository.delete( + messageChannelMessageAssociationsToDelete.map(({ id }) => id), + ); } + } - await this.threadCleanerService.cleanOrphanMessagesAndThreads( - workspaceId, - ); - }, - ); + await this.threadCleanerService.cleanOrphanMessagesAndThreads( + workspaceId, + ); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-reimport-messages.job.ts b/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-reimport-messages.job.ts index 72246a5c2db..a2b6d1c9860 100644 --- a/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-reimport-messages.job.ts +++ b/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-reimport-messages.job.ts @@ -36,37 +36,32 @@ export class BlocklistReimportMessagesJob { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); - for (const eventPayload of data.events) { - const workspaceMemberId = - eventPayload.properties.before.workspaceMemberId; + for (const eventPayload of data.events) { + const workspaceMemberId = + eventPayload.properties.before.workspaceMemberId; - const messageChannels = await messageChannelRepository.find({ - select: ['id'], - where: { - connectedAccount: { - accountOwnerId: workspaceMemberId, - }, - syncStage: Not( - MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, - ), + const messageChannels = await messageChannelRepository.find({ + select: ['id'], + where: { + connectedAccount: { + accountOwnerId: workspaceMemberId, }, - }); + syncStage: Not(MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING), + }, + }); - await this.messagingChannelSyncStatusService.resetAndMarkAsMessagesListFetchPending( - messageChannels.map((messageChannel) => messageChannel.id), - workspaceId, - ); - } - }, - ); + await this.messagingChannelSyncStatusService.resetAndMarkAsMessagesListFetchPending( + messageChannels.map((messageChannel) => messageChannel.id), + workspaceId, + ); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts index f88b5f75a30..3392eb1629f 100644 --- a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts @@ -56,7 +56,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { }), executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }; beforeEach(async () => { diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.ts index c098c50d741..c5e4551cf8f 100644 --- a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.ts +++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.ts @@ -28,7 +28,6 @@ export class ApplyMessagesVisibilityRestrictionsService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const messageChannelMessageAssociationRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -128,6 +127,7 @@ export class ApplyMessagesVisibilityRestrictionsService { return messages; }, + authContext, ); } } diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-channel-update-one.pre-query.hook.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-channel-update-one.pre-query.hook.ts index 3eeb1674900..7f4d107c9b4 100644 --- a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-channel-update-one.pre-query.hook.ts +++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-channel-update-one.pre-query.hook.ts @@ -56,7 +56,6 @@ export class MessageChannelUpdateOnePreQueryHook const systemAuthContext = buildSystemAuthContext(workspace.id); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - systemAuthContext, async () => { const messageChannelRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -144,6 +143,7 @@ export class MessageChannelUpdateOnePreQueryHook return payload; }, + systemAuthContext, ); } } diff --git a/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts b/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts index ff9a29e4b2d..81d89232609 100644 --- a/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts +++ b/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts @@ -44,21 +44,18 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); - await messageChannelRepository.update(messageChannelIds, { - syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, - ...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}), - }); - }, - ); + await messageChannelRepository.update(messageChannelIds, { + syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, + ...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}), + }); + }, authContext); } public async markAsMessagesImportPending( @@ -72,21 +69,18 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); - await messageChannelRepository.update(messageChannelIds, { - syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_PENDING, - ...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}), - }); - }, - ); + await messageChannelRepository.update(messageChannelIds, { + syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_PENDING, + ...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}), + }); + }, authContext); } public async resetAndMarkAsMessagesListFetchPending( @@ -105,37 +99,34 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); - - const messageFolderRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageFolder', - ); - - await messageChannelRepository.update(messageChannelIds, { - syncCursor: '', - syncStageStartedAt: null, - throttleFailureCount: 0, - pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction.NONE, - }); - - await messageFolderRepository.update( - { messageChannelId: In(messageChannelIds) }, - { - syncCursor: '', - pendingSyncAction: MessageFolderPendingSyncAction.NONE, - }, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', ); - }, - ); + + const messageFolderRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageFolder', + ); + + await messageChannelRepository.update(messageChannelIds, { + syncCursor: '', + syncStageStartedAt: null, + throttleFailureCount: 0, + pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction.NONE, + }); + + await messageFolderRepository.update( + { messageChannelId: In(messageChannelIds) }, + { + syncCursor: '', + pendingSyncAction: MessageFolderPendingSyncAction.NONE, + }, + ); + }, authContext); await this.markAsMessagesListFetchPending(messageChannelIds, workspaceId); } @@ -150,20 +141,17 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); - await messageChannelRepository.update(messageChannelIds, { - syncStageStartedAt: null, - }); - }, - ); + await messageChannelRepository.update(messageChannelIds, { + syncStageStartedAt: null, + }); + }, authContext); } public async markAsMessagesListFetchScheduled( @@ -176,22 +164,19 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); - await messageChannelRepository.update(messageChannelIds, { - syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED, - syncStatus: MessageChannelSyncStatus.ONGOING, - syncStageStartedAt: new Date().toISOString(), - }); - }, - ); + await messageChannelRepository.update(messageChannelIds, { + syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED, + syncStatus: MessageChannelSyncStatus.ONGOING, + syncStageStartedAt: new Date().toISOString(), + }); + }, authContext); } public async markAsMessagesListFetchOngoing( @@ -204,21 +189,18 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); - await messageChannelRepository.update(messageChannelIds, { - syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING, - syncStatus: MessageChannelSyncStatus.ONGOING, - }); - }, - ); + await messageChannelRepository.update(messageChannelIds, { + syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING, + syncStatus: MessageChannelSyncStatus.ONGOING, + }); + }, authContext); } public async markAsCompletedAndMarkAsMessagesListFetchPending( @@ -231,24 +213,21 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); - await messageChannelRepository.update(messageChannelIds, { - syncStatus: MessageChannelSyncStatus.ACTIVE, - syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, - throttleFailureCount: 0, - syncStageStartedAt: null, - syncedAt: new Date().toISOString(), - }); - }, - ); + await messageChannelRepository.update(messageChannelIds, { + syncStatus: MessageChannelSyncStatus.ACTIVE, + syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, + throttleFailureCount: 0, + syncStageStartedAt: null, + syncedAt: new Date().toISOString(), + }); + }, authContext); await this.metricsService.batchIncrementCounter({ key: MetricsKeys.MessageChannelSyncJobActive, @@ -266,20 +245,17 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); - await messageChannelRepository.update(messageChannelIds, { - syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED, - }); - }, - ); + await messageChannelRepository.update(messageChannelIds, { + syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED, + }); + }, authContext); } public async markAsMessagesImportOngoing( @@ -292,21 +268,18 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); - await messageChannelRepository.update(messageChannelIds, { - syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING, - syncStageStartedAt: new Date().toISOString(), - }); - }, - ); + await messageChannelRepository.update(messageChannelIds, { + syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING, + syncStageStartedAt: new Date().toISOString(), + }); + }, authContext); } public async markAsFailed( @@ -322,64 +295,59 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); + + await messageChannelRepository.update(messageChannelIds, { + syncStage: MessageChannelSyncStage.FAILED, + syncStatus: syncStatus, + }); + + const metricsKey = + syncStatus === MessageChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS + ? MetricsKeys.MessageChannelSyncJobFailedInsufficientPermissions + : MetricsKeys.MessageChannelSyncJobFailedUnknown; + + await this.metricsService.batchIncrementCounter({ + key: metricsKey, + eventIds: messageChannelIds, + }); + + if ( + syncStatus === MessageChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS + ) { + const connectedAccountRepository = + await this.globalWorkspaceOrmManager.getRepository( workspaceId, - 'messageChannel', + 'connectedAccount', ); - await messageChannelRepository.update(messageChannelIds, { - syncStage: MessageChannelSyncStage.FAILED, - syncStatus: syncStatus, + const messageChannels = await messageChannelRepository.find({ + select: ['id', 'connectedAccountId'], + where: { id: Any(messageChannelIds) }, }); - const metricsKey = - syncStatus === - MessageChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS - ? MetricsKeys.MessageChannelSyncJobFailedInsufficientPermissions - : MetricsKeys.MessageChannelSyncJobFailedUnknown; + const connectedAccountIds = messageChannels.map( + (messageChannel) => messageChannel.connectedAccountId, + ); - await this.metricsService.batchIncrementCounter({ - key: metricsKey, - eventIds: messageChannelIds, - }); + await connectedAccountRepository.update( + { id: Any(connectedAccountIds) }, + { + authFailedAt: new Date(), + }, + ); - if ( - syncStatus === - MessageChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS - ) { - const connectedAccountRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'connectedAccount', - ); - - const messageChannels = await messageChannelRepository.find({ - select: ['id', 'connectedAccountId'], - where: { id: Any(messageChannelIds) }, - }); - - const connectedAccountIds = messageChannels.map( - (messageChannel) => messageChannel.connectedAccountId, - ); - - await connectedAccountRepository.update( - { id: Any(connectedAccountIds) }, - { - authFailedAt: new Date(), - }, - ); - - await this.addToAccountsToReconnect( - messageChannels.map((messageChannel) => messageChannel.id), - workspaceId, - ); - } - }, - ); + await this.addToAccountsToReconnect( + messageChannels.map((messageChannel) => messageChannel.id), + workspaceId, + ); + } + }, authContext); } private async addToAccountsToReconnect( diff --git a/packages/twenty-server/src/modules/messaging/message-cleaner/commands/messaging-reset-channel.command.ts b/packages/twenty-server/src/modules/messaging/message-cleaner/commands/messaging-reset-channel.command.ts index 62f2bb5ad16..152a827828e 100644 --- a/packages/twenty-server/src/modules/messaging/message-cleaner/commands/messaging-reset-channel.command.ts +++ b/packages/twenty-server/src/modules/messaging/message-cleaner/commands/messaging-reset-channel.command.ts @@ -38,52 +38,49 @@ export class MessagingResetChannelCommand extends CommandRunner { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); - - this.logger.log( - `No message channel ID provided, resetting all message channels in workspace ${workspaceId}`, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', ); - const messageChannels = await messageChannelRepository.find({ - where: { - ...(isDefined(messageChannelId) ? { id: messageChannelId } : {}), - }, - }); + this.logger.log( + `No message channel ID provided, resetting all message channels in workspace ${workspaceId}`, + ); - if (messageChannels.length === 0) { - this.logger.log( - `No message channels found in workspace ${workspaceId}`, - ); - - return; - } + const messageChannels = await messageChannelRepository.find({ + where: { + ...(isDefined(messageChannelId) ? { id: messageChannelId } : {}), + }, + }); + if (messageChannels.length === 0) { this.logger.log( - `Found ${messageChannels.length} message channels to reset`, + `No message channels found in workspace ${workspaceId}`, ); - for (const messageChannel of messageChannels) { - await this.messagingChannelSyncStatusService.resetAndMarkAsMessagesListFetchPending( - [messageChannel.id], - workspaceId, - ); - await this.messagingMessageCleanerService.cleanOrphanMessagesAndThreads( - workspaceId, - ); - } + return; + } - this.logger.log( - `Successfully reset all ${messageChannels.length} message channels in workspace ${workspaceId}`, + this.logger.log( + `Found ${messageChannels.length} message channels to reset`, + ); + + for (const messageChannel of messageChannels) { + await this.messagingChannelSyncStatusService.resetAndMarkAsMessagesListFetchPending( + [messageChannel.id], + workspaceId, ); - }, - ); + await this.messagingMessageCleanerService.cleanOrphanMessagesAndThreads( + workspaceId, + ); + } + + this.logger.log( + `Successfully reset all ${messageChannels.length} message channels in workspace ${workspaceId}`, + ); + }, authContext); } @Option({ diff --git a/packages/twenty-server/src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service.ts b/packages/twenty-server/src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service.ts index 39661ad09d0..8a22c85db35 100644 --- a/packages/twenty-server/src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service.ts @@ -29,197 +29,191 @@ export class MessagingMessageCleanerService { }) { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'message', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'message', + ); - const messageChannelMessageAssociationRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannelMessageAssociation', - ); + const messageChannelMessageAssociationRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannelMessageAssociation', + ); - const messageThreadRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageThread', - ); + const messageThreadRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageThread', + ); - const messageExternalIdsChunks = chunk(messageExternalIds, 500); + const messageExternalIdsChunks = chunk(messageExternalIds, 500); - for (const messageExternalIdsChunk of messageExternalIdsChunks) { - const messageChannelMessageAssociationsToDelete = - await messageChannelMessageAssociationRepository.find({ - where: { - messageExternalId: In(messageExternalIdsChunk), - messageChannelId, - }, - }); - - if (messageChannelMessageAssociationsToDelete.length <= 0) { - continue; - } - - await messageChannelMessageAssociationRepository.delete( - messageChannelMessageAssociationsToDelete.map(({ id }) => id), - ); - - this.logger.log( - `WorkspaceId: ${workspaceId} Deleting ${messageChannelMessageAssociationsToDelete.length} message channel message associations`, - ); - - const orphanMessages = await messageRepository.find({ + for (const messageExternalIdsChunk of messageExternalIdsChunks) { + const messageChannelMessageAssociationsToDelete = + await messageChannelMessageAssociationRepository.find({ where: { - id: In( - messageChannelMessageAssociationsToDelete.map( - ({ messageId }) => messageId, - ), - ), - messageChannelMessageAssociations: { - id: IsNull(), - }, + messageExternalId: In(messageExternalIdsChunk), + messageChannelId, }, }); - if (orphanMessages.length <= 0) { - continue; - } - - this.logger.log( - `WorkspaceId: ${workspaceId} Deleting ${orphanMessages.length} orphan messages`, - ); - - await messageRepository.delete(orphanMessages.map(({ id }) => id)); - - const orphanMessageThreads = await messageThreadRepository.find({ - where: { - id: In( - orphanMessages.map(({ messageThreadId }) => messageThreadId), - ), - messages: { - id: IsNull(), - }, - }, - }); - - if (orphanMessageThreads.length <= 0) { - continue; - } - - this.logger.log( - `WorkspaceId: ${workspaceId} Deleting ${orphanMessageThreads.length} orphan message threads`, - ); - - await messageThreadRepository.delete( - orphanMessageThreads.map(({ id }) => id), - ); + if (messageChannelMessageAssociationsToDelete.length <= 0) { + continue; } - }, - ); + + await messageChannelMessageAssociationRepository.delete( + messageChannelMessageAssociationsToDelete.map(({ id }) => id), + ); + + this.logger.log( + `WorkspaceId: ${workspaceId} Deleting ${messageChannelMessageAssociationsToDelete.length} message channel message associations`, + ); + + const orphanMessages = await messageRepository.find({ + where: { + id: In( + messageChannelMessageAssociationsToDelete.map( + ({ messageId }) => messageId, + ), + ), + messageChannelMessageAssociations: { + id: IsNull(), + }, + }, + }); + + if (orphanMessages.length <= 0) { + continue; + } + + this.logger.log( + `WorkspaceId: ${workspaceId} Deleting ${orphanMessages.length} orphan messages`, + ); + + await messageRepository.delete(orphanMessages.map(({ id }) => id)); + + const orphanMessageThreads = await messageThreadRepository.find({ + where: { + id: In( + orphanMessages.map(({ messageThreadId }) => messageThreadId), + ), + messages: { + id: IsNull(), + }, + }, + }); + + if (orphanMessageThreads.length <= 0) { + continue; + } + + this.logger.log( + `WorkspaceId: ${workspaceId} Deleting ${orphanMessageThreads.length} orphan message threads`, + ); + + await messageThreadRepository.delete( + orphanMessageThreads.map(({ id }) => id), + ); + } + }, authContext); } public async cleanOrphanMessagesAndThreads(workspaceId: string) { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageThreadRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageThread', - ); - - const messageRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'message', - ); - - const workspaceDataSource = - await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource(); - - await workspaceDataSource.transaction( - async (transactionManager: WorkspaceEntityManager) => { - await deleteUsingPagination( - workspaceId, - 500, - async ( - limit: number, - offset: number, - _workspaceId: string, - transactionManager: WorkspaceEntityManager, - ) => { - const nonAssociatedMessages = await messageRepository.find( - { - where: { - messageChannelMessageAssociations: { - id: IsNull(), - }, - }, - take: limit, - skip: offset, - relations: ['messageChannelMessageAssociations'], - }, - transactionManager, - ); - - return nonAssociatedMessages.map(({ id }) => id); - }, - async ( - ids: string[], - workspaceId: string, - transactionManager?: WorkspaceEntityManager, - ) => { - this.logger.log( - `WorkspaceId: ${workspaceId} Deleting ${ids.length} messages from message cleaner`, - ); - await messageRepository.delete(ids, transactionManager); - }, - transactionManager, - ); - - await deleteUsingPagination( - workspaceId, - 500, - async ( - limit: number, - offset: number, - _workspaceId: string, - transactionManager?: WorkspaceEntityManager, - ) => { - const orphanThreads = await messageThreadRepository.find( - { - where: { - messages: { - id: IsNull(), - }, - }, - take: limit, - skip: offset, - }, - transactionManager, - ); - - return orphanThreads.map(({ id }) => id); - }, - async ( - ids: string[], - _workspaceId: string, - transactionManager?: WorkspaceEntityManager, - ) => { - await messageThreadRepository.delete(ids, transactionManager); - }, - transactionManager, - ); - }, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageThreadRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageThread', ); - }, - ); + + const messageRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'message', + ); + + const workspaceDataSource = + await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource(); + + await workspaceDataSource.transaction( + async (transactionManager: WorkspaceEntityManager) => { + await deleteUsingPagination( + workspaceId, + 500, + async ( + limit: number, + offset: number, + _workspaceId: string, + transactionManager: WorkspaceEntityManager, + ) => { + const nonAssociatedMessages = await messageRepository.find( + { + where: { + messageChannelMessageAssociations: { + id: IsNull(), + }, + }, + take: limit, + skip: offset, + relations: ['messageChannelMessageAssociations'], + }, + transactionManager, + ); + + return nonAssociatedMessages.map(({ id }) => id); + }, + async ( + ids: string[], + workspaceId: string, + transactionManager?: WorkspaceEntityManager, + ) => { + this.logger.log( + `WorkspaceId: ${workspaceId} Deleting ${ids.length} messages from message cleaner`, + ); + await messageRepository.delete(ids, transactionManager); + }, + transactionManager, + ); + + await deleteUsingPagination( + workspaceId, + 500, + async ( + limit: number, + offset: number, + _workspaceId: string, + transactionManager?: WorkspaceEntityManager, + ) => { + const orphanThreads = await messageThreadRepository.find( + { + where: { + messages: { + id: IsNull(), + }, + }, + take: limit, + skip: offset, + }, + transactionManager, + ); + + return orphanThreads.map(({ id }) => id); + }, + async ( + ids: string[], + _workspaceId: string, + transactionManager?: WorkspaceEntityManager, + ) => { + await messageThreadRepository.delete(ids, transactionManager); + }, + transactionManager, + ); + }, + ); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.spec.ts index 1d3ae5cf13c..131d9cc0b23 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.spec.ts @@ -126,7 +126,9 @@ describe('SyncMessageFoldersService', () => { useValue: { executeInWorkspaceContext: jest .fn() - .mockImplementation((_, callback) => callback()), + .mockImplementation((callback: () => any, _authContext?: any) => + callback(), + ), getRepository: jest.fn().mockResolvedValue(mockRepository), getDataSourceForWorkspace: jest .fn() diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts index 9eac1f436a3..1dd442bd7ff 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts @@ -133,7 +133,6 @@ export class SyncMessageFoldersService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const messageFolderRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -188,6 +187,7 @@ export class SyncMessageFoldersService { }, ); }, + authContext, ); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/commands/messaging-trigger-message-list-fetch.command.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/commands/messaging-trigger-message-list-fetch.command.ts index 10cf4ad08d2..74aa8cbee62 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/commands/messaging-trigger-message-list-fetch.command.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/commands/messaging-trigger-message-list-fetch.command.ts @@ -51,63 +51,60 @@ export class MessagingTriggerMessageListFetchCommand extends CommandRunner { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); + + const whereCondition: Record = { + isSyncEnabled: true, + syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, + }; + + if (messageChannelId) { + whereCondition.id = messageChannelId; + } + + const messageChannels = + await messageChannelRepository.find(whereCondition); + + if (messageChannels.length === 0) { + this.logger.warn( + 'No message channels found with MESSAGE_LIST_FETCH_PENDING status', + ); + + return; + } + + this.logger.log( + `Found ${messageChannels.length} message channel(s) to process`, + ); + + for (const messageChannel of messageChannels) { + await messageChannelRepository.update(messageChannel.id, { + syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED, + syncStageStartedAt: new Date().toISOString(), + }); + + await this.messageQueueService.add( + MessagingMessageListFetchJob.name, + { + messageChannelId: messageChannel.id, workspaceId, - 'messageChannel', - ); - - const whereCondition: Record = { - isSyncEnabled: true, - syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, - }; - - if (messageChannelId) { - whereCondition.id = messageChannelId; - } - - const messageChannels = - await messageChannelRepository.find(whereCondition); - - if (messageChannels.length === 0) { - this.logger.warn( - 'No message channels found with MESSAGE_LIST_FETCH_PENDING status', - ); - - return; - } - - this.logger.log( - `Found ${messageChannels.length} message channel(s) to process`, + }, ); - for (const messageChannel of messageChannels) { - await messageChannelRepository.update(messageChannel.id, { - syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED, - syncStageStartedAt: new Date().toISOString(), - }); - - await this.messageQueueService.add( - MessagingMessageListFetchJob.name, - { - messageChannelId: messageChannel.id, - workspaceId, - }, - ); - - this.logger.log( - `Triggered fetch for message channel ${messageChannel.id}`, - ); - } - this.logger.log( - `Successfully triggered ${messageChannels.length} message list fetch job(s)`, + `Triggered fetch for message channel ${messageChannel.id}`, ); - }, - ); + } + + this.logger.log( + `Successfully triggered ${messageChannels.length} message list fetch job(s)`, + ); + }, authContext); } @Option({ diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts index 632ef76cd00..da5a4e7c47f 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts @@ -48,82 +48,79 @@ export class MessagingMessageListFetchJob { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); - const messageChannel = await messageChannelRepository.findOne({ - where: { - id: messageChannelId, - }, - relations: ['connectedAccount', 'messageFolders'], + const messageChannel = await messageChannelRepository.findOne({ + where: { + id: messageChannelId, + }, + relations: ['connectedAccount', 'messageFolders'], + }); + + if (!messageChannel) { + await this.messagingMonitoringService.track({ + eventName: 'message_list_fetch_job.error.message_channel_not_found', + messageChannelId, + workspaceId, }); - if (!messageChannel) { - await this.messagingMonitoringService.track({ - eventName: 'message_list_fetch_job.error.message_channel_not_found', - messageChannelId, - workspaceId, - }); + return; + } - return; - } + if ( + messageChannel.syncStage !== + MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED + ) { + return; + } + try { if ( - messageChannel.syncStage !== - MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED + isThrottled( + messageChannel.syncStageStartedAt, + messageChannel.throttleFailureCount, + ) ) { + await this.messageChannelSyncStatusService.markAsMessagesListFetchPending( + [messageChannel.id], + workspaceId, + true, + ); + return; } - try { - if ( - isThrottled( - messageChannel.syncStageStartedAt, - messageChannel.throttleFailureCount, - ) - ) { - await this.messageChannelSyncStatusService.markAsMessagesListFetchPending( - [messageChannel.id], - workspaceId, - true, - ); + await this.messagingMonitoringService.track({ + eventName: 'message_list_fetch.started', + workspaceId, + connectedAccountId: messageChannel.connectedAccount.id, + messageChannelId: messageChannel.id, + }); - return; - } + await this.messagingMessageListFetchService.processMessageListFetch( + messageChannel, + workspaceId, + ); - await this.messagingMonitoringService.track({ - eventName: 'message_list_fetch.started', - workspaceId, - connectedAccountId: messageChannel.connectedAccount.id, - messageChannelId: messageChannel.id, - }); - - await this.messagingMessageListFetchService.processMessageListFetch( - messageChannel, - workspaceId, - ); - - await this.messagingMonitoringService.track({ - eventName: 'message_list_fetch.completed', - workspaceId, - connectedAccountId: messageChannel.connectedAccount.id, - messageChannelId: messageChannel.id, - }); - } catch (error) { - await this.messageImportErrorHandlerService.handleDriverException( - error, - MessageImportSyncStep.MESSAGE_LIST_FETCH, - messageChannel, - workspaceId, - ); - } - }, - ); + await this.messagingMonitoringService.track({ + eventName: 'message_list_fetch.completed', + workspaceId, + connectedAccountId: messageChannel.connectedAccount.id, + messageChannelId: messageChannel.id, + }); + } catch (error) { + await this.messageImportErrorHandlerService.handleDriverException( + error, + MessageImportSyncStep.MESSAGE_LIST_FETCH, + messageChannel, + workspaceId, + ); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts index fd9bce545a4..d947f0f0e78 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts @@ -43,64 +43,61 @@ export class MessagingMessagesImportJob { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); - const messageChannel = await messageChannelRepository.findOne({ - where: { - id: messageChannelId, - }, - relations: ['connectedAccount'], + const messageChannel = await messageChannelRepository.findOne({ + where: { + id: messageChannelId, + }, + relations: ['connectedAccount'], + }); + + if (!messageChannel) { + await this.messagingMonitoringService.track({ + eventName: 'messages_import.error.message_channel_not_found', + messageChannelId, + workspaceId, }); - if (!messageChannel) { - await this.messagingMonitoringService.track({ - eventName: 'messages_import.error.message_channel_not_found', - messageChannelId, - workspaceId, - }); + return; + } - return; - } + if (!messageChannel?.isSyncEnabled) { + return; + } - if (!messageChannel?.isSyncEnabled) { - return; - } + if ( + messageChannel.syncStage !== + MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED + ) { + return; + } - if ( - messageChannel.syncStage !== - MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED - ) { - return; - } - - if ( - isThrottled( - messageChannel.syncStageStartedAt, - messageChannel.throttleFailureCount, - ) - ) { - await this.messageChannelSyncStatusService.markAsMessagesImportPending( - [messageChannel.id], - workspaceId, - true, - ); - - return; - } - - await this.messagingMessagesImportService.processMessageBatchImport( - messageChannel, - messageChannel.connectedAccount, + if ( + isThrottled( + messageChannel.syncStageStartedAt, + messageChannel.throttleFailureCount, + ) + ) { + await this.messageChannelSyncStatusService.markAsMessagesImportPending( + [messageChannel.id], workspaceId, + true, ); - }, - ); + + return; + } + + await this.messagingMessagesImportService.processMessageBatchImport( + messageChannel, + messageChannel.connectedAccount, + workspaceId, + ); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-ongoing-stale.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-ongoing-stale.job.ts index f013a3942db..701acd366e5 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-ongoing-stale.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-ongoing-stale.job.ts @@ -35,63 +35,60 @@ export class MessagingOngoingStaleJob { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); + + const messageChannels = await messageChannelRepository.find({ + where: { + syncStage: In([ + MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING, + MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING, + MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED, + MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED, + ]), + }, + }); + + for (const messageChannel of messageChannels) { + if ( + messageChannel.syncStageStartedAt && + isSyncStale(messageChannel.syncStageStartedAt) + ) { + await this.messageChannelSyncStatusService.resetSyncStageStartedAt( + [messageChannel.id], workspaceId, - 'messageChannel', ); - const messageChannels = await messageChannelRepository.find({ - where: { - syncStage: In([ - MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING, - MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING, - MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED, - MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED, - ]), - }, - }); - - for (const messageChannel of messageChannels) { - if ( - messageChannel.syncStageStartedAt && - isSyncStale(messageChannel.syncStageStartedAt) - ) { - await this.messageChannelSyncStatusService.resetSyncStageStartedAt( - [messageChannel.id], - workspaceId, - ); - - switch (messageChannel.syncStage) { - case MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING: - case MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED: - this.logger.log( - `Sync for message channel ${messageChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to MESSAGE_LIST_FETCH_PENDING`, - ); - await this.messageChannelSyncStatusService.markAsMessagesListFetchPending( - [messageChannel.id], - workspaceId, - ); - break; - case MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING: - case MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED: - this.logger.log( - `Sync for message channel ${messageChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to MESSAGES_IMPORT_PENDING`, - ); - await this.messageChannelSyncStatusService.markAsMessagesImportPending( - [messageChannel.id], - workspaceId, - ); - break; - default: - break; - } + switch (messageChannel.syncStage) { + case MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING: + case MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED: + this.logger.log( + `Sync for message channel ${messageChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to MESSAGE_LIST_FETCH_PENDING`, + ); + await this.messageChannelSyncStatusService.markAsMessagesListFetchPending( + [messageChannel.id], + workspaceId, + ); + break; + case MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING: + case MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED: + this.logger.log( + `Sync for message channel ${messageChannel.id} and workspace ${workspaceId} is stale. Setting sync stage to MESSAGES_IMPORT_PENDING`, + ); + await this.messageChannelSyncStatusService.markAsMessagesImportPending( + [messageChannel.id], + workspaceId, + ); + break; + default: + break; } } - }, - ); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-relaunch-failed-message-channel.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-relaunch-failed-message-channel.job.ts index c35b80ff704..d2a03bc7f6b 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-relaunch-failed-message-channel.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-relaunch-failed-message-channel.job.ts @@ -31,40 +31,37 @@ export class MessagingRelaunchFailedMessageChannelJob { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - { shouldBypassPermissionChecks: true }, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + { shouldBypassPermissionChecks: true }, + ); - const messageChannel = await messageChannelRepository.findOne({ - where: { - id: messageChannelId, + const messageChannel = await messageChannelRepository.findOne({ + where: { + id: messageChannelId, + }, + relations: { + connectedAccount: { + accountOwner: true, }, - relations: { - connectedAccount: { - accountOwner: true, - }, - }, - }); + }, + }); - if ( - !messageChannel || - messageChannel.syncStage !== MessageChannelSyncStage.FAILED || - messageChannel.syncStatus !== MessageChannelSyncStatus.FAILED_UNKNOWN - ) { - return; - } + if ( + !messageChannel || + messageChannel.syncStage !== MessageChannelSyncStage.FAILED || + messageChannel.syncStatus !== MessageChannelSyncStatus.FAILED_UNKNOWN + ) { + return; + } - await messageChannelRepository.update(messageChannelId, { - syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, - syncStatus: MessageChannelSyncStatus.ACTIVE, - }); - }, - ); + await messageChannelRepository.update(messageChannelId, { + syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, + syncStatus: MessageChannelSyncStatus.ACTIVE, + }); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-message-list-fetch.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-message-list-fetch.service.spec.ts index 285917a14ed..c09c46bdee5 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-message-list-fetch.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-message-list-fetch.service.spec.ts @@ -213,7 +213,7 @@ describe('MessagingMessageListFetchService', () => { executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), getRepository: jest.fn().mockImplementation((workspaceId, name) => { if (name === 'messageChannel') { return { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-messages-import.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-messages-import.service.spec.ts index 34dea3ba733..c9063227e8a 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-messages-import.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-messages-import.service.spec.ts @@ -113,7 +113,7 @@ describe('MessagingMessagesImportService', () => { }), executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }, }, { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-cursor.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-cursor.service.ts index 3b6ce11fc30..71c23ac8468 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-cursor.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-cursor.service.ts @@ -19,55 +19,52 @@ export class MessagingCursorService { ) { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); - const folderRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageFolder', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); + const folderRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageFolder', + ); - if (!folderId) { - await messageChannelRepository.update( - { - id: messageChannel.id, - }, - { - throttleFailureCount: 0, - syncStageStartedAt: null, - syncCursor: - !messageChannel.syncCursor || - nextSyncCursor > messageChannel.syncCursor - ? nextSyncCursor - : messageChannel.syncCursor, - }, - ); - } else { - await folderRepository.update( - { - id: folderId, - }, - { - syncCursor: nextSyncCursor, - }, - ); - await messageChannelRepository.update( - { - id: messageChannel.id, - }, - { - throttleFailureCount: 0, - syncStageStartedAt: null, - }, - ); - } - }, - ); + if (!folderId) { + await messageChannelRepository.update( + { + id: messageChannel.id, + }, + { + throttleFailureCount: 0, + syncStageStartedAt: null, + syncCursor: + !messageChannel.syncCursor || + nextSyncCursor > messageChannel.syncCursor + ? nextSyncCursor + : messageChannel.syncCursor, + }, + ); + } else { + await folderRepository.update( + { + id: folderId, + }, + { + syncCursor: nextSyncCursor, + }, + ); + await messageChannelRepository.update( + { + id: messageChannel.id, + }, + { + throttleFailureCount: 0, + syncStageStartedAt: null, + }, + ); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-delete-group-email-messages.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-delete-group-email-messages.service.ts index 4b7ee1dc494..00eeffc05c2 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-delete-group-email-messages.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-delete-group-email-messages.service.ts @@ -41,7 +41,6 @@ export class MessagingDeleteGroupEmailMessagesService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const messageChannelMessageAssociationRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -146,6 +145,7 @@ export class MessagingDeleteGroupEmailMessagesService { return totalDeletedCount; }, + authContext, ); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts index 48881a17343..45d61c92e9e 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts @@ -150,24 +150,21 @@ export class MessageImportExceptionHandlerService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); - - await messageChannelRepository.increment( - { id: messageChannel.id }, - 'throttleFailureCount', - 1, - undefined, - ['throttleFailureCount', 'id'], + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', ); - }, - ); + + await messageChannelRepository.increment( + { id: messageChannel.id }, + 'throttleFailureCount', + 1, + undefined, + ['throttleFailureCount', 'id'], + ); + }, authContext); switch (syncStep) { case MessageImportSyncStep.MESSAGE_LIST_FETCH: diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message-list-fetch.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message-list-fetch.service.ts index da3e50e40f9..96a71aeaf4b 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message-list-fetch.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message-list-fetch.service.ts @@ -58,254 +58,250 @@ export class MessagingMessageListFetchService { ) { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - try { - const pendingGroupEmailActionsProcessed = - await this.processPendingGroupEmailActions( - messageChannel, - workspaceId, - ); - - const pendingFolderActionsProcessed = - await this.processPendingFolderActions(messageChannel, workspaceId); - - await this.messageChannelSyncStatusService.markAsMessagesListFetchOngoing( - [messageChannel.id], + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + try { + const pendingGroupEmailActionsProcessed = + await this.processPendingGroupEmailActions( + messageChannel, workspaceId, ); - this.logger.log( - `messageChannelId: ${messageChannel.id} Processing message list fetch`, + const pendingFolderActionsProcessed = + await this.processPendingFolderActions(messageChannel, workspaceId); + + await this.messageChannelSyncStatusService.markAsMessagesListFetchOngoing( + [messageChannel.id], + workspaceId, + ); + + this.logger.log( + `messageChannelId: ${messageChannel.id} Processing message list fetch`, + ); + + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', ); - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( + const freshMessageChannel = + pendingGroupEmailActionsProcessed || pendingFolderActionsProcessed + ? await messageChannelRepository.findOne({ + where: { + id: messageChannel.id, + }, + relations: ['connectedAccount', 'messageFolders'], + }) + : messageChannel; + + if (!isDefined(freshMessageChannel)) { + this.logger.error( + `error processing message list fetch: messageChannelId: ${messageChannel.id} Message channel not found`, + ); + + return; + } + + const { accessToken, refreshToken } = + await this.messagingAccountAuthenticationService.validateAndRefreshConnectedAccountAuthentication( + { + connectedAccount: freshMessageChannel.connectedAccount, workspaceId, - 'messageChannel', - ); - - const freshMessageChannel = - pendingGroupEmailActionsProcessed || pendingFolderActionsProcessed - ? await messageChannelRepository.findOne({ - where: { - id: messageChannel.id, - }, - relations: ['connectedAccount', 'messageFolders'], - }) - : messageChannel; - - if (!isDefined(freshMessageChannel)) { - this.logger.error( - `error processing message list fetch: messageChannelId: ${messageChannel.id} Message channel not found`, - ); - - return; - } - - const { accessToken, refreshToken } = - await this.messagingAccountAuthenticationService.validateAndRefreshConnectedAccountAuthentication( - { - connectedAccount: freshMessageChannel.connectedAccount, - workspaceId, - messageChannelId: freshMessageChannel.id, - }, - ); - - const messageChannelWithFreshTokens = { - ...freshMessageChannel, - connectedAccount: { - ...freshMessageChannel.connectedAccount, - accessToken, - refreshToken, + messageChannelId: freshMessageChannel.id, }, - }; + ); - const messageFolders = - await this.syncMessageFoldersService.syncMessageFolders({ - messageChannel: messageChannelWithFreshTokens, - workspaceId, + const messageChannelWithFreshTokens = { + ...freshMessageChannel, + connectedAccount: { + ...freshMessageChannel.connectedAccount, + accessToken, + refreshToken, + }, + }; + + const messageFolders = + await this.syncMessageFoldersService.syncMessageFolders({ + messageChannel: messageChannelWithFreshTokens, + workspaceId, + }); + + const messageFoldersToSync = messageFolders.filter( + (folder) => + folder.pendingSyncAction === MessageFolderPendingSyncAction.NONE, + ); + + const messageLists = + await this.messagingGetMessageListService.getMessageLists( + messageChannelWithFreshTokens, + messageFoldersToSync, + ); + + await this.cacheStorage.del( + `messages-to-import:${workspaceId}:${freshMessageChannel.id}`, + ); + + const messageExternalIds = messageLists.flatMap( + (messageList) => messageList.messageExternalIds, + ); + + const messageExternalIdsToDelete = messageLists.flatMap( + (messageList) => messageList.messageExternalIdsToDelete, + ); + + const isFullSync = + messageLists.every( + (messageList) => !isNonEmptyString(messageList.previousSyncCursor), + ) && !isNonEmptyString(freshMessageChannel.syncCursor); + + let totalMessagesToImportCount = 0; + + this.logger.log( + `messageChannelId: ${freshMessageChannel.id} Is full sync: ${isFullSync} and toImportCount: ${messageExternalIds.length}, toDeleteCount: ${messageExternalIdsToDelete.length}, cursors: ${messageLists.map( + (messageList) => { + messageList.nextSyncCursor; + }, + )}`, + ); + + const messageChannelMessageAssociationRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannelMessageAssociation', + ); + + const messageExternalIdsChunks = chunk(messageExternalIds, 200); + + for (const [ + index, + messageExternalIdsChunk, + ] of messageExternalIdsChunks.entries()) { + const existingMessageChannelMessageAssociations = + await messageChannelMessageAssociationRepository.find({ + where: { + messageChannelId: freshMessageChannel.id, + messageExternalId: In(messageExternalIdsChunk), + }, }); - const messageFoldersToSync = messageFolders.filter( - (folder) => - folder.pendingSyncAction === MessageFolderPendingSyncAction.NONE, - ); - - const messageLists = - await this.messagingGetMessageListService.getMessageLists( - messageChannelWithFreshTokens, - messageFoldersToSync, - ); - - await this.cacheStorage.del( - `messages-to-import:${workspaceId}:${freshMessageChannel.id}`, - ); - - const messageExternalIds = messageLists.flatMap( - (messageList) => messageList.messageExternalIds, - ); - - const messageExternalIdsToDelete = messageLists.flatMap( - (messageList) => messageList.messageExternalIdsToDelete, - ); - - const isFullSync = - messageLists.every( - (messageList) => - !isNonEmptyString(messageList.previousSyncCursor), - ) && !isNonEmptyString(freshMessageChannel.syncCursor); - - let totalMessagesToImportCount = 0; - - this.logger.log( - `messageChannelId: ${freshMessageChannel.id} Is full sync: ${isFullSync} and toImportCount: ${messageExternalIds.length}, toDeleteCount: ${messageExternalIdsToDelete.length}, cursors: ${messageLists.map( - (messageList) => { - messageList.nextSyncCursor; - }, - )}`, - ); - - const messageChannelMessageAssociationRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannelMessageAssociation', - ); - - const messageExternalIdsChunks = chunk(messageExternalIds, 200); - - for (const [ - index, - messageExternalIdsChunk, - ] of messageExternalIdsChunks.entries()) { - const existingMessageChannelMessageAssociations = - await messageChannelMessageAssociationRepository.find({ - where: { - messageChannelId: freshMessageChannel.id, - messageExternalId: In(messageExternalIdsChunk), - }, - }); - - const existingMessageChannelMessageAssociationsExternalIds = - existingMessageChannelMessageAssociations.map( - (messageChannelMessageAssociation) => - messageChannelMessageAssociation.messageExternalId, - ); - - const messageExternalIdsToImport = messageExternalIdsChunk.filter( - (messageExternalId) => - !existingMessageChannelMessageAssociationsExternalIds.includes( - messageExternalId, - ), - ); - - if (messageExternalIdsToImport.length) { - this.logger.log( - `messageChannelId: ${freshMessageChannel.id} Adding ${messageExternalIdsToImport.length} message external ids to import in batch ${index + 1}`, - ); - - totalMessagesToImportCount += messageExternalIdsToImport.length; - - await this.cacheStorage.setAdd( - `messages-to-import:${workspaceId}:${messageChannelWithFreshTokens.id}`, - messageExternalIdsToImport, - ONE_WEEK_IN_MILLISECONDS, - ); - } - } - - for (const messageList of messageLists) { - const { nextSyncCursor, folderId } = messageList; - - await this.messagingCursorService.updateCursor( - messageChannelWithFreshTokens, - nextSyncCursor, - workspaceId, - folderId, - ); - } - - const fullSyncMessageChannelMessageAssociationsToDelete = isFullSync - ? await this.computeFullSyncMessageChannelMessageAssociationsToDelete( - freshMessageChannel, - messageExternalIds, - workspaceId, - ) - : []; - - const allMessageExternalIdsToDelete = [ - ...messageExternalIdsToDelete, - ...fullSyncMessageChannelMessageAssociationsToDelete.map( + const existingMessageChannelMessageAssociationsExternalIds = + existingMessageChannelMessageAssociations.map( (messageChannelMessageAssociation) => messageChannelMessageAssociation.messageExternalId, - ), - ]; + ); - if (allMessageExternalIdsToDelete.length) { + const messageExternalIdsToImport = messageExternalIdsChunk.filter( + (messageExternalId) => + !existingMessageChannelMessageAssociationsExternalIds.includes( + messageExternalId, + ), + ); + + if (messageExternalIdsToImport.length) { this.logger.log( - `messageChannelId: ${freshMessageChannel.id} Deleting ${allMessageExternalIdsToDelete.length} message channel message associations`, + `messageChannelId: ${freshMessageChannel.id} Adding ${messageExternalIdsToImport.length} message external ids to import in batch ${index + 1}`, ); - const toDeleteChunks = chunk(allMessageExternalIdsToDelete, 200); + totalMessagesToImportCount += messageExternalIdsToImport.length; - for (const [index, toDeleteChunk] of toDeleteChunks.entries()) { - this.logger.log( - `messageChannelId: ${freshMessageChannel.id} Deleting ${toDeleteChunk.length} message channel message associations in batch ${index + 1}`, - ); - - await this.messagingMessageCleanerService.deleteMessagesChannelMessageAssociationsAndRelatedOrphans( - { - workspaceId, - messageExternalIds: toDeleteChunk.filter( - (messageExternalId) => isNonEmptyString(messageExternalId), - ), - messageChannelId: messageChannelWithFreshTokens.id, - }, - ); - } + await this.cacheStorage.setAdd( + `messages-to-import:${workspaceId}:${messageChannelWithFreshTokens.id}`, + messageExternalIdsToImport, + ONE_WEEK_IN_MILLISECONDS, + ); } + } - this.logger.log( - `messageChannelId: ${freshMessageChannel.id} Total messages to import count: ${totalMessagesToImportCount}`, + for (const messageList of messageLists) { + const { nextSyncCursor, folderId } = messageList; + + await this.messagingCursorService.updateCursor( + messageChannelWithFreshTokens, + nextSyncCursor, + workspaceId, + folderId, ); + } - if (totalMessagesToImportCount === 0) { - await this.messageChannelSyncStatusService.markAsCompletedAndMarkAsMessagesListFetchPending( - [messageChannelWithFreshTokens.id], + const fullSyncMessageChannelMessageAssociationsToDelete = isFullSync + ? await this.computeFullSyncMessageChannelMessageAssociationsToDelete( + freshMessageChannel, + messageExternalIds, workspaceId, - ); + ) + : []; - return; - } + const allMessageExternalIdsToDelete = [ + ...messageExternalIdsToDelete, + ...fullSyncMessageChannelMessageAssociationsToDelete.map( + (messageChannelMessageAssociation) => + messageChannelMessageAssociation.messageExternalId, + ), + ]; + if (allMessageExternalIdsToDelete.length) { this.logger.log( - `messageChannelId: ${freshMessageChannel.id} Scheduling direct messages import`, + `messageChannelId: ${freshMessageChannel.id} Deleting ${allMessageExternalIdsToDelete.length} message channel message associations`, ); - await this.messageChannelSyncStatusService.markAsMessagesImportScheduled( + const toDeleteChunks = chunk(allMessageExternalIdsToDelete, 200); + + for (const [index, toDeleteChunk] of toDeleteChunks.entries()) { + this.logger.log( + `messageChannelId: ${freshMessageChannel.id} Deleting ${toDeleteChunk.length} message channel message associations in batch ${index + 1}`, + ); + + await this.messagingMessageCleanerService.deleteMessagesChannelMessageAssociationsAndRelatedOrphans( + { + workspaceId, + messageExternalIds: toDeleteChunk.filter((messageExternalId) => + isNonEmptyString(messageExternalId), + ), + messageChannelId: messageChannelWithFreshTokens.id, + }, + ); + } + } + + this.logger.log( + `messageChannelId: ${freshMessageChannel.id} Total messages to import count: ${totalMessagesToImportCount}`, + ); + + if (totalMessagesToImportCount === 0) { + await this.messageChannelSyncStatusService.markAsCompletedAndMarkAsMessagesListFetchPending( [messageChannelWithFreshTokens.id], workspaceId, ); - await this.messagingMessagesImportService.processMessageBatchImport( - { - ...messageChannelWithFreshTokens, - syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED, - }, - messageChannelWithFreshTokens.connectedAccount, - workspaceId, - ); - } catch (error) { - await this.messageImportErrorHandlerService.handleDriverException( - error, - MessageImportSyncStep.MESSAGE_LIST_FETCH, - messageChannel, - workspaceId, - ); + return; } - }, - ); + + this.logger.log( + `messageChannelId: ${freshMessageChannel.id} Scheduling direct messages import`, + ); + + await this.messageChannelSyncStatusService.markAsMessagesImportScheduled( + [messageChannelWithFreshTokens.id], + workspaceId, + ); + + await this.messagingMessagesImportService.processMessageBatchImport( + { + ...messageChannelWithFreshTokens, + syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED, + }, + messageChannelWithFreshTokens.connectedAccount, + workspaceId, + ); + } catch (error) { + await this.messageImportErrorHandlerService.handleDriverException( + error, + MessageImportSyncStep.MESSAGE_LIST_FETCH, + messageChannel, + workspaceId, + ); + } + }, authContext); } private async processPendingGroupEmailActions( diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message.service.ts index c2c5cf0880a..7a5c07ebe11 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message.service.ts @@ -55,7 +55,6 @@ export class MessagingMessageService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const messageChannelMessageAssociationRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -245,6 +244,7 @@ export class MessagingMessageService { messageExternalIdsAndIdsMap, }; }, + authContext, ); } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts index feecb7b1741..4a9958cee79 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts @@ -60,169 +60,164 @@ export class MessagingMessagesImportService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - try { - if ( - messageChannel.syncStage !== - MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED - ) { - return; - } + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + try { + if ( + messageChannel.syncStage !== + MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED + ) { + return; + } - await this.messagingMonitoringService.track({ - eventName: 'messages_import.started', - workspaceId, - connectedAccountId: messageChannel.connectedAccountId, - messageChannelId: messageChannel.id, - }); + await this.messagingMonitoringService.track({ + eventName: 'messages_import.started', + workspaceId, + connectedAccountId: messageChannel.connectedAccountId, + messageChannelId: messageChannel.id, + }); - await this.messageChannelSyncStatusService.markAsMessagesImportOngoing( + await this.messageChannelSyncStatusService.markAsMessagesImportOngoing( + [messageChannel.id], + workspaceId, + ); + + const { accessToken, refreshToken } = + await this.messagingAccountAuthenticationService.validateAndRefreshConnectedAccountAuthentication( + { + connectedAccount, + workspaceId, + messageChannelId: messageChannel.id, + }, + ); + + const connectedAccountWithFreshTokens = { + ...connectedAccount, + accessToken, + refreshToken, + }; + + await this.emailAliasManagerService.refreshHandleAliases( + connectedAccountWithFreshTokens, + workspaceId, + ); + + messageIdsToFetch = await this.cacheStorage.setPop( + `messages-to-import:${workspaceId}:${messageChannel.id}`, + MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE, + ); + + if (!messageIdsToFetch?.length) { + await this.messageChannelSyncStatusService.markAsCompletedAndMarkAsMessagesListFetchPending( [messageChannel.id], workspaceId, ); - const { accessToken, refreshToken } = - await this.messagingAccountAuthenticationService.validateAndRefreshConnectedAccountAuthentication( - { - connectedAccount, - workspaceId, - messageChannelId: messageChannel.id, - }, - ); - - const connectedAccountWithFreshTokens = { - ...connectedAccount, - accessToken, - refreshToken, - }; - - await this.emailAliasManagerService.refreshHandleAliases( - connectedAccountWithFreshTokens, - workspaceId, - ); - - messageIdsToFetch = await this.cacheStorage.setPop( - `messages-to-import:${workspaceId}:${messageChannel.id}`, - MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE, - ); - - if (!messageIdsToFetch?.length) { - await this.messageChannelSyncStatusService.markAsCompletedAndMarkAsMessagesListFetchPending( - [messageChannel.id], - workspaceId, - ); - - return await this.trackMessageImportCompleted( - messageChannel, - workspaceId, - ); - } - - const allMessages = - await this.messagingGetMessagesService.getMessages( - messageIdsToFetch, - connectedAccountWithFreshTokens, - ); - - const blocklist = - await this.blocklistRepository.getByWorkspaceMemberId( - connectedAccountWithFreshTokens.accountOwnerId, - workspaceId, - ); - - if (!isDefined(messageChannel.handle)) { - throw new MessageImportDriverException( - 'Message channel handle is required', - MessageImportDriverExceptionCode.CHANNEL_MISCONFIGURED, - ); - } - - if (!isDefined(connectedAccountWithFreshTokens.handleAliases)) { - throw new MessageImportDriverException( - 'Message channel handle is required', - MessageImportDriverExceptionCode.CHANNEL_MISCONFIGURED, - ); - } - - const messagesToSave = filterEmails( - messageChannel.handle, - [...connectedAccountWithFreshTokens.handleAliases.split(',')], - allMessages, - blocklist - .map((blocklistItem) => blocklistItem.handle) - .filter(isDefined), - messageChannel.excludeGroupEmails, - ); - - if (messagesToSave.length > 0) { - await this.saveMessagesAndEnqueueContactCreationService.saveMessagesAndEnqueueContactCreation( - messagesToSave, - messageChannel, - connectedAccountWithFreshTokens, - workspaceId, - ); - } - - if ( - messageIdsToFetch.length < - MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE - ) { - await this.messageChannelSyncStatusService.markAsCompletedAndMarkAsMessagesListFetchPending( - [messageChannel.id], - workspaceId, - ); - } else { - await this.messageChannelSyncStatusService.markAsMessagesImportPending( - [messageChannel.id], - workspaceId, - ); - } - - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); - - await messageChannelRepository.update( - { - id: messageChannel.id, - }, - { - throttleFailureCount: 0, - syncStageStartedAt: null, - }, - ); - - return await this.trackMessageImportCompleted( - messageChannel, - workspaceId, - ); - } catch (error) { - this.logger.error( - `Error (${error.code}) importing messages for workspace ${workspaceId.slice(0, 8)} and account ${connectedAccount.id.slice(0, 8)}: ${error.message} - ${error.body}`, - ); - await this.cacheStorage.setAdd( - `messages-to-import:${workspaceId}:${messageChannel.id}`, - messageIdsToFetch, - ); - - await this.messageImportErrorHandlerService.handleDriverException( - error, - MessageImportSyncStep.MESSAGES_IMPORT_ONGOING, - messageChannel, - workspaceId, - ); - return await this.trackMessageImportCompleted( messageChannel, workspaceId, ); } - }, - ); + + const allMessages = await this.messagingGetMessagesService.getMessages( + messageIdsToFetch, + connectedAccountWithFreshTokens, + ); + + const blocklist = await this.blocklistRepository.getByWorkspaceMemberId( + connectedAccountWithFreshTokens.accountOwnerId, + workspaceId, + ); + + if (!isDefined(messageChannel.handle)) { + throw new MessageImportDriverException( + 'Message channel handle is required', + MessageImportDriverExceptionCode.CHANNEL_MISCONFIGURED, + ); + } + + if (!isDefined(connectedAccountWithFreshTokens.handleAliases)) { + throw new MessageImportDriverException( + 'Message channel handle is required', + MessageImportDriverExceptionCode.CHANNEL_MISCONFIGURED, + ); + } + + const messagesToSave = filterEmails( + messageChannel.handle, + [...connectedAccountWithFreshTokens.handleAliases.split(',')], + allMessages, + blocklist + .map((blocklistItem) => blocklistItem.handle) + .filter(isDefined), + messageChannel.excludeGroupEmails, + ); + + if (messagesToSave.length > 0) { + await this.saveMessagesAndEnqueueContactCreationService.saveMessagesAndEnqueueContactCreation( + messagesToSave, + messageChannel, + connectedAccountWithFreshTokens, + workspaceId, + ); + } + + if ( + messageIdsToFetch.length < + MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE + ) { + await this.messageChannelSyncStatusService.markAsCompletedAndMarkAsMessagesListFetchPending( + [messageChannel.id], + workspaceId, + ); + } else { + await this.messageChannelSyncStatusService.markAsMessagesImportPending( + [messageChannel.id], + workspaceId, + ); + } + + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); + + await messageChannelRepository.update( + { + id: messageChannel.id, + }, + { + throttleFailureCount: 0, + syncStageStartedAt: null, + }, + ); + + return await this.trackMessageImportCompleted( + messageChannel, + workspaceId, + ); + } catch (error) { + this.logger.error( + `Error (${error.code}) importing messages for workspace ${workspaceId.slice(0, 8)} and account ${connectedAccount.id.slice(0, 8)}: ${error.message} - ${error.body}`, + ); + await this.cacheStorage.setAdd( + `messages-to-import:${workspaceId}:${messageChannel.id}`, + messageIdsToFetch, + ); + + await this.messageImportErrorHandlerService.handleDriverException( + error, + MessageImportSyncStep.MESSAGES_IMPORT_ONGOING, + messageChannel, + workspaceId, + ); + + return await this.trackMessageImportCompleted( + messageChannel, + workspaceId, + ); + } + }, authContext); } private async trackMessageImportCompleted( diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-folder-actions.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-folder-actions.service.ts index 092cefb7186..5042509e724 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-folder-actions.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-folder-actions.service.ts @@ -90,7 +90,6 @@ export class MessagingProcessFolderActionsService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceDataSource = await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource(); @@ -128,6 +127,7 @@ export class MessagingProcessFolderActionsService { }, ); }, + authContext, ); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service.ts index db8cf2bf64f..6db0f46f208 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service.ts @@ -30,25 +30,22 @@ export class MessagingProcessGroupEmailActionsService { ): Promise { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); - - await messageChannelRepository.update( - { id: messageChannel.id }, - { pendingGroupEmailsAction }, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', ); - this.logger.log( - `WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Marked message channel as pending group emails action: ${pendingGroupEmailsAction}`, - ); - }, - ); + await messageChannelRepository.update( + { id: messageChannel.id }, + { pendingGroupEmailsAction }, + ); + + this.logger.log( + `WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Marked message channel as pending group emails action: ${pendingGroupEmailsAction}`, + ); + }, authContext); } async processGroupEmailActions( @@ -70,61 +67,58 @@ export class MessagingProcessGroupEmailActionsService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workspaceDataSource = - await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource(); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workspaceDataSource = + await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource(); - await workspaceDataSource?.transaction( - async (transactionManager: WorkspaceEntityManager) => { - try { - const messageChannelRepository = - await this.globalWorkspaceOrmManager.getRepository( + await workspaceDataSource?.transaction( + async (transactionManager: WorkspaceEntityManager) => { + try { + const messageChannelRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageChannel', + ); + + switch (pendingGroupEmailsAction) { + case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_DELETION: + await this.handleGroupEmailsDeletion( workspaceId, - 'messageChannel', + messageChannel.id, + transactionManager, ); - - switch (pendingGroupEmailsAction) { - case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_DELETION: - await this.handleGroupEmailsDeletion( - workspaceId, - messageChannel.id, - transactionManager, - ); - break; - case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_IMPORT: - await this.handleGroupEmailsImport( - workspaceId, - messageChannel.id, - transactionManager, - ); - break; - } - - await messageChannelRepository.update( - { id: messageChannel.id }, - { - pendingGroupEmailsAction: - MessageChannelPendingGroupEmailsAction.NONE, - }, - transactionManager, - ); - - this.logger.log( - `WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Reset pendingGroupEmailsAction to NONE`, - ); - } catch (error) { - this.logger.error( - `WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Error processing group email action: ${error.message}`, - error.stack, - ); - throw error; + break; + case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_IMPORT: + await this.handleGroupEmailsImport( + workspaceId, + messageChannel.id, + transactionManager, + ); + break; } - }, - ); - }, - ); + + await messageChannelRepository.update( + { id: messageChannel.id }, + { + pendingGroupEmailsAction: + MessageChannelPendingGroupEmailsAction.NONE, + }, + transactionManager, + ); + + this.logger.log( + `WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Reset pendingGroupEmailsAction to NONE`, + ); + } catch (error) { + this.logger.error( + `WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Error processing group email action: ${error.message}`, + error.stack, + ); + throw error; + } + }, + ); + }, authContext); } private async handleGroupEmailsDeletion( diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts index 400b46f47d8..c40762f5723 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts @@ -159,7 +159,7 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => { .mockResolvedValue(datasourceInstance), executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }, }, ], diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts index 0927f2cba8a..505e7f82fbb 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts @@ -47,7 +47,6 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService { const participantsWithMessageId = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workspaceDataSource = await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource(); @@ -117,6 +116,7 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService { }, ); }, + authContext, ); if ( diff --git a/packages/twenty-server/src/modules/messaging/message-participant-manager/services/messaging-message-participant.service.ts b/packages/twenty-server/src/modules/messaging/message-participant-manager/services/messaging-message-participant.service.ts index fa7a9635faf..040faa7961b 100644 --- a/packages/twenty-server/src/modules/messaging/message-participant-manager/services/messaging-message-participant.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-participant-manager/services/messaging-message-participant.service.ts @@ -23,60 +23,57 @@ export class MessagingMessageParticipantService { ): Promise { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const messageParticipantRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageParticipant', - ); - - const existingParticipantsBasedOnMessageIds = - await messageParticipantRepository.find({ - where: { - messageId: In( - participants.map((participant) => participant.messageId), - ), - }, - }); - - const participantsToCreate: Pick< - MessageParticipantWorkspaceEntity, - 'messageId' | 'handle' | 'displayName' | 'role' - >[] = participants - .filter( - (participant) => - !existingParticipantsBasedOnMessageIds.find( - (existingParticipant) => - existingParticipant.messageId === participant.messageId && - existingParticipant.handle === participant.handle && - existingParticipant.displayName === participant.displayName && - existingParticipant.role === participant.role, - ), - ) - .map((participant) => { - return { - messageId: participant.messageId, - handle: participant.handle, - displayName: participant.displayName, - role: participant.role, - }; - }); - - const createdParticipants = await messageParticipantRepository.insert( - participantsToCreate, - transactionManager, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const messageParticipantRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'messageParticipant', ); - await this.matchParticipantService.matchParticipants({ - participants: createdParticipants.raw ?? [], - objectMetadataName: 'messageParticipant', - transactionManager, - matchWith: 'workspaceMemberAndPerson', - workspaceId, + const existingParticipantsBasedOnMessageIds = + await messageParticipantRepository.find({ + where: { + messageId: In( + participants.map((participant) => participant.messageId), + ), + }, }); - }, - ); + + const participantsToCreate: Pick< + MessageParticipantWorkspaceEntity, + 'messageId' | 'handle' | 'displayName' | 'role' + >[] = participants + .filter( + (participant) => + !existingParticipantsBasedOnMessageIds.find( + (existingParticipant) => + existingParticipant.messageId === participant.messageId && + existingParticipant.handle === participant.handle && + existingParticipant.displayName === participant.displayName && + existingParticipant.role === participant.role, + ), + ) + .map((participant) => { + return { + messageId: participant.messageId, + handle: participant.handle, + displayName: participant.displayName, + role: participant.role, + }; + }); + + const createdParticipants = await messageParticipantRepository.insert( + participantsToCreate, + transactionManager, + ); + + await this.matchParticipantService.matchParticipants({ + participants: createdParticipants.raw ?? [], + objectMetadataName: 'messageParticipant', + transactionManager, + matchWith: 'workspaceMemberAndPerson', + workspaceId, + }); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job.ts b/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job.ts index 553f66d6f94..aeed0f0cfc4 100644 --- a/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job.ts +++ b/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job.ts @@ -50,7 +50,6 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob { const authContext = buildSystemAuthContext(activeWorkspace.id); await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const messageChannelRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -76,6 +75,7 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob { }); } }, + authContext, ); } catch (error) { this.exceptionHandlerService.captureExceptions([error], { diff --git a/packages/twenty-server/src/modules/note/query-hooks/note-post-query-hook.service.ts b/packages/twenty-server/src/modules/note/query-hooks/note-post-query-hook.service.ts index 3f10408ba9a..a416acbc4ee 100644 --- a/packages/twenty-server/src/modules/note/query-hooks/note-post-query-hook.service.ts +++ b/packages/twenty-server/src/modules/note/query-hooks/note-post-query-hook.service.ts @@ -29,20 +29,17 @@ export class NotePostQueryHookService { assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext as WorkspaceAuthContext, - async () => { - const noteTargetRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspace.id, - 'noteTarget', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const noteTargetRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspace.id, + 'noteTarget', + ); - await noteTargetRepository.softDelete({ - noteId: In(payload.map((note) => note.id)), - }); - }, - ); + await noteTargetRepository.softDelete({ + noteId: In(payload.map((note) => note.id)), + }); + }, authContext as WorkspaceAuthContext); } async handleNoteTargetsRestore( @@ -57,19 +54,16 @@ export class NotePostQueryHookService { assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext as WorkspaceAuthContext, - async () => { - const noteTargetRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspace.id, - 'noteTarget', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const noteTargetRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspace.id, + 'noteTarget', + ); - await noteTargetRepository.restore({ - noteId: In(payload.map((note) => note.id)), - }); - }, - ); + await noteTargetRepository.restore({ + noteId: In(payload.map((note) => note.id)), + }); + }, authContext as WorkspaceAuthContext); } } diff --git a/packages/twenty-server/src/modules/task/query-hooks/task-post-query-hook.service.ts b/packages/twenty-server/src/modules/task/query-hooks/task-post-query-hook.service.ts index fc312b86c45..a5ed6abcc0b 100644 --- a/packages/twenty-server/src/modules/task/query-hooks/task-post-query-hook.service.ts +++ b/packages/twenty-server/src/modules/task/query-hooks/task-post-query-hook.service.ts @@ -29,20 +29,17 @@ export class TaskPostQueryHookService { assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext as WorkspaceAuthContext, - async () => { - const taskTargetRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspace.id, - 'taskTarget', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const taskTargetRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspace.id, + 'taskTarget', + ); - await taskTargetRepository.softDelete({ - taskId: In(payload.map((task) => task.id)), - }); - }, - ); + await taskTargetRepository.softDelete({ + taskId: In(payload.map((task) => task.id)), + }); + }, authContext as WorkspaceAuthContext); } async handleTaskTargetsRestore( @@ -57,19 +54,16 @@ export class TaskPostQueryHookService { assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext as WorkspaceAuthContext, - async () => { - const taskTargetRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspace.id, - 'taskTarget', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const taskTargetRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspace.id, + 'taskTarget', + ); - await taskTargetRepository.restore({ - taskId: In(payload.map((task) => task.id)), - }); - }, - ); + await taskTargetRepository.restore({ + taskId: In(payload.map((task) => task.id)), + }); + }, authContext as WorkspaceAuthContext); } } diff --git a/packages/twenty-server/src/modules/timeline/jobs/upsert-timeline-activity-from-internal-event.job.ts b/packages/twenty-server/src/modules/timeline/jobs/upsert-timeline-activity-from-internal-event.job.ts index 4e46f7643b8..fdaf722e90b 100644 --- a/packages/twenty-server/src/modules/timeline/jobs/upsert-timeline-activity-from-internal-event.job.ts +++ b/packages/twenty-server/src/modules/timeline/jobs/upsert-timeline-activity-from-internal-event.job.ts @@ -38,38 +38,35 @@ export class UpsertTimelineActivityFromInternalEvent { const authContext = buildSystemAuthContext(workspaceEventBatch.workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workspaceMemberRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceEventBatch.workspaceId, - WorkspaceMemberWorkspaceEntity, - { - shouldBypassPermissionChecks: true, - }, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workspaceMemberRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceEventBatch.workspaceId, + WorkspaceMemberWorkspaceEntity, + { + shouldBypassPermissionChecks: true, + }, + ); - const userIds = workspaceEventBatch.events - .map((event) => event.userId) - .filter(isDefined); + const userIds = workspaceEventBatch.events + .map((event) => event.userId) + .filter(isDefined); - const workspaceMembers = await workspaceMemberRepository.findBy({ - userId: In(userIds), - }); + const workspaceMembers = await workspaceMemberRepository.findBy({ + userId: In(userIds), + }); - for (const eventData of workspaceEventBatch.events) { - const workspaceMember = workspaceMembers.find( - (workspaceMember) => workspaceMember.userId === eventData.userId, - ); + for (const eventData of workspaceEventBatch.events) { + const workspaceMember = workspaceMembers.find( + (workspaceMember) => workspaceMember.userId === eventData.userId, + ); - if (eventData.userId && workspaceMember) { - eventData.workspaceMemberId = workspaceMember.id; - } + if (eventData.userId && workspaceMember) { + eventData.workspaceMemberId = workspaceMember.id; } + } - await this.timelineActivityService.upsertEvents(workspaceEventBatch); - }, - ); + await this.timelineActivityService.upsertEvents(workspaceEventBatch); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/timeline/repositories/timeline-activity.repository.ts b/packages/twenty-server/src/modules/timeline/repositories/timeline-activity.repository.ts index eafaad9cb75..c9460ac2f57 100644 --- a/packages/twenty-server/src/modules/timeline/repositories/timeline-activity.repository.ts +++ b/packages/twenty-server/src/modules/timeline/repositories/timeline-activity.repository.ts @@ -33,79 +33,74 @@ export class TimelineActivityRepository { }: TimelineActivityPayloadWorkspaceIdAndObjectSingularName) { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const recentTimelineActivities = - await this.findRecentTimelineActivities({ - objectSingularName, - workspaceId, - payloads, - isFeatureFlagTimelineActivityMigrated, - }); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const recentTimelineActivities = await this.findRecentTimelineActivities({ + objectSingularName, + workspaceId, + payloads, + isFeatureFlagTimelineActivityMigrated, + }); - const payloadsWithDiff = payloads - .filter(({ properties }) => { - const isDiffEmpty = - properties.diff !== null && - properties.diff && - Object.keys(properties.diff).length === 0; + const payloadsWithDiff = payloads + .filter(({ properties }) => { + const isDiffEmpty = + properties.diff !== null && + properties.diff && + Object.keys(properties.diff).length === 0; - return !isDiffEmpty; - }) - .map(({ properties, ...rest }) => ({ - ...rest, - properties: isDefined(properties.diff) - ? { diff: properties.diff } - : {}, - })); + return !isDiffEmpty; + }) + .map(({ properties, ...rest }) => ({ + ...rest, + properties: isDefined(properties.diff) + ? { diff: properties.diff } + : {}, + })); - const payloadsToInsert: TimelineActivityPayloadWorkspaceIdAndObjectSingularName['payloads'] = - []; + const payloadsToInsert: TimelineActivityPayloadWorkspaceIdAndObjectSingularName['payloads'] = + []; - const timelineActivityPropertyName = - await this.getTimelineActivityPropertyName( - objectSingularName, - isFeatureFlagTimelineActivityMigrated, - ); - - for (const payload of payloadsWithDiff) { - const recentTimelineActivity = recentTimelineActivities.find( - (timelineActivity) => - timelineActivity[timelineActivityPropertyName] === - payload.recordId && - timelineActivity.workspaceMemberId === - payload.workspaceMemberId && - (!isDefined(payload.linkedRecordId) || - timelineActivity.linkedRecordId === payload.linkedRecordId) && - timelineActivity.name === payload.name, - ); - - if (recentTimelineActivity) { - const mergedProperties = objectRecordDiffMerge( - recentTimelineActivity.properties, - payload.properties, - ); - - await this.updateTimelineActivity({ - id: recentTimelineActivity.id, - properties: mergedProperties, - workspaceMemberId: payload.workspaceMemberId, - workspaceId, - }); - } else { - payloadsToInsert.push(payload); - } - } - - await this.insertTimelineActivities({ + const timelineActivityPropertyName = + await this.getTimelineActivityPropertyName( objectSingularName, - payloads: payloadsToInsert, - workspaceId, isFeatureFlagTimelineActivityMigrated, - }); - }, - ); + ); + + for (const payload of payloadsWithDiff) { + const recentTimelineActivity = recentTimelineActivities.find( + (timelineActivity) => + timelineActivity[timelineActivityPropertyName] === + payload.recordId && + timelineActivity.workspaceMemberId === payload.workspaceMemberId && + (!isDefined(payload.linkedRecordId) || + timelineActivity.linkedRecordId === payload.linkedRecordId) && + timelineActivity.name === payload.name, + ); + + if (recentTimelineActivity) { + const mergedProperties = objectRecordDiffMerge( + recentTimelineActivity.properties, + payload.properties, + ); + + await this.updateTimelineActivity({ + id: recentTimelineActivity.id, + properties: mergedProperties, + workspaceMemberId: payload.workspaceMemberId, + workspaceId, + }); + } else { + payloadsToInsert.push(payload); + } + } + + await this.insertTimelineActivities({ + objectSingularName, + payloads: payloadsToInsert, + workspaceId, + isFeatureFlagTimelineActivityMigrated, + }); + }, authContext); } private async findRecentTimelineActivities({ diff --git a/packages/twenty-server/src/modules/timeline/services/timeline-activity.service.ts b/packages/twenty-server/src/modules/timeline/services/timeline-activity.service.ts index 3dad7e9b87c..6325d14ba38 100644 --- a/packages/twenty-server/src/modules/timeline/services/timeline-activity.service.ts +++ b/packages/twenty-server/src/modules/timeline/services/timeline-activity.service.ts @@ -192,7 +192,6 @@ export class TimelineActivityService { const activityTargets = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const activityTargetRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -209,6 +208,7 @@ export class TimelineActivityService { }, }); }, + authContext, ); if (activityTargets.length === 0) { @@ -277,7 +277,6 @@ export class TimelineActivityService { const activities = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const activityRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -303,6 +302,7 @@ export class TimelineActivityService { }, }); }, + authContext, ); if (activities.length === 0) { diff --git a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts index 84b2f501790..a862972630e 100644 --- a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts +++ b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts @@ -36,37 +36,34 @@ export class WorkflowCreateManyPostQueryHook assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext as WorkspaceAuthContext, - async () => { - const workflowVersionRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspace.id, - 'workflowVersion', - ); - - const position = await this.recordPositionService.buildRecordPosition({ - value: 'first', - objectMetadata: { - isCustom: false, - nameSingular: 'workflowVersion', - }, - workspaceId: workspace.id, - }); - - const workflowVersionsToCreate = payload.map((workflow) => ({ - workflowId: workflow.id, - status: WorkflowVersionStatus.DRAFT, - name: 'v1', - position, - })); - - await Promise.all( - workflowVersionsToCreate.map((workflowVersion) => { - return workflowVersionRepository.insert(workflowVersion); - }), + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowVersionRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspace.id, + 'workflowVersion', ); - }, - ); + + const position = await this.recordPositionService.buildRecordPosition({ + value: 'first', + objectMetadata: { + isCustom: false, + nameSingular: 'workflowVersion', + }, + workspaceId: workspace.id, + }); + + const workflowVersionsToCreate = payload.map((workflow) => ({ + workflowId: workflow.id, + status: WorkflowVersionStatus.DRAFT, + name: 'v1', + position, + })); + + await Promise.all( + workflowVersionsToCreate.map((workflowVersion) => { + return workflowVersionRepository.insert(workflowVersion); + }), + ); + }, authContext as WorkspaceAuthContext); } } diff --git a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts index 566d2bf7232..9843f602e68 100644 --- a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts +++ b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts @@ -38,31 +38,28 @@ export class WorkflowCreateOnePostQueryHook const workflow = payload[0]; - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext as WorkspaceAuthContext, - async () => { - const workflowVersionRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspace.id, - 'workflowVersion', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowVersionRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspace.id, + 'workflowVersion', + ); - const position = await this.recordPositionService.buildRecordPosition({ - value: 'first', - objectMetadata: { - isCustom: false, - nameSingular: 'workflowVersion', - }, - workspaceId: workspace.id, - }); + const position = await this.recordPositionService.buildRecordPosition({ + value: 'first', + objectMetadata: { + isCustom: false, + nameSingular: 'workflowVersion', + }, + workspaceId: workspace.id, + }); - await workflowVersionRepository.insert({ - workflowId: workflow.id, - status: WorkflowVersionStatus.DRAFT, - name: 'v1', - position, - }); - }, - ); + await workflowVersionRepository.insert({ + workflowId: workflow.id, + status: WorkflowVersionStatus.DRAFT, + name: 'v1', + position, + }); + }, authContext as WorkspaceAuthContext); } } diff --git a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts index 82fa308abd2..e3273a6b5ae 100644 --- a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-common.workspace-service.ts @@ -62,7 +62,6 @@ export class WorkflowCommonWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowVersionRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -79,6 +78,7 @@ export class WorkflowCommonWorkspaceService { return this.getValidWorkflowVersionOrFail(workflowVersion); }, + authContext, ); } @@ -165,78 +165,75 @@ export class WorkflowCommonWorkspaceService { }): Promise { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workflowVersionRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workflowVersion', - { shouldBypassPermissionChecks: true }, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowVersionRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workflowVersion', + { shouldBypassPermissionChecks: true }, + ); - const workflowRunRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workflowRun', - { shouldBypassPermissionChecks: true }, - ); + const workflowRunRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workflowRun', + { shouldBypassPermissionChecks: true }, + ); - const workflowAutomatedTriggerRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workflowAutomatedTrigger', - { shouldBypassPermissionChecks: true }, - ); + const workflowAutomatedTriggerRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workflowAutomatedTrigger', + { shouldBypassPermissionChecks: true }, + ); - for (const workflowId of workflowIds) { - switch (operation) { - case 'delete': - await workflowAutomatedTriggerRepository.softDelete({ - workflowId, - }); + for (const workflowId of workflowIds) { + switch (operation) { + case 'delete': + await workflowAutomatedTriggerRepository.softDelete({ + workflowId, + }); - await workflowRunRepository.softDelete({ - workflowId, - }); + await workflowRunRepository.softDelete({ + workflowId, + }); - await workflowVersionRepository.softDelete({ - workflowId, - }); + await workflowVersionRepository.softDelete({ + workflowId, + }); - break; - case 'restore': - await workflowAutomatedTriggerRepository.restore({ - workflowId, - }); + break; + case 'restore': + await workflowAutomatedTriggerRepository.restore({ + workflowId, + }); - await workflowRunRepository.restore({ - workflowId, - }); + await workflowRunRepository.restore({ + workflowId, + }); - await workflowVersionRepository.restore({ - workflowId, - }); + await workflowVersionRepository.restore({ + workflowId, + }); - break; - } - - await this.deactivateVersionOnDelete({ - workflowVersionRepository, - workflowId, - workspaceId, - operation, - }); - - await this.handleServerlessFunctionSubEntities({ - workflowVersionRepository, - workflowId, - workspaceId, - operation, - }); + break; } - }, - ); + + await this.deactivateVersionOnDelete({ + workflowVersionRepository, + workflowId, + workspaceId, + operation, + }); + + await this.handleServerlessFunctionSubEntities({ + workflowVersionRepository, + workflowId, + workspaceId, + operation, + }); + } + }, authContext); } private async deactivateVersionOnDelete({ diff --git a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts index cea81f7800d..130715207b3 100644 --- a/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/common/workspace-services/workflow-version-validation.workspace-service.ts @@ -48,36 +48,33 @@ export class WorkflowVersionValidationWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workflowVersionRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workflowVersion', - { shouldBypassPermissionChecks: true }, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowVersionRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workflowVersion', + { shouldBypassPermissionChecks: true }, + ); - const workflowAlreadyHasDraftVersion = - await workflowVersionRepository.exists({ - where: { - workflowId: payload.data.workflowId, - status: WorkflowVersionStatus.DRAFT, - deletedAt: IsNull(), - }, - }); + const workflowAlreadyHasDraftVersion = + await workflowVersionRepository.exists({ + where: { + workflowId: payload.data.workflowId, + status: WorkflowVersionStatus.DRAFT, + deletedAt: IsNull(), + }, + }); - if (workflowAlreadyHasDraftVersion) { - throw new WorkflowQueryValidationException( - 'Cannot create multiple draft versions for the same workflow', - WorkflowQueryValidationExceptionCode.FORBIDDEN, - { - userFriendlyMessage: msg`Cannot create multiple draft versions for the same workflow`, - }, - ); - } - }, - ); + if (workflowAlreadyHasDraftVersion) { + throw new WorkflowQueryValidationException( + 'Cannot create multiple draft versions for the same workflow', + WorkflowQueryValidationExceptionCode.FORBIDDEN, + { + userFriendlyMessage: msg`Cannot create multiple draft versions for the same workflow`, + }, + ); + } + }, authContext); } async validateWorkflowVersionForUpdateOne({ @@ -130,35 +127,33 @@ export class WorkflowVersionValidationWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workflowVersionRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workflowVersion', - { shouldBypassPermissionChecks: true }, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowVersionRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workflowVersion', + { shouldBypassPermissionChecks: true }, + ); - const otherWorkflowVersionsExist = - await workflowVersionRepository.exists({ - where: { - workflowId: workflowVersion.workflowId, - deletedAt: IsNull(), - id: Not(workflowVersion.id), - }, - }); + const otherWorkflowVersionsExist = await workflowVersionRepository.exists( + { + where: { + workflowId: workflowVersion.workflowId, + deletedAt: IsNull(), + id: Not(workflowVersion.id), + }, + }, + ); - if (!otherWorkflowVersionsExist) { - throw new WorkflowQueryValidationException( - 'The initial version of a workflow can not be deleted', - WorkflowQueryValidationExceptionCode.FORBIDDEN, - { - userFriendlyMessage: msg`The initial version of a workflow can not be deleted`, - }, - ); - } - }, - ); + if (!otherWorkflowVersionsExist) { + throw new WorkflowQueryValidationException( + 'The initial version of a workflow can not be deleted', + WorkflowQueryValidationExceptionCode.FORBIDDEN, + { + userFriendlyMessage: msg`The initial version of a workflow can not be deleted`, + }, + ); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/__tests__/workflow-version-edge.workspace-service.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/__tests__/workflow-version-edge.workspace-service.spec.ts index 267ea7f9f9c..0a1effb5570 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/__tests__/workflow-version-edge.workspace-service.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/__tests__/workflow-version-edge.workspace-service.spec.ts @@ -91,7 +91,9 @@ describe('WorkflowVersionEdgeWorkspaceService', () => { globalWorkspaceOrmManager = { executeInWorkspaceContext: jest .fn() - .mockImplementation(async (_authContext, callback) => callback()), + .mockImplementation(async (callback: () => any, _authContext?: any) => + callback(), + ), getRepository: jest .fn() .mockResolvedValue(mockWorkflowVersionWorkspaceRepository), diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service.ts index 221444fc1f1..94b3a8fbf9d 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service.ts @@ -45,7 +45,6 @@ export class WorkflowVersionEdgeWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowVersionRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -96,6 +95,7 @@ export class WorkflowVersionEdgeWorkspaceService { }); } }, + authContext, ); } @@ -115,7 +115,6 @@ export class WorkflowVersionEdgeWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowVersionRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -166,6 +165,7 @@ export class WorkflowVersionEdgeWorkspaceService { }); } }, + authContext, ); } diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step.workspace-service.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step.workspace-service.spec.ts index 8d265c2ab28..15601291095 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step.workspace-service.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step.workspace-service.spec.ts @@ -115,7 +115,7 @@ describe('WorkflowVersionStepWorkspaceService', () => { executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), } as unknown as jest.Mocked; const module: TestingModule = await Test.createTestingModule({ diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-helpers.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-helpers.workspace-service.ts index 644496fd570..28f48497293 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-helpers.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-helpers.workspace-service.ts @@ -46,28 +46,25 @@ export class WorkflowVersionStepHelpersWorkspaceService { }): Promise { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workflowVersionRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workflowVersion', - { shouldBypassPermissionChecks: true }, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowVersionRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workflowVersion', + { shouldBypassPermissionChecks: true }, + ); - const updateData: Partial = {}; + const updateData: Partial = {}; - if (steps !== undefined) { - updateData.steps = steps; - } + if (steps !== undefined) { + updateData.steps = steps; + } - if (trigger !== undefined) { - updateData.trigger = trigger; - } + if (trigger !== undefined) { + updateData.trigger = trigger; + } - await workflowVersionRepository.update(workflowVersionId, updateData); - }, - ); + await workflowVersionRepository.update(workflowVersionId, updateData); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts index 299799ca993..238da56b1c3 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts @@ -533,7 +533,6 @@ export class WorkflowVersionStepOperationsWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const responseKeys = Object.keys(response); @@ -598,6 +597,7 @@ export class WorkflowVersionStepOperationsWorkspaceService { return acc; }, {}); }, + authContext, ); } @@ -727,7 +727,6 @@ export class WorkflowVersionStepOperationsWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowVersionRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -777,6 +776,7 @@ export class WorkflowVersionStepOperationsWorkspaceService { return emptyNodeStep; }, + authContext, ); } @@ -797,7 +797,6 @@ export class WorkflowVersionStepOperationsWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowVersionRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -878,6 +877,7 @@ export class WorkflowVersionStepOperationsWorkspaceService { branches, }; }, + authContext, ); } diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts index fc52faa7b2f..1a87c4b9321 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts @@ -50,7 +50,6 @@ export class WorkflowVersionWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowVersionRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -139,6 +138,7 @@ export class WorkflowVersionWorkspaceService { trigger: newWorkflowVersionTrigger, }; }, + authContext, ); } @@ -154,7 +154,6 @@ export class WorkflowVersionWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -311,6 +310,7 @@ export class WorkflowVersionWorkspaceService { trigger: remappedTrigger ?? null, }; }, + authContext, ); } @@ -325,61 +325,55 @@ export class WorkflowVersionWorkspaceService { }) { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workflowVersionRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workflowVersion', - { shouldBypassPermissionChecks: true }, - ); - - const workflowVersion = await workflowVersionRepository.findOneOrFail({ - where: { - id: workflowVersionId, - }, - }); - - assertWorkflowVersionIsDraft(workflowVersion); - - const triggerPosition = positions.find( - (position) => position.id === TRIGGER_STEP_ID, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowVersionRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workflowVersion', + { shouldBypassPermissionChecks: true }, ); - const updatedTrigger = - isDefined(triggerPosition) && isDefined(workflowVersion.trigger) - ? { - ...workflowVersion.trigger, - position: triggerPosition.position, - } - : undefined; + const workflowVersion = await workflowVersionRepository.findOneOrFail({ + where: { + id: workflowVersionId, + }, + }); - const updatedSteps = workflowVersion.steps?.map((step) => { - const updatedStep = positions.find( - (position) => position.id === step.id, - ); + assertWorkflowVersionIsDraft(workflowVersion); - if (updatedStep) { - return { - ...step, - position: updatedStep.position, - }; - } + const triggerPosition = positions.find( + (position) => position.id === TRIGGER_STEP_ID, + ); - return step; - }); + const updatedTrigger = + isDefined(triggerPosition) && isDefined(workflowVersion.trigger) + ? { + ...workflowVersion.trigger, + position: triggerPosition.position, + } + : undefined; - const updatePayload = { - ...(!isDefined(updatedTrigger) ? {} : { trigger: updatedTrigger }), - ...(!isDefined(updatedSteps) ? {} : { steps: updatedSteps }), - }; - - await workflowVersionRepository.update( - workflowVersionId, - updatePayload, + const updatedSteps = workflowVersion.steps?.map((step) => { + const updatedStep = positions.find( + (position) => position.id === step.id, ); - }, - ); + + if (updatedStep) { + return { + ...step, + position: updatedStep.position, + }; + } + + return step; + }); + + const updatePayload = { + ...(!isDefined(updatedTrigger) ? {} : { trigger: updatedTrigger }), + ...(!isDefined(updatedSteps) ? {} : { steps: updatedSteps }), + }; + + await workflowVersionRepository.update(workflowVersionId, updatePayload); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/delay/jobs/resume-delayed-workflow.job.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/delay/jobs/resume-delayed-workflow.job.ts index 25fb5769916..36d3dda37c0 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/delay/jobs/resume-delayed-workflow.job.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/delay/jobs/resume-delayed-workflow.job.ts @@ -41,72 +41,69 @@ export class ResumeDelayedWorkflowJob { }: ResumeDelayedWorkflowJobData): Promise { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - try { - const workflowRun = - await this.workflowRunWorkspaceService.getWorkflowRunOrFail({ - workflowRunId, - workspaceId, - }); - - if (workflowRun.status !== WorkflowRunStatus.RUNNING) { - return; - } - - const step = workflowRun.state?.flow?.steps?.find( - (step) => step.id === stepId, - ); - - const stepInfo = workflowRun.state?.stepInfos[stepId]; - - if (!step || !isWorkflowDelayAction(step)) { - throw new WorkflowRunException( - 'Step not found or is not a delay action', - WorkflowRunExceptionCode.INVALID_INPUT, - ); - } - - if (stepInfo?.status !== StepStatus.PENDING) { - throw new WorkflowRunException( - 'Step is not pending', - WorkflowRunExceptionCode.INVALID_INPUT, - ); - } - - await this.workflowRunWorkspaceService.updateWorkflowRunStepInfo({ - stepId, - stepInfo: { - status: StepStatus.SUCCESS, - result: { - success: true, - }, - }, - workspaceId, - workflowRunId, - }); - - await this.messageQueueService.add( - RunWorkflowJob.name, - { - workspaceId, - workflowRunId, - lastExecutedStepId: stepId, - }, - ); - } catch (error) { - await this.workflowRunWorkspaceService.endWorkflowRun({ + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + try { + const workflowRun = + await this.workflowRunWorkspaceService.getWorkflowRunOrFail({ workflowRunId, workspaceId, - status: WorkflowRunStatus.FAILED, - error: - error instanceof Error - ? error.message - : 'Unknown error during delay resume', }); + + if (workflowRun.status !== WorkflowRunStatus.RUNNING) { + return; } - }, - ); + + const step = workflowRun.state?.flow?.steps?.find( + (step) => step.id === stepId, + ); + + const stepInfo = workflowRun.state?.stepInfos[stepId]; + + if (!step || !isWorkflowDelayAction(step)) { + throw new WorkflowRunException( + 'Step not found or is not a delay action', + WorkflowRunExceptionCode.INVALID_INPUT, + ); + } + + if (stepInfo?.status !== StepStatus.PENDING) { + throw new WorkflowRunException( + 'Step is not pending', + WorkflowRunExceptionCode.INVALID_INPUT, + ); + } + + await this.workflowRunWorkspaceService.updateWorkflowRunStepInfo({ + stepId, + stepInfo: { + status: StepStatus.SUCCESS, + result: { + success: true, + }, + }, + workspaceId, + workflowRunId, + }); + + await this.messageQueueService.add( + RunWorkflowJob.name, + { + workspaceId, + workflowRunId, + lastExecutedStepId: stepId, + }, + ); + } catch (error) { + await this.workflowRunWorkspaceService.endWorkflowRun({ + workflowRunId, + workspaceId, + status: WorkflowRunStatus.FAILED, + error: + error instanceof Error + ? error.message + : 'Unknown error during delay resume', + }); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts index 40892886f89..75211ca8f53 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/jobs/run-workflow.job.ts @@ -39,32 +39,29 @@ export class RunWorkflowJob { }: RunWorkflowJobData): Promise { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - try { - if (lastExecutedStepId) { - await this.resumeWorkflowExecution({ - workspaceId, - workflowRunId, - lastExecutedStepId, - }); - } else { - await this.startWorkflowExecution({ - workflowRunId, - workspaceId, - }); - } - } catch (error) { - await this.workflowRunWorkspaceService.endWorkflowRun({ + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + try { + if (lastExecutedStepId) { + await this.resumeWorkflowExecution({ workspaceId, workflowRunId, - status: WorkflowRunStatus.FAILED, - error: error.message, + lastExecutedStepId, + }); + } else { + await this.startWorkflowExecution({ + workflowRunId, + workspaceId, }); } - }, - ); + } catch (error) { + await this.workflowRunWorkspaceService.endWorkflowRun({ + workspaceId, + workflowRunId, + status: WorkflowRunStatus.FAILED, + error: error.message, + }); + } + }, authContext); } private async startWorkflowExecution({ diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/cron/jobs/workflow-clean-workflow-runs.cron.job.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/cron/jobs/workflow-clean-workflow-runs.cron.job.ts index 199ed4b4a39..4f75ef8f03d 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/cron/jobs/workflow-clean-workflow-runs.cron.job.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/cron/jobs/workflow-clean-workflow-runs.cron.job.ts @@ -76,11 +76,9 @@ export class WorkflowCleanWorkflowRunsJob { const schemaName = getWorkspaceSchemaName(workspaceId); const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workflowRunsToDelete = await this.coreDataSource.query( - ` + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowRunsToDelete = await this.coreDataSource.query( + ` WITH ranked_runs AS ( SELECT id, ROW_NUMBER() OVER ( @@ -95,23 +93,22 @@ export class WorkflowCleanWorkflowRunsJob { WHERE rn > ${NUMBER_OF_WORKFLOW_RUNS_TO_KEEP} OR "createdAt" < NOW() - INTERVAL '14 days'; `, + ); + + const workflowRunRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + WorkflowRunWorkspaceEntity, + { shouldBypassPermissionChecks: true }, ); - const workflowRunRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - WorkflowRunWorkspaceEntity, - { shouldBypassPermissionChecks: true }, - ); + for (const workflowRunToDelete of workflowRunsToDelete) { + await workflowRunRepository.delete(workflowRunToDelete.id); + } - for (const workflowRunToDelete of workflowRunsToDelete) { - await workflowRunRepository.delete(workflowRunToDelete.id); - } - - this.logger.log( - `Deleted ${workflowRunsToDelete.length} workflow runs for workspace ${workspaceId}`, - ); - }, - ); + this.logger.log( + `Deleted ${workflowRunsToDelete.length} workflow runs for workspace ${workspaceId}`, + ); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/workspace-services/workflow-handle-staled-runs.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/workspace-services/workflow-handle-staled-runs.workspace-service.ts index 8275127139a..ed03548ede6 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/workspace-services/workflow-handle-staled-runs.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/workspace-services/workflow-handle-staled-runs.workspace-service.ts @@ -51,41 +51,38 @@ export class WorkflowHandleStaledRunsWorkspaceService { private async handleStaledRunsForWorkspace(workspaceId: string) { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workflowRunRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - WorkflowRunWorkspaceEntity, - { shouldBypassPermissionChecks: true }, - ); - - const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000); - - const staledWorkflowRuns = await workflowRunRepository.find({ - where: { - status: WorkflowRunStatus.ENQUEUED, - enqueuedAt: Or(LessThan(oneHourAgo), IsNull()), - }, - }); - - if (staledWorkflowRuns.length <= 0) { - return; - } - - await workflowRunRepository.update( - staledWorkflowRuns.map((workflowRun) => workflowRun.id), - { - enqueuedAt: null, - status: WorkflowRunStatus.NOT_STARTED, - }, - ); - - await this.workflowThrottlingWorkspaceService.recomputeWorkflowRunNotStartedCount( + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowRunRepository = + await this.globalWorkspaceOrmManager.getRepository( workspaceId, + WorkflowRunWorkspaceEntity, + { shouldBypassPermissionChecks: true }, ); - }, - ); + + const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000); + + const staledWorkflowRuns = await workflowRunRepository.find({ + where: { + status: WorkflowRunStatus.ENQUEUED, + enqueuedAt: Or(LessThan(oneHourAgo), IsNull()), + }, + }); + + if (staledWorkflowRuns.length <= 0) { + return; + } + + await workflowRunRepository.update( + staledWorkflowRuns.map((workflowRun) => workflowRun.id), + { + enqueuedAt: null, + status: WorkflowRunStatus.NOT_STARTED, + }, + ); + + await this.workflowThrottlingWorkspaceService.recomputeWorkflowRunNotStartedCount( + workspaceId, + ); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/workspace-services/workflow-run-enqueue.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/workspace-services/workflow-run-enqueue.workspace-service.ts index 5a56e06ce82..d19d2e49965 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/workspace-services/workflow-run-enqueue.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/workspace-services/workflow-run-enqueue.workspace-service.ts @@ -48,7 +48,6 @@ export class WorkflowRunEnqueueWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowRunRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -152,6 +151,7 @@ export class WorkflowRunEnqueueWorkspaceService { ); } }, + authContext, ); } catch (error) { this.metricsService.incrementCounter({ diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/workspace-services/workflow-throttling.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/workspace-services/workflow-throttling.workspace-service.ts index 62c62e51eda..43c1cb2078c 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/workspace-services/workflow-throttling.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run-queue/workspace-services/workflow-throttling.workspace-service.ts @@ -80,7 +80,6 @@ export class WorkflowThrottlingWorkspaceService { const currentlyNotStartedWorkflowRunCount = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowRunRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -95,6 +94,7 @@ export class WorkflowThrottlingWorkspaceService { }, }); }, + authContext, ); await this.setWorkflowRunNotStartedCount( @@ -113,7 +113,6 @@ export class WorkflowThrottlingWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowRunRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -128,6 +127,7 @@ export class WorkflowThrottlingWorkspaceService { }, }); }, + authContext, ); } diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/command/delete-workflow-runs.command.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/command/delete-workflow-runs.command.ts index b7cce596ca1..f55ddca45ef 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/command/delete-workflow-runs.command.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/command/delete-workflow-runs.command.ts @@ -53,38 +53,35 @@ export class DeleteWorkflowRunsCommand extends ActiveOrSuspendedWorkspacesMigrat }: RunOnWorkspaceArgs): Promise { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - try { - const workflowRunRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workflowRun', - { shouldBypassPermissionChecks: true }, - ); - - const createdAtCondition = { - createdAt: LessThan( - this.createdBeforeDate || new Date().toISOString(), - ), - }; - - const workflowRunCount = await workflowRunRepository.count({ - where: createdAtCondition, - }); - - if (!options.dryRun && workflowRunCount > 0) { - await workflowRunRepository.delete(createdAtCondition); - } - - this.logger.log( - `${options.dryRun ? ' (DRY RUN): ' : ''}Deleted ${workflowRunCount} workflow runs`, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + try { + const workflowRunRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workflowRun', + { shouldBypassPermissionChecks: true }, ); - } catch (error) { - this.logger.error('Error while deleting workflowRun', error); + + const createdAtCondition = { + createdAt: LessThan( + this.createdBeforeDate || new Date().toISOString(), + ), + }; + + const workflowRunCount = await workflowRunRepository.count({ + where: createdAtCondition, + }); + + if (!options.dryRun && workflowRunCount > 0) { + await workflowRunRepository.delete(createdAtCondition); } - }, - ); + + this.logger.log( + `${options.dryRun ? ' (DRY RUN): ' : ''}Deleted ${workflowRunCount} workflow runs`, + ); + } catch (error) { + this.logger.error('Error while deleting workflowRun', error); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts index d38438fc779..8ce7fbe623e 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts @@ -57,7 +57,6 @@ export class WorkflowRunWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowRunRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -136,6 +135,7 @@ export class WorkflowRunWorkspaceService { return workflowRun.id; }, + authContext, ); } @@ -346,7 +346,6 @@ export class WorkflowRunWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowRunRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -359,6 +358,7 @@ export class WorkflowRunWorkspaceService { where: { id: workflowRunId }, }); }, + authContext, ); } @@ -395,35 +395,32 @@ export class WorkflowRunWorkspaceService { }) { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workflowRunRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workflowRun', - { shouldBypassPermissionChecks: true }, - ); - - const workflowRunToUpdate = await workflowRunRepository.findOneBy({ - id: workflowRunId, - }); - - if (!workflowRunToUpdate) { - throw new WorkflowRunException( - `workflowRun ${workflowRunId} not found`, - WorkflowRunExceptionCode.WORKFLOW_RUN_NOT_FOUND, - ); - } - - await workflowRunRepository.update( - workflowRunToUpdate.id, - partialUpdate, - undefined, - ['id'], + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowRunRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workflowRun', + { shouldBypassPermissionChecks: true }, ); - }, - ); + + const workflowRunToUpdate = await workflowRunRepository.findOneBy({ + id: workflowRunId, + }); + + if (!workflowRunToUpdate) { + throw new WorkflowRunException( + `workflowRun ${workflowRunId} not found`, + WorkflowRunExceptionCode.WORKFLOW_RUN_NOT_FOUND, + ); + } + + await workflowRunRepository.update( + workflowRunToUpdate.id, + partialUpdate, + undefined, + ['id'], + ); + }, authContext); } private getInitState( diff --git a/packages/twenty-server/src/modules/workflow/workflow-status/jobs/__tests__/workflow-statuses-update.job.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-status/jobs/__tests__/workflow-statuses-update.job.spec.ts index a7facf6c639..da2f8f9218b 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-status/jobs/__tests__/workflow-statuses-update.job.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-status/jobs/__tests__/workflow-statuses-update.job.spec.ts @@ -49,7 +49,7 @@ describe('WorkflowStatusesUpdate', () => { executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), }; const mockServerlessFunctionService = { diff --git a/packages/twenty-server/src/modules/workflow/workflow-status/jobs/workflow-statuses-update.job.ts b/packages/twenty-server/src/modules/workflow/workflow-status/jobs/workflow-statuses-update.job.ts index a428640c3ac..10bbe736f19 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-status/jobs/workflow-statuses-update.job.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-status/jobs/workflow-statuses-update.job.ts @@ -78,36 +78,33 @@ export class WorkflowStatusesUpdateJob { async handle(event: WorkflowVersionBatchEvent): Promise { const authContext = buildSystemAuthContext(event.workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - switch (event.type) { - case WorkflowVersionEventType.CREATE: - case WorkflowVersionEventType.DELETE: - await Promise.all( - event.workflowIds.map((workflowId) => - this.handleWorkflowVersionCreatedOrDeleted({ - workflowId, - workspaceId: event.workspaceId, - }), - ), - ); - break; - case WorkflowVersionEventType.STATUS_UPDATE: - await Promise.all( - event.statusUpdates.map((statusUpdate) => - this.handleWorkflowVersionStatusUpdated({ - statusUpdate, - workspaceId: event.workspaceId, - }), - ), - ); - break; - default: - break; - } - }, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + switch (event.type) { + case WorkflowVersionEventType.CREATE: + case WorkflowVersionEventType.DELETE: + await Promise.all( + event.workflowIds.map((workflowId) => + this.handleWorkflowVersionCreatedOrDeleted({ + workflowId, + workspaceId: event.workspaceId, + }), + ), + ); + break; + case WorkflowVersionEventType.STATUS_UPDATE: + await Promise.all( + event.statusUpdates.map((statusUpdate) => + this.handleWorkflowVersionStatusUpdated({ + statusUpdate, + workspaceId: event.workspaceId, + }), + ), + ); + break; + default: + break; + } + }, authContext); } private async handleWorkflowVersionCreatedOrDeleted({ diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-complete-workflow.tool.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-complete-workflow.tool.ts index 902a2cd9dad..2b882e8bcb0 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-complete-workflow.tool.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-complete-workflow.tool.ts @@ -206,38 +206,35 @@ const createWorkflow = async ({ }): Promise => { const authContext = buildSystemAuthContext(context.workspaceId); - return deps.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workflowRepository = - await deps.globalWorkspaceOrmManager.getRepository( - context.workspaceId, - 'workflow', - context.rolePermissionConfig, - ); + return deps.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowRepository = + await deps.globalWorkspaceOrmManager.getRepository( + context.workspaceId, + 'workflow', + context.rolePermissionConfig, + ); - const workflowPosition = - await deps.recordPositionService.buildRecordPosition({ - value: 'first', - objectMetadata: { - isCustom: false, - nameSingular: 'workflow', - }, - workspaceId: context.workspaceId, - }); + const workflowPosition = + await deps.recordPositionService.buildRecordPosition({ + value: 'first', + objectMetadata: { + isCustom: false, + nameSingular: 'workflow', + }, + workspaceId: context.workspaceId, + }); - const workflow = { - id: uuidv4(), - name, - statuses: [WorkflowStatus.DRAFT], - position: workflowPosition, - }; + const workflow = { + id: uuidv4(), + name, + statuses: [WorkflowStatus.DRAFT], + position: workflowPosition, + }; - await workflowRepository.insert(workflow); + await workflowRepository.insert(workflow); - return workflow.id; - }, - ); + return workflow.id; + }, authContext); }; const createWorkflowVersion = async ({ @@ -255,41 +252,38 @@ const createWorkflowVersion = async ({ }): Promise => { const authContext = buildSystemAuthContext(context.workspaceId); - return deps.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workflowVersionRepository = - await deps.globalWorkspaceOrmManager.getRepository( - context.workspaceId, - 'workflowVersion', - context.rolePermissionConfig, - ); + return deps.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowVersionRepository = + await deps.globalWorkspaceOrmManager.getRepository( + context.workspaceId, + 'workflowVersion', + context.rolePermissionConfig, + ); - const versionPosition = - await deps.recordPositionService.buildRecordPosition({ - value: 'first', - objectMetadata: { - isCustom: false, - nameSingular: 'workflowVersion', - }, - workspaceId: context.workspaceId, - }); + const versionPosition = + await deps.recordPositionService.buildRecordPosition({ + value: 'first', + objectMetadata: { + isCustom: false, + nameSingular: 'workflowVersion', + }, + workspaceId: context.workspaceId, + }); - const workflowVersion = { - id: uuidv4(), - workflowId, - name: 'v1', - status: WorkflowVersionStatus.DRAFT, - trigger, - steps, - position: versionPosition, - }; + const workflowVersion = { + id: uuidv4(), + workflowId, + name: 'v1', + status: WorkflowVersionStatus.DRAFT, + trigger, + steps, + position: versionPosition, + }; - await workflowVersionRepository.insert(workflowVersion); + await workflowVersionRepository.insert(workflowVersion); - return workflowVersion.id; - }, - ); + return workflowVersion.id; + }, authContext); }; const updateWorkflowStatus = async ({ @@ -305,20 +299,17 @@ const updateWorkflowStatus = async ({ }) => { const authContext = buildSystemAuthContext(context.workspaceId); - await deps.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workflowRepository = - await deps.globalWorkspaceOrmManager.getRepository( - context.workspaceId, - 'workflow', - context.rolePermissionConfig, - ); + await deps.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowRepository = + await deps.globalWorkspaceOrmManager.getRepository( + context.workspaceId, + 'workflow', + context.rolePermissionConfig, + ); - await workflowRepository.update(workflowId, { - statuses: [WorkflowStatus.ACTIVE], - lastPublishedVersionId: workflowVersionId, - }); - }, - ); + await workflowRepository.update(workflowId, { + statuses: [WorkflowStatus.ACTIVE], + lastPublishedVersionId: workflowVersionId, + }); + }, authContext); }; diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/get-workflow-current-version.tool.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/get-workflow-current-version.tool.ts index b9cdbd5cd70..e851b1c978a 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/get-workflow-current-version.tool.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/get-workflow-current-version.tool.ts @@ -35,7 +35,6 @@ export const createGetWorkflowCurrentVersionTool = ( const authContext = buildSystemAuthContext(context.workspaceId); return await deps.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowRepository = await deps.globalWorkspaceOrmManager.getRepository( @@ -103,6 +102,7 @@ export const createGetWorkflowCurrentVersionTool = ( }, }; }, + authContext, ); } catch (error) { return { diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.workspace-service.ts index 2673512be8a..2e994f62c26 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.workspace-service.ts @@ -27,22 +27,19 @@ export class AutomatedTriggerWorkspaceService { }) { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workflowAutomatedTriggerRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workflowAutomatedTrigger', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowAutomatedTriggerRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workflowAutomatedTrigger', + ); - await workflowAutomatedTriggerRepository.insert({ - type, - settings, - workflowId, - }); - }, - ); + await workflowAutomatedTriggerRepository.insert({ + type, + settings, + workflowId, + }); + }, authContext); } async deleteAutomatedTrigger({ @@ -54,17 +51,14 @@ export class AutomatedTriggerWorkspaceService { }) { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workflowAutomatedTriggerRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workflowAutomatedTrigger', - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowAutomatedTriggerRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workflowAutomatedTrigger', + ); - await workflowAutomatedTriggerRepository.delete({ workflowId }); - }, - ); + await workflowAutomatedTriggerRepository.delete({ workflowId }); + }, authContext); } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/__tests__/workflow-database-event-trigger.listener.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/__tests__/workflow-database-event-trigger.listener.spec.ts index 01d0c8d5971..66b5e70b677 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/__tests__/workflow-database-event-trigger.listener.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/__tests__/workflow-database-event-trigger.listener.spec.ts @@ -52,7 +52,7 @@ describe('WorkflowDatabaseEventTriggerListener', () => { getRepository: jest.fn().mockResolvedValue(mockRepository), executeInWorkspaceContext: jest .fn() - .mockImplementation((_authContext: any, fn: () => any) => fn()), + .mockImplementation((fn: () => any, _authContext?: any) => fn()), } as any; messageQueueService = { diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/workflow-database-event-trigger.listener.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/workflow-database-event-trigger.listener.ts index 3a236d137a9..db4cb59b880 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/workflow-database-event-trigger.listener.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/automated-trigger/listeners/workflow-database-event-trigger.listener.ts @@ -247,64 +247,60 @@ export class WorkflowDatabaseEventTriggerListener { }) { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const { fieldIdByJoinColumnName } = - buildFieldMapsFromFlatObjectMetadata( - flatFieldMetadataMaps, - flatObjectMetadata, + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const { fieldIdByJoinColumnName } = buildFieldMapsFromFlatObjectMetadata( + flatFieldMetadataMaps, + flatObjectMetadata, + ); + + for (const [joinColumnName, joinFieldId] of Object.entries( + fieldIdByJoinColumnName, + )) { + const joinField = findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityMaps: flatFieldMetadataMaps, + flatEntityId: joinFieldId, + }); + + const joinRecordIds = records + .map((record) => record[joinColumnName]) + .filter(isDefined); + + if (joinRecordIds.length === 0) { + continue; + } + + const relatedObjectMetadataId = + joinField.relationTargetObjectMetadataId; + + if (!isDefined(relatedObjectMetadataId)) { + continue; + } + + const relatedObjectMetadataNameSingular = + flatObjectMetadataMaps.byId[relatedObjectMetadataId]?.nameSingular; + + if (!isDefined(relatedObjectMetadataNameSingular)) { + continue; + } + + const relatedObjectRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + relatedObjectMetadataNameSingular, + { shouldBypassPermissionChecks: true }, ); - for (const [joinColumnName, joinFieldId] of Object.entries( - fieldIdByJoinColumnName, - )) { - const joinField = findFlatEntityByIdInFlatEntityMapsOrThrow({ - flatEntityMaps: flatFieldMetadataMaps, - flatEntityId: joinFieldId, - }); + const relatedRecords = await relatedObjectRepository.find({ + where: { id: In(joinRecordIds) }, + }); - const joinRecordIds = records - .map((record) => record[joinColumnName]) - .filter(isDefined); - - if (joinRecordIds.length === 0) { - continue; - } - - const relatedObjectMetadataId = - joinField.relationTargetObjectMetadataId; - - if (!isDefined(relatedObjectMetadataId)) { - continue; - } - - const relatedObjectMetadataNameSingular = - flatObjectMetadataMaps.byId[relatedObjectMetadataId]?.nameSingular; - - if (!isDefined(relatedObjectMetadataNameSingular)) { - continue; - } - - const relatedObjectRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - relatedObjectMetadataNameSingular, - { shouldBypassPermissionChecks: true }, - ); - - const relatedRecords = await relatedObjectRepository.find({ - where: { id: In(joinRecordIds) }, - }); - - for (const record of records) { - record[joinField.name] = relatedRecords.find( - (relatedRecord) => relatedRecord.id === record[joinColumnName], - ); - } + for (const record of records) { + record[joinField.name] = relatedRecords.find( + (relatedRecord) => relatedRecord.id === record[joinColumnName], + ); } - }, - ); + } + }, authContext); } private async shouldIgnoreEvent( @@ -339,50 +335,47 @@ export class WorkflowDatabaseEventTriggerListener { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - const workflowAutomatedTriggerRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - automatedTriggerTableName, - { shouldBypassPermissionChecks: true }, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workflowAutomatedTriggerRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + automatedTriggerTableName, + { shouldBypassPermissionChecks: true }, + ); - const eventListeners = await workflowAutomatedTriggerRepository.find({ - where: { - type: AutomatedTriggerType.DATABASE_EVENT, - settings: Raw( - () => - `"${automatedTriggerTableName}"."settings"->>'eventName' = :eventName`, - { eventName: databaseEventName }, - ), - }, - }); + const eventListeners = await workflowAutomatedTriggerRepository.find({ + where: { + type: AutomatedTriggerType.DATABASE_EVENT, + settings: Raw( + () => + `"${automatedTriggerTableName}"."settings"->>'eventName' = :eventName`, + { eventName: databaseEventName }, + ), + }, + }); - for (const eventListener of eventListeners) { - for (const eventPayload of payload.events) { - const shouldTriggerJob = this.shouldTriggerJob({ - eventPayload, - eventListener, - action, - }); + for (const eventListener of eventListeners) { + for (const eventPayload of payload.events) { + const shouldTriggerJob = this.shouldTriggerJob({ + eventPayload, + eventListener, + action, + }); - if (shouldTriggerJob) { - await this.messageQueueService.add( - WorkflowTriggerJob.name, - { - workspaceId, - workflowId: eventListener.workflowId, - payload: eventPayload, - }, - { retryLimit: 3 }, - ); - } + if (shouldTriggerJob) { + await this.messageQueueService.add( + WorkflowTriggerJob.name, + { + workspaceId, + workflowId: eventListener.workflowId, + payload: eventPayload, + }, + { retryLimit: 3 }, + ); } } - }, - ); + } + }, authContext); } private shouldTriggerJob({ diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job.ts index 253d36386e7..2ec97cebfd6 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job.ts @@ -44,81 +44,78 @@ export class WorkflowTriggerJob { async handle(data: WorkflowTriggerJobData): Promise { const authContext = buildSystemAuthContext(data.workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, - async () => { - try { - const workflowRepository = - await this.globalWorkspaceOrmManager.getRepository( - data.workspaceId, - 'workflow', - { shouldBypassPermissionChecks: true }, - ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + try { + const workflowRepository = + await this.globalWorkspaceOrmManager.getRepository( + data.workspaceId, + 'workflow', + { shouldBypassPermissionChecks: true }, + ); - const workflow = await workflowRepository.findOneBy({ - id: data.workflowId, - }); + const workflow = await workflowRepository.findOneBy({ + id: data.workflowId, + }); - if (!workflow) { - throw new WorkflowTriggerException( - `Workflow ${data.workflowId} not found in workspace ${data.workspaceId}`, - WorkflowTriggerExceptionCode.NOT_FOUND, - ); - } - - if (!workflow.lastPublishedVersionId) { - throw new WorkflowTriggerException( - `Workflow ${data.workflowId} has no published version in workspace ${data.workspaceId}`, - WorkflowTriggerExceptionCode.INTERNAL_ERROR, - ); - } - - const workflowVersionRepository = - await this.globalWorkspaceOrmManager.getRepository( - data.workspaceId, - 'workflowVersion', - { shouldBypassPermissionChecks: true }, - ); - - const workflowVersion = await workflowVersionRepository.findOneBy({ - id: workflow.lastPublishedVersionId, - }); - - if (!workflowVersion) { - throw new WorkflowTriggerException( - `Workflow version ${workflow.lastPublishedVersionId} not found in workspace ${data.workspaceId}`, - WorkflowTriggerExceptionCode.NOT_FOUND, - ); - } - if (workflowVersion.status !== WorkflowVersionStatus.ACTIVE) { - throw new WorkflowTriggerException( - `Workflow version ${workflowVersion.id} is not active in workspace ${data.workspaceId}`, - WorkflowTriggerExceptionCode.INTERNAL_ERROR, - ); - } - - await this.workflowRunnerWorkspaceService.run({ - workspaceId: data.workspaceId, - workflowVersionId: workflow.lastPublishedVersionId, - payload: data.payload, - source: { - source: FieldActorSource.WORKFLOW, - name: - isDefined(workflow.name) && !isEmpty(workflow.name) - ? workflow.name - : DEFAULT_WORKFLOW_NAME, - context: {}, - workspaceMemberId: null, - }, - }); - } catch (e) { - await this.messageQueueService.removeCron({ - jobName: WorkflowTriggerJob.name, - jobId: data.workflowId, - }); - handleWorkflowTriggerException(e); + if (!workflow) { + throw new WorkflowTriggerException( + `Workflow ${data.workflowId} not found in workspace ${data.workspaceId}`, + WorkflowTriggerExceptionCode.NOT_FOUND, + ); } - }, - ); + + if (!workflow.lastPublishedVersionId) { + throw new WorkflowTriggerException( + `Workflow ${data.workflowId} has no published version in workspace ${data.workspaceId}`, + WorkflowTriggerExceptionCode.INTERNAL_ERROR, + ); + } + + const workflowVersionRepository = + await this.globalWorkspaceOrmManager.getRepository( + data.workspaceId, + 'workflowVersion', + { shouldBypassPermissionChecks: true }, + ); + + const workflowVersion = await workflowVersionRepository.findOneBy({ + id: workflow.lastPublishedVersionId, + }); + + if (!workflowVersion) { + throw new WorkflowTriggerException( + `Workflow version ${workflow.lastPublishedVersionId} not found in workspace ${data.workspaceId}`, + WorkflowTriggerExceptionCode.NOT_FOUND, + ); + } + if (workflowVersion.status !== WorkflowVersionStatus.ACTIVE) { + throw new WorkflowTriggerException( + `Workflow version ${workflowVersion.id} is not active in workspace ${data.workspaceId}`, + WorkflowTriggerExceptionCode.INTERNAL_ERROR, + ); + } + + await this.workflowRunnerWorkspaceService.run({ + workspaceId: data.workspaceId, + workflowVersionId: workflow.lastPublishedVersionId, + payload: data.payload, + source: { + source: FieldActorSource.WORKFLOW, + name: + isDefined(workflow.name) && !isEmpty(workflow.name) + ? workflow.name + : DEFAULT_WORKFLOW_NAME, + context: {}, + workspaceMemberId: null, + }, + }); + } catch (e) { + await this.messageQueueService.removeCron({ + jobName: WorkflowTriggerJob.name, + jobId: data.workflowId, + }); + handleWorkflowTriggerException(e); + } + }, authContext); } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts index c93e4c419dd..6df0328dba1 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service.ts @@ -82,7 +82,6 @@ export class WorkflowTriggerWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowVersionRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -132,6 +131,7 @@ export class WorkflowTriggerWorkspaceService { return true; }, + authContext, ); } @@ -142,7 +142,6 @@ export class WorkflowTriggerWorkspaceService { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext, async () => { const workflowVersionRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -159,6 +158,7 @@ export class WorkflowTriggerWorkspaceService { return true; }, + authContext, ); } diff --git a/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-delete-one.post-query.hook.ts b/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-delete-one.post-query.hook.ts index 78bc1d7b42f..8c6c41bb897 100644 --- a/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-delete-one.post-query.hook.ts +++ b/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-delete-one.post-query.hook.ts @@ -63,7 +63,6 @@ export class WorkspaceMemberDeleteOnePostQueryHook const workspaceMember = await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - authContext as WorkspaceAuthContext, async () => { const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -78,6 +77,7 @@ export class WorkspaceMemberDeleteOnePostQueryHook withDeleted: true, }); }, + authContext as WorkspaceAuthContext, ); if (!isDefined(workspaceMember)) {