From 859004f4fce0a2e8e3455ba7ed5572119b1e0bff Mon Sep 17 00:00:00 2001 From: Weiko Date: Mon, 8 Dec 2025 17:05:00 +0100 Subject: [PATCH] Refactor global datasource part 2 (#16399) ## Context Deprecating TwentyORMManager in favor of TwentyORMGlobalManager (temporarily, as this will simplify the ultimate goal to later replace all usages with the new TwentyORMGlobalManagerV2 which will have a similar signature) This means this PR had to refactor a bit of code to pass down the workspaceId when not available directly as it is now a requirement, meaning we also deprecated scopedWorkspaceContextFactory to have a less obscure way to fetch the workspaceId and have something more declarative. Step 3 will be to update TwentyORMGlobalManager to use a featureFlag toggling and use the new GlobalWorkspaceOrmManager internally using the new cache service Step 4 will be to remove the feature flag and pg_pool patch --- .../core/handlers/rest-api-base.handler.ts | 4 +- .../jobs/update-subscription-quantity.job.ts | 7 +- .../timeline-calendar-event.resolver.ts | 8 ++ .../timeline-calendar-event.service.spec.ts | 15 ++-- .../timeline-calendar-event.service.ts | 23 +++-- .../engine/core-modules/core-engine.module.ts | 4 +- .../services/get-messages.service.ts | 18 +++- .../services/timeline-messaging.service.ts | 18 ++-- .../messaging/timeline-messaging.resolver.ts | 3 + .../search/__tests__/search.service.spec.ts | 4 +- .../core-modules/search/search.module.ts | 9 +- .../core-modules/search/search.resolver.ts | 34 +++++++ .../search/services/search.service.ts | 13 ++- .../services/tool-provider.service.ts | 4 +- .../tool/services/tool-registry.service.ts | 7 +- .../tool/tools/http-tool/http-tool.ts | 5 +- .../tools/send-email-tool/send-email-tool.ts | 16 +--- .../core-modules/tool/types/tool.type.ts | 2 +- .../workflow-trigger.controller.ts | 1 + .../resolvers/workflow-trigger.resolver.ts | 11 ++- .../workflow-version-step.resolver.ts | 16 ++-- .../services/agent-async-executor.service.ts | 2 +- .../__tests__/tool-adapter.service.spec.ts | 34 +++---- .../ai-tools/services/tool-adapter.service.ts | 12 +-- .../page-layout/page-layout.module.ts | 4 + .../permissions/permissions.module.ts | 2 +- .../src/engine/twenty-orm/factories/index.ts | 2 - .../scoped-workspace-context.factory.ts | 45 ---------- .../engine/twenty-orm/twenty-orm.manager.ts | 90 ------------------- .../engine/twenty-orm/twenty-orm.module.ts | 16 +--- ...ocklist-item-delete-calendar-events.job.ts | 13 +-- .../blocklist-reimport-calendar-events.job.ts | 7 +- .../calendar-event-cleaner.service.ts | 11 ++- .../jobs/calendar-event-list-fetch.job.ts | 7 +- .../jobs/calendar-events-import.job.ts | 7 +- .../jobs/calendar-ongoing-stale.job.ts | 7 +- ...-event-import-exception-handler.service.ts | 7 +- .../calendar-events-import.service.ts | 7 +- .../services/calendar-fetch-events.service.ts | 7 +- .../services/calendar-save-events.service.ts | 15 ++-- ...event-participant-match-participant.job.ts | 2 + .../calendar-event-participant.service.ts | 8 +- ...alendar-event-find-many.post-query.hook.ts | 7 ++ ...calendar-event-find-one.post-query.hook.ts | 7 ++ ...ts-visibility-restrictions.service.spec.ts | 38 ++++---- ...-events-visibility-restrictions.service.ts | 16 ++-- .../email-alias-manager.service.spec.ts | 7 +- .../services/email-alias-manager.service.ts | 8 +- ...ected-account-delete-one.pre-query.hook.ts | 7 +- ...ted-account-refresh-tokens.service.spec.ts | 22 +++-- ...onnected-account-refresh-tokens.service.ts | 7 +- .../services/favorite-deletion.service.ts | 7 +- .../match-participant.module.ts | 3 +- .../match-participant.service.ts | 28 ++---- ...ging-blocklist-item-delete-messages.job.ts | 13 +-- ...ssaging-blocklist-reimport-messages.job.ts | 7 +- ...es-visibility-restrictions.service.spec.ts | 39 ++++---- ...essages-visibility-restrictions.service.ts | 16 ++-- .../message-find-many.post-query.hook.ts | 7 ++ .../message-find-one.post-query.hook.ts | 7 ++ .../jobs/messaging-message-list-fetch.job.ts | 7 +- .../jobs/messaging-messages-import.job.ts | 7 +- .../jobs/messaging-ongoing-stale.job.ts | 7 +- ...ssaging-message-list-fetch.service.spec.ts | 44 +++++---- .../messaging-messages-import.service.spec.ts | 19 ++-- .../services/messaging-cursor.service.ts | 13 ++- ...saging-import-exception-handler.service.ts | 7 +- .../messaging-message-list-fetch.service.ts | 27 ++++-- .../services/messaging-message.service.ts | 15 ++-- .../messaging-messages-import.service.ts | 8 +- ...essaging-process-folder-actions.service.ts | 12 ++- ...ing-process-group-email-actions.service.ts | 25 ++++-- ...d-enqueue-contact-creation.service.spec.ts | 8 +- ...es-and-enqueue-contact-creation.service.ts | 10 ++- ...ssage-participant-match-participant.job.ts | 4 +- .../messaging-message-participant.service.ts | 9 +- .../workflow-create-many.post-query.hook.ts | 7 +- .../workflow-create-one.post-query.hook.ts | 7 +- ...-step-operations.workspace-service.spec.ts | 5 -- .../ai-agent/ai-agent-action.module.ts | 7 +- .../code/code-action.module.ts | 3 +- .../code/code.workflow-action.ts | 12 +-- .../create-record.workflow-action.ts | 11 +-- .../delete-record.workflow-action.ts | 11 +-- .../find-records.workflow-action.ts | 11 +-- .../record-crud/record-crud-action.module.ts | 2 - .../update-record.workflow-action.ts | 11 +-- .../upsert-record.workflow-action.ts | 11 +-- .../tool-executor-workflow-action.ts | 3 +- .../workflow-executor.module.ts | 2 - .../workflow-run/workflow-run.module.ts | 7 +- .../workflow-run.workspace-service.ts | 14 +-- .../workflow-runner.workspace-service.ts | 6 ++ .../workflow-tool.workspace-service.ts | 2 + .../tools/activate-workflow-version.tool.ts | 7 +- .../tools/create-complete-workflow.tool.ts | 1 + .../tools/deactivate-workflow-version.tool.ts | 7 +- .../automated-trigger.workspace-service.ts | 22 +++-- .../workflow-trigger.module.ts | 7 +- .../workflow-trigger.workspace-service.ts | 66 ++++++++------ ...space-member-delete-one.post-query.hook.ts | 7 +- 101 files changed, 647 insertions(+), 597 deletions(-) delete mode 100644 packages/twenty-server/src/engine/twenty-orm/factories/scoped-workspace-context.factory.ts delete mode 100644 packages/twenty-server/src/engine/twenty-orm/twenty-orm.manager.ts diff --git a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-base.handler.ts b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-base.handler.ts index 5f77ac1b19..47acc70599 100644 --- a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-base.handler.ts +++ b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-base.handler.ts @@ -33,7 +33,7 @@ import { PermissionsExceptionMessage, } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects'; @@ -56,7 +56,7 @@ export interface FormatResult { export abstract class RestApiBaseHandler { @Inject() - protected readonly twentyORMManager: TwentyORMManager; + protected readonly twentyORMGlobalManager: TwentyORMGlobalManager; @Inject() protected readonly workspaceCacheService: WorkspaceCacheService; @Inject() 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 edc2cab89d..6956a9836b 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 @@ -7,7 +7,7 @@ import { StripeSubscriptionItemService } from 'src/engine/core-modules/billing/s import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; export type UpdateSubscriptionQuantityJobData = { workspaceId: string }; @@ -21,13 +21,14 @@ export class UpdateSubscriptionQuantityJob { constructor( private readonly billingSubscriptionService: BillingSubscriptionService, private readonly stripeSubscriptionItemService: StripeSubscriptionItemService, - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} @Process(UpdateSubscriptionQuantityJob.name) async handle(data: UpdateSubscriptionQuantityJobData): Promise { const workspaceMemberRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + data.workspaceId, 'workspaceMember', ); diff --git a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.resolver.ts b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.resolver.ts index a260a589bc..d1c782ad2c 100644 --- a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.resolver.ts @@ -7,9 +7,11 @@ import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/ import { TIMELINE_CALENDAR_EVENTS_MAX_PAGE_SIZE } from 'src/engine/core-modules/calendar/constants/calendar.constants'; import { TimelineCalendarEventsWithTotalDTO } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto'; import { TimelineCalendarEventService } from 'src/engine/core-modules/calendar/timeline-calendar-event.service'; +import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace-member-id.decorator'; import { CustomPermissionGuard } from 'src/engine/guards/custom-permission.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; +import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; @ArgsType() class GetTimelineCalendarEventsFromPersonIdArgs { @@ -62,11 +64,13 @@ export class TimelineCalendarEventResolver { @Args() { personId, page, pageSize }: GetTimelineCalendarEventsFromPersonIdArgs, @AuthWorkspaceMemberId() workspaceMemberId: string, + @AuthWorkspace() workspace: WorkspaceEntity, ) { const timelineCalendarEvents = await this.timelineCalendarEventService.getCalendarEventsFromPersonIds({ currentWorkspaceMemberId: workspaceMemberId, personIds: [personId], + workspaceId: workspace.id, page, pageSize, }); @@ -79,11 +83,13 @@ export class TimelineCalendarEventResolver { @Args() { companyId, page, pageSize }: GetTimelineCalendarEventsFromCompanyIdArgs, @AuthWorkspaceMemberId() workspaceMemberId: string, + @AuthWorkspace() workspace: WorkspaceEntity, ) { const timelineCalendarEvents = await this.timelineCalendarEventService.getCalendarEventsFromCompanyId({ currentWorkspaceMemberId: workspaceMemberId, companyId, + workspaceId: workspace.id, page, pageSize, }); @@ -100,12 +106,14 @@ export class TimelineCalendarEventResolver { pageSize, }: GetTimelineCalendarEventsFromOpportunityIdArgs, @AuthWorkspaceMemberId() workspaceMemberId: string, + @AuthWorkspace() workspace: WorkspaceEntity, ) { const timelineCalendarEvents = await this.timelineCalendarEventService.getCalendarEventsFromOpportunityId( { currentWorkspaceMemberId: workspaceMemberId, opportunityId, + workspaceId: workspace.id, page, pageSize, }, 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 d2a547e639..4462071892 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 @@ -3,7 +3,7 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { type CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; @@ -36,16 +36,18 @@ describe('TimelineCalendarEventService', () => { findAndCount: jest.fn(), }; - const mockTwentyORMManager = { - getRepository: jest.fn().mockResolvedValue(mockCalendarEventRepository), + const mockTwentyORMGlobalManager = { + getRepositoryForWorkspace: jest + .fn() + .mockResolvedValue(mockCalendarEventRepository), }; const module: TestingModule = await Test.createTestingModule({ providers: [ TimelineCalendarEventService, { - provide: TwentyORMManager, - useValue: mockTwentyORMManager, + provide: TwentyORMGlobalManager, + useValue: mockTwentyORMGlobalManager, }, ], }).compile(); @@ -84,6 +86,7 @@ describe('TimelineCalendarEventService', () => { const result = await service.getCalendarEventsFromPersonIds({ currentWorkspaceMemberId, personIds, + workspaceId: 'test-workspace-id', page: 1, pageSize: 10, }); @@ -123,6 +126,7 @@ describe('TimelineCalendarEventService', () => { const result = await service.getCalendarEventsFromPersonIds({ currentWorkspaceMemberId, personIds, + workspaceId: 'test-workspace-id', page: 1, pageSize: 10, }); @@ -164,6 +168,7 @@ describe('TimelineCalendarEventService', () => { const result = await service.getCalendarEventsFromPersonIds({ currentWorkspaceMemberId, personIds, + workspaceId: 'test-workspace-id', page: 1, pageSize: 10, }); 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 620109ca71..839ce524d8 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 @@ -6,7 +6,7 @@ import { Any } from 'typeorm'; import { TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE } from 'src/engine/core-modules/calendar/constants/calendar.constants'; import { type TimelineCalendarEventsWithTotalDTO } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { type CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; import { type OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity'; @@ -14,24 +14,29 @@ import { type PersonWorkspaceEntity } from 'src/modules/person/standard-objects/ @Injectable() export class TimelineCalendarEventService { - constructor(private readonly twentyORMManager: TwentyORMManager) {} + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} // TODO: Align return type with the entities to avoid mapping async getCalendarEventsFromPersonIds({ currentWorkspaceMemberId, personIds, + workspaceId, page = 1, pageSize = TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE, }: { currentWorkspaceMemberId: string; personIds: string[]; + workspaceId: string; page: number; pageSize: number; }): Promise { const offset = (page - 1) * pageSize; const calendarEventRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'calendarEvent', ); @@ -162,16 +167,19 @@ export class TimelineCalendarEventService { async getCalendarEventsFromCompanyId({ currentWorkspaceMemberId, companyId, + workspaceId, page = 1, pageSize = TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE, }: { currentWorkspaceMemberId: string; companyId: string; + workspaceId: string; page: number; pageSize: number; }): Promise { const personRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'person', ); @@ -196,6 +204,7 @@ export class TimelineCalendarEventService { const calendarEvents = await this.getCalendarEventsFromPersonIds({ currentWorkspaceMemberId, personIds: formattedPersonIds, + workspaceId, page, pageSize, }); @@ -206,16 +215,19 @@ export class TimelineCalendarEventService { async getCalendarEventsFromOpportunityId({ currentWorkspaceMemberId, opportunityId, + workspaceId, page = 1, pageSize = TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE, }: { currentWorkspaceMemberId: string; opportunityId: string; + workspaceId: string; page: number; pageSize: number; }): Promise { const opportunityRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'opportunity', ); @@ -238,6 +250,7 @@ export class TimelineCalendarEventService { const calendarEvents = await this.getCalendarEventsFromCompanyId({ currentWorkspaceMemberId, companyId: opportunity.companyId, + workspaceId, page, pageSize, }); diff --git a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts index 537598c639..2b2b88425d 100644 --- a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts +++ b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts @@ -37,7 +37,6 @@ import { MessageQueueModule } from 'src/engine/core-modules/message-queue/messag import { messageQueueModuleFactory } from 'src/engine/core-modules/message-queue/message-queue.module-factory'; import { TimelineMessagingModule } from 'src/engine/core-modules/messaging/timeline-messaging.module'; import { OpenApiModule } from 'src/engine/core-modules/open-api/open-api.module'; -import { PageLayoutModule } from 'src/engine/metadata-modules/page-layout/page-layout.module'; import { PostgresCredentialsModule } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.module'; import { PublicDomainModule } from 'src/engine/core-modules/public-domain/public-domain.module'; import { RedisClientModule } from 'src/engine/core-modules/redis-client/redis-client.module'; @@ -56,12 +55,13 @@ import { WorkspaceInvitationModule } from 'src/engine/core-modules/workspace-inv import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module'; import { AiBillingModule } from 'src/engine/metadata-modules/ai/ai-billing/ai-billing.module'; import { AiModelsModule } from 'src/engine/metadata-modules/ai/ai-models/ai-models.module'; +import { FlatPageLayoutTabModule } from 'src/engine/metadata-modules/flat-page-layout-tab/flat-page-layout-tab.module'; import { RoleModule } from 'src/engine/metadata-modules/role/role.module'; import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module'; import { TrashCleanupModule } from 'src/engine/trash-cleanup/trash-cleanup.module'; import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module'; import { ChannelSyncModule } from 'src/modules/connected-account/channel-sync/channel-sync.module'; -import { FlatPageLayoutTabModule } from 'src/engine/metadata-modules/flat-page-layout-tab/flat-page-layout-tab.module'; +import { PageLayoutModule } from 'src/engine/metadata-modules/page-layout/page-layout.module'; import { AuditModule } from './audit/audit.module'; import { ClientConfigModule } from './client-config/client-config.module'; 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 d5cd241d85..0203313569 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 @@ -4,20 +4,21 @@ import { TIMELINE_THREADS_DEFAULT_PAGE_SIZE } from 'src/engine/core-modules/mess import { type TimelineThreadsWithTotalDTO } from 'src/engine/core-modules/messaging/dtos/timeline-threads-with-total.dto'; import { TimelineMessagingService } from 'src/engine/core-modules/messaging/services/timeline-messaging.service'; import { formatThreads } from 'src/engine/core-modules/messaging/utils/format-threads.util'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity'; import { type PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; @Injectable() export class GetMessagesService { constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly timelineMessagingService: TimelineMessagingService, ) {} async getMessagesFromPersonIds( workspaceMemberId: string, personIds: string[], + workspaceId: string, page = 1, pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE, ): Promise { @@ -26,6 +27,7 @@ export class GetMessagesService { const { messageThreads, totalNumberOfThreads } = await this.timelineMessagingService.getAndCountMessageThreads( personIds, + workspaceId, offset, pageSize, ); @@ -44,12 +46,14 @@ export class GetMessagesService { const threadParticipantsByThreadId = await this.timelineMessagingService.getThreadParticipantsByThreadId( messageThreadIds, + workspaceId, ); const threadVisibilityByThreadId = await this.timelineMessagingService.getThreadVisibilityByThreadId( messageThreadIds, workspaceMemberId, + workspaceId, ); return { @@ -65,11 +69,13 @@ export class GetMessagesService { async getMessagesFromCompanyId( workspaceMemberId: string, companyId: string, + workspaceId: string, page = 1, pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE, ): Promise { const personRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'person', ); const personIds = ( @@ -93,6 +99,7 @@ export class GetMessagesService { const messageThreads = await this.getMessagesFromPersonIds( workspaceMemberId, personIds, + workspaceId, page, pageSize, ); @@ -103,11 +110,13 @@ export class GetMessagesService { async getMessagesFromOpportunityId( workspaceMemberId: string, opportunityId: string, + workspaceId: string, page = 1, pageSize: number = TIMELINE_THREADS_DEFAULT_PAGE_SIZE, ): Promise { const opportunityRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'opportunity', ); @@ -130,6 +139,7 @@ export class GetMessagesService { const messageThreads = await this.getMessagesFromCompanyId( workspaceMemberId, opportunity.companyId, + workspaceId, page, pageSize, ); 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 b73fe09e9c..a6c86d149d 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 @@ -3,17 +3,20 @@ import { Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { type TimelineThreadDTO } from 'src/engine/core-modules/messaging/dtos/timeline-thread.dto'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { type MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity'; import { type MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity'; @Injectable() export class TimelineMessagingService { - constructor(private readonly twentyORMManager: TwentyORMManager) {} + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} public async getAndCountMessageThreads( personIds: string[], + workspaceId: string, offset: number, pageSize: number, ): Promise<{ @@ -28,7 +31,8 @@ export class TimelineMessagingService { totalNumberOfThreads: number; }> { const messageThreadRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageThread', ); @@ -87,11 +91,13 @@ export class TimelineMessagingService { public async getThreadParticipantsByThreadId( messageThreadIds: string[], + workspaceId: string, ): Promise<{ [key: string]: MessageParticipantWorkspaceEntity[]; }> { const messageParticipantRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageParticipant', ); @@ -176,11 +182,13 @@ export class TimelineMessagingService { public async getThreadVisibilityByThreadId( messageThreadIds: string[], workspaceMemberId: string, + workspaceId: string, ): Promise<{ [key: string]: MessageChannelVisibility; }> { const messageThreadRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageThread', ); diff --git a/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.resolver.ts b/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.resolver.ts index efeca838b3..76f26c738f 100644 --- a/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.resolver.ts @@ -93,6 +93,7 @@ export class TimelineMessagingResolver { await this.getMessagesFromPersonIdsService.getMessagesFromPersonIds( workspaceMember.id, [personId], + workspace.id, page, pageSize, ); @@ -119,6 +120,7 @@ export class TimelineMessagingResolver { await this.getMessagesFromPersonIdsService.getMessagesFromCompanyId( workspaceMember.id, companyId, + workspace.id, page, pageSize, ); @@ -146,6 +148,7 @@ export class TimelineMessagingResolver { await this.getMessagesFromPersonIdsService.getMessagesFromOpportunityId( workspaceMember.id, opportunityId, + workspace.id, page, pageSize, ); diff --git a/packages/twenty-server/src/engine/core-modules/search/__tests__/search.service.spec.ts b/packages/twenty-server/src/engine/core-modules/search/__tests__/search.service.spec.ts index 6a984ed317..169920f9d2 100644 --- a/packages/twenty-server/src/engine/core-modules/search/__tests__/search.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/search/__tests__/search.service.spec.ts @@ -7,7 +7,7 @@ import { } from 'src/engine/core-modules/__mocks__/mockFlatObjectMetadatas'; import { FileService } from 'src/engine/core-modules/file/services/file.service'; import { SearchService } from 'src/engine/core-modules/search/services/search.service'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; describe('SearchService', () => { @@ -17,7 +17,7 @@ describe('SearchService', () => { const module: TestingModule = await Test.createTestingModule({ providers: [ SearchService, - { provide: TwentyORMManager, useValue: {} }, + { provide: TwentyORMGlobalManager, useValue: {} }, { provide: WorkspaceCacheStorageService, useValue: {} }, { provide: FileService, useValue: {} }, ], diff --git a/packages/twenty-server/src/engine/core-modules/search/search.module.ts b/packages/twenty-server/src/engine/core-modules/search/search.module.ts index 2c8aa75407..ff7eabd8a3 100644 --- a/packages/twenty-server/src/engine/core-modules/search/search.module.ts +++ b/packages/twenty-server/src/engine/core-modules/search/search.module.ts @@ -4,9 +4,16 @@ import { FileModule } from 'src/engine/core-modules/file/file.module'; import { SearchResolver } from 'src/engine/core-modules/search/search.resolver'; import { SearchService } from 'src/engine/core-modules/search/services/search.service'; import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; +import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; +import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module'; @Module({ - imports: [FileModule, WorkspaceManyOrAllFlatEntityMapsCacheModule], + imports: [ + FileModule, + WorkspaceManyOrAllFlatEntityMapsCacheModule, + UserRoleModule, + PermissionsModule, + ], providers: [SearchResolver, SearchService], }) export class SearchModule {} diff --git a/packages/twenty-server/src/engine/core-modules/search/search.resolver.ts b/packages/twenty-server/src/engine/core-modules/search/search.resolver.ts index caf1b4f27f..367e051d62 100644 --- a/packages/twenty-server/src/engine/core-modules/search/search.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/search/search.resolver.ts @@ -3,6 +3,7 @@ import { Args, Query, Resolver } from '@nestjs/graphql'; import { isDefined } from 'twenty-shared/utils'; +import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service'; import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter'; import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe'; import { SearchArgs } from 'src/engine/core-modules/search/dtos/search-args'; @@ -10,10 +11,14 @@ import { SearchResultConnectionDTO } from 'src/engine/core-modules/search/dtos/s import { SearchApiExceptionFilter } from 'src/engine/core-modules/search/filters/search-api-exception.filter'; import { SearchService } from 'src/engine/core-modules/search/services/search.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; +import { AuthApiKey } from 'src/engine/decorators/auth/auth-api-key.decorator'; +import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { CustomPermissionGuard } from 'src/engine/guards/custom-permission.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; +import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; @Resolver() @UseFilters(SearchApiExceptionFilter, PreventNestToAutoLogGraphqlErrorsFilter) @@ -23,11 +28,15 @@ export class SearchResolver { constructor( private readonly searchService: SearchService, private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, + private readonly userRoleService: UserRoleService, + private readonly apiKeyRoleService: ApiKeyRoleService, ) {} @Query(() => SearchResultConnectionDTO) async search( @AuthWorkspace() workspace: WorkspaceEntity, + @AuthUserWorkspaceId() userWorkspaceId: string | undefined, + @AuthApiKey() apiKey: string | undefined, @Args() { searchInput, @@ -57,6 +66,29 @@ export class SearchResolver { excludedObjectNameSingulars: excludedObjectNameSingulars ?? [], }); + // TODO: move to a service + let rolePermissionConfig: RolePermissionConfig | undefined; + + if (isDefined(apiKey)) { + const roleId = await this.apiKeyRoleService.getRoleIdForApiKey( + apiKey, + workspace.id, + ); + + if (isDefined(roleId)) { + rolePermissionConfig = { unionOf: [roleId] }; + } + } else if (isDefined(userWorkspaceId)) { + const roleId = await this.userRoleService.getRoleIdForUserWorkspace({ + userWorkspaceId, + workspaceId: workspace.id, + }); + + if (isDefined(roleId)) { + rolePermissionConfig = { unionOf: [roleId] }; + } + } + const allRecordsWithObjectMetadataItems = await this.searchService.getAllRecordsWithObjectMetadataItems({ flatObjectMetadatas: filteredObjectMetadataItems, @@ -67,6 +99,8 @@ export class SearchResolver { includedObjectNameSingulars, excludedObjectNameSingulars, after, + workspaceId: workspace.id, + rolePermissionConfig, }); return this.searchService.computeSearchObjectResults({ 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 c7045d8bc4..43c09cdc47 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 @@ -31,7 +31,8 @@ import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-m import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants'; import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; type LastRanks = { tsRankCD: number; tsRank: number }; @@ -45,7 +46,7 @@ const OBJECT_METADATA_ITEMS_CHUNK_SIZE = 5; @Injectable() export class SearchService { constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly fileService: FileService, ) {} @@ -58,9 +59,13 @@ export class SearchService { limit, filter, after, + workspaceId, + rolePermissionConfig, }: { flatObjectMetadatas: FlatObjectMetadata[]; flatFieldMetadataMaps: FlatEntityMaps; + workspaceId: string; + rolePermissionConfig?: RolePermissionConfig; } & SearchArgs) { const filteredObjectMetadataItems = this.filterObjectMetadataItems({ flatObjectMetadatas, @@ -80,8 +85,10 @@ export class SearchService { const recordsWithObjectMetadataItems = await Promise.all( objectMetadataItemChunk.map(async (flatObjectMetadata) => { const repository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, flatObjectMetadata.nameSingular, + rolePermissionConfig, ); return { diff --git a/packages/twenty-server/src/engine/core-modules/tool-provider/services/tool-provider.service.ts b/packages/twenty-server/src/engine/core-modules/tool-provider/services/tool-provider.service.ts index 773d0c83cd..596d886c38 100644 --- a/packages/twenty-server/src/engine/core-modules/tool-provider/services/tool-provider.service.ts +++ b/packages/twenty-server/src/engine/core-modules/tool-provider/services/tool-provider.service.ts @@ -173,7 +173,7 @@ export class ToolProviderService { description: tool.description, inputSchema: tool.inputSchema, execute: async (parameters: { input: ToolInput }) => - tool.execute(parameters.input), + tool.execute(parameters.input, spec.workspaceId), }; } else if (spec.rolePermissionConfig && spec.workspaceId) { const hasPermission = await this.permissionsService.hasToolPermission( @@ -187,7 +187,7 @@ export class ToolProviderService { description: tool.description, inputSchema: tool.inputSchema, execute: async (parameters: { input: ToolInput }) => - tool.execute(parameters.input), + tool.execute(parameters.input, spec.workspaceId), }; } } diff --git a/packages/twenty-server/src/engine/core-modules/tool/services/tool-registry.service.ts b/packages/twenty-server/src/engine/core-modules/tool/services/tool-registry.service.ts index 77396a566b..4a07648f18 100644 --- a/packages/twenty-server/src/engine/core-modules/tool/services/tool-registry.service.ts +++ b/packages/twenty-server/src/engine/core-modules/tool/services/tool-registry.service.ts @@ -26,7 +26,8 @@ export class ToolRegistryService { return { description: httpTool.description, inputSchema: httpTool.inputSchema, - execute: (params) => httpTool.execute(params), + execute: (params, workspaceId) => + httpTool.execute(params, workspaceId), flag: PermissionFlagType.HTTP_REQUEST_TOOL, }; }, @@ -36,8 +37,8 @@ export class ToolRegistryService { () => ({ description: this.sendEmailTool.description, inputSchema: this.sendEmailTool.inputSchema, - execute: (params) => - this.sendEmailTool.execute(params as SendEmailInput), + execute: (params, workspaceId) => + this.sendEmailTool.execute(params as SendEmailInput, workspaceId), flag: PermissionFlagType.SEND_EMAIL_TOOL, }), ], diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/http-tool/http-tool.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/http-tool/http-tool.ts index 82eb75fbcb..8eee4ab691 100644 --- a/packages/twenty-server/src/engine/core-modules/tool/tools/http-tool/http-tool.ts +++ b/packages/twenty-server/src/engine/core-modules/tool/tools/http-tool/http-tool.ts @@ -20,7 +20,10 @@ export class HttpTool implements Tool { constructor(private readonly twentyConfigService: TwentyConfigService) {} - async execute(parameters: ToolInput): Promise { + async execute( + parameters: ToolInput, + _workspaceId: string, + ): Promise { const { url, method, headers, body } = parameters as HttpRequestInput; const headersCopy = { ...headers }; const isMethodForBody = ['POST', 'PUT', 'PATCH'].includes(method); 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 c8f97245d7..5aca19cbe7 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 @@ -18,7 +18,6 @@ import { SendEmailToolParametersZodSchema } from 'src/engine/core-modules/tool/t import { type SendEmailInput } from 'src/engine/core-modules/tool/tools/send-email-tool/types/send-email-input.type'; import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type'; import { type Tool } from 'src/engine/core-modules/tool/types/tool.type'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { MessagingSendMessageService } from 'src/modules/messaging/message-import-manager/services/messaging-send-message.service'; @@ -36,7 +35,6 @@ export class SendEmailTool implements Tool { inputSchema = SendEmailToolParametersZodSchema; constructor( - private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly sendMessageService: MessagingSendMessageService, @InjectRepository(FileEntity) @@ -160,9 +158,10 @@ export class SendEmailTool implements Tool { return attachments; } - async execute(parameters: SendEmailInput): Promise { - const { workspaceId } = this.scopedWorkspaceContextFactory.create(); - + async execute( + parameters: SendEmailInput, + workspaceId: string, + ): Promise { const { email, subject, body, files } = parameters; let { connectedAccountId } = parameters; @@ -180,13 +179,6 @@ export class SendEmailTool implements Tool { ); } - if (!workspaceId) { - throw new SendEmailToolException( - 'Workspace ID not found', - SendEmailToolExceptionCode.WORKSPACE_ID_NOT_FOUND, - ); - } - if (!connectedAccountId) { connectedAccountId = await this.getOrThrowFirstConnectedAccountId(workspaceId); diff --git a/packages/twenty-server/src/engine/core-modules/tool/types/tool.type.ts b/packages/twenty-server/src/engine/core-modules/tool/types/tool.type.ts index 583892023d..46f696ea64 100644 --- a/packages/twenty-server/src/engine/core-modules/tool/types/tool.type.ts +++ b/packages/twenty-server/src/engine/core-modules/tool/types/tool.type.ts @@ -7,6 +7,6 @@ import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output. export type Tool = { description: string; inputSchema: FlexibleSchema; - execute(input: ToolInput): Promise; + execute(input: ToolInput, workspaceId: string): Promise; flag?: PermissionFlagType; }; 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 e26a016a01..b4d2490d3c 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 @@ -141,6 +141,7 @@ export class WorkflowTriggerController { name: 'Webhook', context: {}, }, + workspaceId, }); return { 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 23a628accc..4b3ea51465 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 @@ -43,21 +43,25 @@ export class WorkflowTriggerResolver { @Mutation(() => Boolean) async activateWorkflowVersion( + @AuthWorkspace() workspace: WorkspaceEntity, @Args('workflowVersionId', { type: () => UUIDScalarType }) workflowVersionId: string, ) { return this.workflowTriggerWorkspaceService.activateWorkflowVersion( workflowVersionId, + workspace.id, ); } @Mutation(() => Boolean) async deactivateWorkflowVersion( + @AuthWorkspace() workspace: WorkspaceEntity, @Args('workflowVersionId', { type: () => UUIDScalarType }) workflowVersionId: string, ) { return this.workflowTriggerWorkspaceService.deactivateWorkflowVersion( workflowVersionId, + workspace.id, ); } @@ -92,14 +96,19 @@ export class WorkflowTriggerResolver { }, workspaceMemberId: workspaceMember.id, }), + workspaceId: workspace.id, }); } @Mutation(() => WorkflowRunDTO) async stopWorkflowRun( + @AuthWorkspace() workspace: WorkspaceEntity, @Args('workflowRunId', { type: () => UUIDScalarType }) workflowRunId: string, ) { - return this.workflowTriggerWorkspaceService.stopWorkflowRun(workflowRunId); + return this.workflowTriggerWorkspaceService.stopWorkflowRun( + workflowRunId, + workspace.id, + ); } } diff --git a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version-step.resolver.ts b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version-step.resolver.ts index f0c45bc6b8..9058553b3a 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version-step.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version-step.resolver.ts @@ -150,14 +150,18 @@ export class WorkflowVersionStepResolver { @Mutation(() => TestHttpRequestOutput) async testHttpRequest( + @AuthWorkspace() workspace: WorkspaceEntity, @Args('input') { url, method, headers, body }: TestHttpRequestInput, ): Promise { - return this.httpTool.execute({ - url, - method, - headers, - body, - }); + return this.httpTool.execute( + { + url, + method, + headers, + body, + }, + workspace.id, + ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts index 0464e4d109..7ddf1160e1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts @@ -23,10 +23,10 @@ import { AGENT_SYSTEM_PROMPTS } from 'src/engine/metadata-modules/ai/ai-agent/co import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; import { repairToolCall } from 'src/engine/metadata-modules/ai/ai-agent/utils/repair-tool-call.util'; import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-telemetry.const'; +import { AgentModelConfigService } from 'src/engine/metadata-modules/ai/ai-models/services/agent-model-config.service'; import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service'; import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; -import { AgentModelConfigService } from 'src/engine/metadata-modules/ai/ai-models/services/agent-model-config.service'; // Agent execution within workflows uses database and action tools only. // Workflow tools are intentionally excluded to avoid circular dependencies diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-tools/services/__tests__/tool-adapter.service.spec.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-tools/services/__tests__/tool-adapter.service.spec.ts index 30d2771608..a977bc34a3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-tools/services/__tests__/tool-adapter.service.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-tools/services/__tests__/tool-adapter.service.spec.ts @@ -85,38 +85,28 @@ describe('ToolAdapterService', () => { service = moduleRef.get(ToolAdapterService); }); - it('should include unflagged tools regardless of role/workspace', async () => { - const toolsNoContext = await service.getTools(); + it('should include unflagged tools regardless of rolePermissionConfig', async () => { + const toolsNoContext = await service.getTools('ws-1'); expect(Object.keys(toolsNoContext)).toContain('http_request'); - const toolsWithPartialContext = await service.getTools({ + const toolsWithPartialContext = await service.getTools('ws-1', { unionOf: ['role-1'], }); expect(Object.keys(toolsWithPartialContext)).toContain('http_request'); }); - it('should not include flagged tools when role/workspace are missing', async () => { - const toolsNoContext = await service.getTools(); + it('should not include flagged tools when rolePermissionConfig is missing', async () => { + const toolsNoRoleConfig = await service.getTools('ws-1'); - expect(Object.keys(toolsNoContext)).not.toContain('send_email'); - - const toolsRoleOnly = await service.getTools({ - unionOf: ['role-1'], - }); - - expect(Object.keys(toolsRoleOnly)).not.toContain('send_email'); - - const toolsWorkspaceOnly = await service.getTools(undefined, 'ws-1'); - - expect(Object.keys(toolsWorkspaceOnly)).not.toContain('send_email'); + expect(Object.keys(toolsNoRoleConfig)).not.toContain('send_email'); }); it('should include flagged tools when permission is granted', async () => { mockPermissions.hasToolPermission.mockResolvedValueOnce(true); - const tools = await service.getTools({ unionOf: ['role-1'] }, 'ws-1'); + const tools = await service.getTools('ws-1', { unionOf: ['role-1'] }); expect(mockPermissions.hasToolPermission).toHaveBeenCalledWith( { unionOf: ['role-1'] }, @@ -130,13 +120,13 @@ describe('ToolAdapterService', () => { it('should exclude flagged tools when permission is denied', async () => { mockPermissions.hasToolPermission.mockResolvedValueOnce(false); - const tools = await service.getTools({ unionOf: ['role-1'] }, 'ws-1'); + const tools = await service.getTools('ws-1', { unionOf: ['role-1'] }); expect(Object.keys(tools)).not.toContain('send_email'); }); it('should lowercase tool type keys in the returned ToolSet', async () => { - const tools = await service.getTools(); + const tools = await service.getTools('ws-1'); const keys = Object.keys(tools); @@ -145,7 +135,7 @@ describe('ToolAdapterService', () => { }); it('should forward execute input correctly and return underlying result', async () => { - const tools = await service.getTools(); + const tools = await service.getTools('ws-1'); const input = { url: 'https://example.com', method: 'GET' } as ToolInput; const result = await tools['http_request'].execute?.( @@ -161,8 +151,8 @@ describe('ToolAdapterService', () => { }, ); - // Ensure wrapper forwards only parameters.input - expect(unflaggedToolExecute).toHaveBeenCalledWith(input); + // Ensure wrapper forwards parameters.input and workspaceId + expect(unflaggedToolExecute).toHaveBeenCalledWith(input, 'ws-1'); expect(result).toEqual({ success: true, message: 'Tool executed successfully', diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-tools/services/tool-adapter.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-tools/services/tool-adapter.service.ts index dd20f30ff2..eae3ea8b0d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-tools/services/tool-adapter.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-tools/services/tool-adapter.service.ts @@ -17,8 +17,8 @@ export class ToolAdapterService { ) {} async getTools( + workspaceId: string, rolePermissionConfig?: RolePermissionConfig, - workspaceId?: string, ): Promise { const tools: ToolSet = {}; @@ -26,8 +26,8 @@ export class ToolAdapterService { const tool = this.toolRegistry.getTool(toolType); if (!tool.flag) { - tools[toolType.toLowerCase()] = this.createToolSet(tool); - } else if (rolePermissionConfig && workspaceId) { + tools[toolType.toLowerCase()] = this.createToolSet(tool, workspaceId); + } else if (rolePermissionConfig) { const hasPermission = await this.permissionsService.hasToolPermission( rolePermissionConfig, workspaceId, @@ -35,7 +35,7 @@ export class ToolAdapterService { ); if (hasPermission) { - tools[toolType.toLowerCase()] = this.createToolSet(tool); + tools[toolType.toLowerCase()] = this.createToolSet(tool, workspaceId); } } } @@ -43,12 +43,12 @@ export class ToolAdapterService { return tools; } - private createToolSet(tool: Tool) { + private createToolSet(tool: Tool, workspaceId: string) { return { description: tool.description, inputSchema: tool.inputSchema, execute: async (parameters: { input: ToolInput }) => - tool.execute(parameters.input), + tool.execute(parameters.input, workspaceId), }; } } diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout/page-layout.module.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout/page-layout.module.ts index fe237f4ff9..2391a56c52 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout/page-layout.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout/page-layout.module.ts @@ -4,6 +4,8 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; +import { FlatPageLayoutTabModule } from 'src/engine/metadata-modules/flat-page-layout-tab/flat-page-layout-tab.module'; +import { FlatPageLayoutWidgetModule } from 'src/engine/metadata-modules/flat-page-layout-widget/flat-page-layout-widget.module'; import { PageLayoutTabController } from 'src/engine/metadata-modules/page-layout/controllers/page-layout-tab.controller'; import { PageLayoutWidgetController } from 'src/engine/metadata-modules/page-layout/controllers/page-layout-widget.controller'; import { PageLayoutController } from 'src/engine/metadata-modules/page-layout/controllers/page-layout.controller'; @@ -36,6 +38,8 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa WorkspaceCacheStorageModule, WorkspaceMigrationV2Module, WorkspaceManyOrAllFlatEntityMapsCacheModule, + FlatPageLayoutTabModule, + FlatPageLayoutWidgetModule, ], controllers: [ PageLayoutController, diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.module.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.module.ts index 44b10439cd..8685741891 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.module.ts @@ -28,6 +28,6 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache RoleTargetModule, ], providers: [ApiKeyRoleService, PermissionsService], - exports: [PermissionsService], + exports: [PermissionsService, ApiKeyRoleService], }) export class PermissionsModule {} diff --git a/packages/twenty-server/src/engine/twenty-orm/factories/index.ts b/packages/twenty-server/src/engine/twenty-orm/factories/index.ts index 6f8a8477b9..b07da0d7bc 100644 --- a/packages/twenty-server/src/engine/twenty-orm/factories/index.ts +++ b/packages/twenty-server/src/engine/twenty-orm/factories/index.ts @@ -1,7 +1,6 @@ import { EntitySchemaColumnFactory } from 'src/engine/twenty-orm/factories/entity-schema-column.factory'; import { EntitySchemaRelationFactory } from 'src/engine/twenty-orm/factories/entity-schema-relation.factory'; import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkspaceDatasourceFactory } from 'src/engine/twenty-orm/factories/workspace-datasource.factory'; export const entitySchemaFactories = [ @@ -9,5 +8,4 @@ export const entitySchemaFactories = [ EntitySchemaRelationFactory, EntitySchemaFactory, WorkspaceDatasourceFactory, - ScopedWorkspaceContextFactory, ]; diff --git a/packages/twenty-server/src/engine/twenty-orm/factories/scoped-workspace-context.factory.ts b/packages/twenty-server/src/engine/twenty-orm/factories/scoped-workspace-context.factory.ts deleted file mode 100644 index f4b46404bb..0000000000 --- a/packages/twenty-server/src/engine/twenty-orm/factories/scoped-workspace-context.factory.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Inject, Injectable, Optional, Scope } from '@nestjs/common'; -import { REQUEST } from '@nestjs/core'; - -@Injectable({ scope: Scope.REQUEST }) -export class ScopedWorkspaceContextFactory { - constructor( - @Optional() - @Inject(REQUEST) - private readonly request: Request | null, - ) {} - - public create(): { - workspaceId: string | null; - userWorkspaceId: string | null; - isExecutedByApiKey: boolean; - apiKeyId: string | null; - } { - const workspaceId: string | undefined = - // @ts-expect-error legacy noImplicitAny - this.request?.['req']?.['workspaceId'] || - // @ts-expect-error legacy noImplicitAny - this.request?.['params']?.['workspaceId'] || - // @ts-expect-error legacy noImplicitAny - this.request?.['workspace']?.['id']; // rest api - - // Extract API key ID (same pattern as userWorkspaceId) - const apiKey = - // @ts-expect-error legacy noImplicitAny - this.request?.['req']?.['apiKey'] || - // @ts-expect-error legacy noImplicitAny - this.request?.['apiKey']; // rest api - - return { - workspaceId: workspaceId ?? null, - userWorkspaceId: - // @ts-expect-error legacy noImplicitAny - this.request?.['req']?.['userWorkspaceId'] ?? - // @ts-expect-error legacy noImplicitAny - this.request?.['userWorkspaceId'] ?? // rest api - null, - isExecutedByApiKey: !!apiKey, - apiKeyId: apiKey?.id ?? null, - }; - } -} diff --git a/packages/twenty-server/src/engine/twenty-orm/twenty-orm.manager.ts b/packages/twenty-server/src/engine/twenty-orm/twenty-orm.manager.ts deleted file mode 100644 index ac2ccbf5d1..0000000000 --- a/packages/twenty-server/src/engine/twenty-orm/twenty-orm.manager.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Injectable, type Type } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import { isDefined } from 'twenty-shared/utils'; -import { type ObjectLiteral, Repository } from 'typeorm'; - -import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; -import { WorkspaceDatasourceFactory } from 'src/engine/twenty-orm/factories/workspace-datasource.factory'; -import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; -import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util'; - -@Injectable() -export class TwentyORMManager { - constructor( - @InjectRepository(RoleTargetEntity) - private readonly roleTargetRepository: Repository, - private readonly workspaceDataSourceFactory: WorkspaceDatasourceFactory, - private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, - ) {} - - async getRepository( - workspaceEntity: Type, - ): Promise>; - - async getRepository( - objectMetadataName: string, - ): Promise>; - - async getRepository( - workspaceEntityOrObjectMetadataName: Type | string, - ): Promise> { - const { workspaceId, userWorkspaceId, apiKeyId } = - this.scopedWorkspaceContextFactory.create(); - - let objectMetadataName: string; - - if (typeof workspaceEntityOrObjectMetadataName === 'string') { - objectMetadataName = workspaceEntityOrObjectMetadataName; - } else { - objectMetadataName = convertClassNameToObjectMetadataName( - workspaceEntityOrObjectMetadataName.name, - ); - } - - if (!workspaceId) { - throw new Error('Workspace not found'); - } - - const workspaceDataSource = - await this.workspaceDataSourceFactory.create(workspaceId); - - let roleId: string | undefined; - - if (isDefined(userWorkspaceId)) { - const roleTarget = await this.roleTargetRepository.findOne({ - where: { - userWorkspaceId, - workspaceId, - }, - }); - - roleId = roleTarget?.roleId; - } else if (isDefined(apiKeyId)) { - const roleTarget = await this.roleTargetRepository.findOne({ - where: { - apiKeyId, - workspaceId, - }, - }); - - roleId = roleTarget?.roleId; - } - - return workspaceDataSource.getRepository( - objectMetadataName, - roleId ? { unionOf: [roleId] } : undefined, - ); - } - - async getDatasource() { - const { workspaceId } = this.scopedWorkspaceContextFactory.create(); - - if (!workspaceId) { - throw new Error('Workspace not found'); - } - - return this.workspaceDataSourceFactory.create(workspaceId); - } -} diff --git a/packages/twenty-server/src/engine/twenty-orm/twenty-orm.module.ts b/packages/twenty-server/src/engine/twenty-orm/twenty-orm.module.ts index 70492e1726..a5ab96929a 100644 --- a/packages/twenty-server/src/engine/twenty-orm/twenty-orm.module.ts +++ b/packages/twenty-server/src/engine/twenty-orm/twenty-orm.module.ts @@ -12,9 +12,7 @@ import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-t import { WorkspaceFeatureFlagsMapCacheModule } from 'src/engine/metadata-modules/workspace-feature-flags-map-cache/workspace-feature-flags-map-cache.module'; import { entitySchemaFactories } from 'src/engine/twenty-orm/factories'; import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; @@ -38,17 +36,7 @@ import { PgPoolSharedModule } from './pg-shared-pool/pg-shared-pool.module'; PgPoolSharedModule, WorkspaceCacheModule, ], - providers: [ - ...entitySchemaFactories, - TwentyORMManager, - TwentyORMGlobalManager, - ], - exports: [ - EntitySchemaFactory, - TwentyORMManager, - TwentyORMGlobalManager, - PgPoolSharedModule, - ScopedWorkspaceContextFactory, - ], + providers: [...entitySchemaFactories, TwentyORMGlobalManager], + exports: [EntitySchemaFactory, TwentyORMGlobalManager, PgPoolSharedModule], }) export class TwentyORMModule {} 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 9d9bda7260..32aa6d8bc2 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 @@ -7,7 +7,7 @@ import { type ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emit import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; import { type BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity'; import { CalendarEventCleanerService } from 'src/modules/calendar/calendar-event-cleaner/services/calendar-event-cleaner.service'; @@ -24,7 +24,7 @@ export type BlocklistItemDeleteCalendarEventsJobData = WorkspaceEventBatch< }) export class BlocklistItemDeleteCalendarEventsJob { constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly calendarEventCleanerService: CalendarEventCleanerService, ) {} @@ -37,7 +37,8 @@ export class BlocklistItemDeleteCalendarEventsJob { ); const blocklistRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'blocklist', ); @@ -67,12 +68,14 @@ export class BlocklistItemDeleteCalendarEventsJob { ); const calendarChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'calendarChannel', ); const calendarChannelEventAssociationRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'calendarChannelEventAssociation', ); 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 2e012007c8..679bfce6ac 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 @@ -6,7 +6,7 @@ import { type ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emit import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; import { type BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; @@ -25,7 +25,7 @@ export type BlocklistReimportCalendarEventsJobData = WorkspaceEventBatch< }) export class BlocklistReimportCalendarEventsJob { constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService, ) {} @@ -34,7 +34,8 @@ export class BlocklistReimportCalendarEventsJob { const workspaceId = data.workspaceId; const calendarChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'calendarChannel', ); 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 a2d7af0a35..556d2a2242 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 @@ -2,16 +2,21 @@ import { Injectable } from '@nestjs/common'; import { Any, IsNull } from 'typeorm'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { deleteUsingPagination } from 'src/modules/messaging/message-cleaner/utils/delete-using-pagination.util'; @Injectable() export class CalendarEventCleanerService { - constructor(private readonly twentyORMManager: TwentyORMManager) {} + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} public async cleanWorkspaceCalendarEvents(workspaceId: string) { const calendarEventRepository = - await this.twentyORMManager.getRepository('calendarEvent'); + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'calendarEvent', + ); await deleteUsingPagination( workspaceId, 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 0c4941b973..3f542be4a5 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 @@ -3,7 +3,7 @@ import { Scope } from '@nestjs/common'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { CalendarFetchEventsService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; import { @@ -23,7 +23,7 @@ export type CalendarEventListFetchJobData = { }) export class CalendarEventListFetchJob { constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService, private readonly calendarFetchEventsService: CalendarFetchEventsService, ) {} @@ -33,7 +33,8 @@ export class CalendarEventListFetchJob { const { workspaceId, calendarChannelId } = data; const calendarChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'calendarChannel', ); 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 1fe6886f98..52e015d01e 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 @@ -3,7 +3,7 @@ import { Scope } from '@nestjs/common'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { CalendarEventsImportService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; import { @@ -25,7 +25,7 @@ export class CalendarEventsImportJob { constructor( private readonly calendarEventsImportService: CalendarEventsImportService, private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService, - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} @Process(CalendarEventsImportJob.name) @@ -33,7 +33,8 @@ export class CalendarEventsImportJob { const { calendarChannelId, workspaceId } = data; const calendarChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'calendarChannel', ); const calendarChannel = await calendarChannelRepository.findOne({ 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 3a2b8141c7..c4c35d5f57 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 @@ -5,7 +5,7 @@ import { In } from 'typeorm'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { isSyncStale } from 'src/modules/calendar/calendar-event-import-manager/utils/is-sync-stale.util'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; import { @@ -24,7 +24,7 @@ export type CalendarOngoingStaleJobData = { export class CalendarOngoingStaleJob { private readonly logger = new Logger(CalendarOngoingStaleJob.name); constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService, ) {} @@ -33,7 +33,8 @@ export class CalendarOngoingStaleJob { const { workspaceId } = data; const calendarChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'calendarChannel', ); 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 dd3a4dd790..74a5a82e4b 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 @@ -5,7 +5,7 @@ import { type TwentyORMException, TwentyORMExceptionCode, } from 'src/engine/twenty-orm/exceptions/twenty-orm.exception'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { CALENDAR_THROTTLE_MAX_ATTEMPTS } from 'src/modules/calendar/calendar-event-import-manager/constants/calendar-throttle-max-attempts'; import { type CalendarEventImportDriverException, @@ -28,7 +28,7 @@ export class CalendarEventImportErrorHandlerService { CalendarEventImportErrorHandlerService.name, ); constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService, private readonly exceptionHandlerService: ExceptionHandlerService, ) {} @@ -131,7 +131,8 @@ export class CalendarEventImportErrorHandlerService { } const calendarChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'calendarChannel', ); 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 64d7d1ad37..f428d20104 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 @@ -7,7 +7,7 @@ import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decora import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { BlocklistRepository } from 'src/modules/blocklist/repositories/blocklist.repository'; import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity'; import { CalendarEventCleanerService } from 'src/modules/calendar/calendar-event-cleaner/services/calendar-event-cleaner.service'; @@ -34,7 +34,7 @@ export class CalendarEventsImportService { constructor( @InjectCacheStorage(CacheStorageNamespace.ModuleCalendar) private readonly cacheStorage: CacheStorageService, - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, @InjectObjectMetadataRepository(BlocklistWorkspaceEntity) private readonly blocklistRepository: BlocklistRepository, private readonly calendarEventCleanerService: CalendarEventCleanerService, @@ -137,7 +137,8 @@ export class CalendarEventsImportService { ); } const calendarChannelEventAssociationRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'calendarChannelEventAssociation', ); 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 536ffcf4d3..6705748988 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 @@ -5,7 +5,7 @@ import { isDefined } from 'twenty-shared/utils'; import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator'; import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { CalendarEventImportDriverException, CalendarEventImportDriverExceptionCode, @@ -27,7 +27,7 @@ export class CalendarFetchEventsService { constructor( @InjectCacheStorage(CacheStorageNamespace.ModuleCalendar) private readonly cacheStorage: CacheStorageService, - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService, private readonly getCalendarEventsService: CalendarGetCalendarEventsService, private readonly calendarEventImportErrorHandlerService: CalendarEventImportErrorHandlerService, @@ -83,7 +83,8 @@ export class CalendarFetchEventsService { const nextSyncCursor = getCalendarEventsResponse.nextSyncCursor; const calendarChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'calendarChannel', ); 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 7644496ded..1f83c1e544 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 @@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common'; import { Any } from 'typeorm'; import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { CalendarEventParticipantService } from 'src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service'; import { type CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity'; import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; @@ -20,7 +20,7 @@ type FetchedCalendarEventWithDBEvent = { @Injectable() export class CalendarSaveEventsService { constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly calendarEventParticipantService: CalendarEventParticipantService, ) {} @@ -31,12 +31,14 @@ export class CalendarSaveEventsService { workspaceId: string, ): Promise { const calendarEventRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'calendarEvent', ); const calendarChannelEventAssociationRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'calendarChannelEventAssociation', ); @@ -61,7 +63,10 @@ export class CalendarSaveEventsService { }; }); - const workspaceDataSource = await this.twentyORMManager.getDatasource(); + const workspaceDataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace({ + workspaceId, + }); await workspaceDataSource.transaction( async (transactionManager: WorkspaceEntityManager) => { diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/jobs/calendar-event-participant-match-participant.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/jobs/calendar-event-participant-match-participant.job.ts index 0887a01fa3..f5613a5e57 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/jobs/calendar-event-participant-match-participant.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/jobs/calendar-event-participant-match-participant.job.ts @@ -53,6 +53,7 @@ export class CalendarEventParticipantMatchParticipantJob { await this.matchParticipantService.matchParticipantsForPeople({ objectMetadataName: 'calendarEventParticipant', participantMatching, + workspaceId, }); } @@ -60,6 +61,7 @@ export class CalendarEventParticipantMatchParticipantJob { await this.matchParticipantService.matchParticipantsForWorkspaceMembers({ objectMetadataName: 'calendarEventParticipant', participantMatching, + workspaceId, }); } } 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 128de9668c..ba3f31806a 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 @@ -10,7 +10,7 @@ import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decora import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { type CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity'; import { type FetchedCalendarEventParticipant } from 'src/modules/calendar/common/types/fetched-calendar-event'; @@ -34,7 +34,7 @@ type FetchedCalendarEventParticipantWithCalendarEventIdAndExistingId = @Injectable() export class CalendarEventParticipantService { constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly matchParticipantService: MatchParticipantService, @InjectMessageQueue(MessageQueue.contactCreationQueue) private readonly messageQueueService: MessageQueueService, @@ -58,7 +58,8 @@ export class CalendarEventParticipantService { const chunkedParticipantsToUpdate = chunk(participantsToUpdate, 200); const calendarEventParticipantRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'calendarEventParticipant', ); @@ -172,6 +173,7 @@ export class CalendarEventParticipantService { objectMetadataName: 'calendarEventParticipant', transactionManager, matchWith: 'workspaceMemberAndPerson', + workspaceId, }); } } diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-many.post-query.hook.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-many.post-query.hook.ts index 215bb417d5..be8c5e8f28 100644 --- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-many.post-query.hook.ts +++ b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-many.post-query.hook.ts @@ -31,8 +31,15 @@ export class CalendarEventFindManyPostQueryHook throw new ForbiddenError('User is required'); } + const workspace = authContext.workspace; + + if (!workspace) { + throw new ForbiddenError('Workspace is required'); + } + await this.applyCalendarEventsVisibilityRestrictionsService.applyCalendarEventsVisibilityRestrictions( payload, + workspace.id, user?.id, ); } diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.post-query.hook.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.post-query.hook.ts index 5c5d3aa879..d6bc8e16cb 100644 --- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.post-query.hook.ts +++ b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.post-query.hook.ts @@ -31,8 +31,15 @@ export class CalendarEventFindOnePostQueryHook throw new ForbiddenError('User is required'); } + const workspace = authContext.workspace; + + if (!workspace) { + throw new ForbiddenError('Workspace is required'); + } + await this.applyCalendarEventsVisibilityRestrictionsService.applyCalendarEventsVisibilityRestrictions( payload, + workspace.id, user?.id, ); } 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 b9b494da82..662aba6e42 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 @@ -2,7 +2,7 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { type CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; @@ -52,18 +52,20 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { findOneByOrFail: jest.fn(), }; - const mockTwentyORMManager = { - getRepository: jest.fn().mockImplementation((name) => { - if (name === 'calendarChannelEventAssociation') { - return mockCalendarEventAssociationRepository; - } - if (name === 'connectedAccount') { - return mockConnectedAccountRepository; - } - if (name === 'workspaceMember') { - return mockWorkspaceMemberRepository; - } - }), + const mockTwentyORMGlobalManager = { + getRepositoryForWorkspace: jest + .fn() + .mockImplementation((workspaceId, name) => { + if (name === 'calendarChannelEventAssociation') { + return mockCalendarEventAssociationRepository; + } + if (name === 'connectedAccount') { + return mockConnectedAccountRepository; + } + if (name === 'workspaceMember') { + return mockWorkspaceMemberRepository; + } + }), }; beforeEach(async () => { @@ -71,8 +73,8 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { providers: [ ApplyCalendarEventsVisibilityRestrictionsService, { - provide: TwentyORMManager, - useValue: mockTwentyORMManager, + provide: TwentyORMGlobalManager, + useValue: mockTwentyORMGlobalManager, }, ], }).compile(); @@ -106,6 +108,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { const result = await service.applyCalendarEventsVisibilityRestrictions( calendarEvents, + 'test-workspace-id', 'user-id', ); @@ -143,6 +146,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { const result = await service.applyCalendarEventsVisibilityRestrictions( calendarEvents, + 'test-workspace-id', 'user-id', ); @@ -178,6 +182,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { const result = await service.applyCalendarEventsVisibilityRestrictions( calendarEvents, + 'test-workspace-id', 'user-id', ); @@ -213,6 +218,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { const result = await service.applyCalendarEventsVisibilityRestrictions( calendarEvents, + 'test-workspace-id', 'user-id', ); @@ -260,6 +266,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { const result = await service.applyCalendarEventsVisibilityRestrictions( calendarEvents, + 'test-workspace-id', 'user-id', ); @@ -315,6 +322,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { const result = await service.applyCalendarEventsVisibilityRestrictions( calendarEvents, + 'test-workspace-id', undefined, ); 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 46a9dfe697..9aa2942228 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 @@ -5,7 +5,7 @@ import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/ import { isDefined } from 'twenty-shared/utils'; import { In } from 'typeorm'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity'; import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { type CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; @@ -14,14 +14,18 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta @Injectable() export class ApplyCalendarEventsVisibilityRestrictionsService { - constructor(private readonly twentyORMManager: TwentyORMManager) {} + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} public async applyCalendarEventsVisibilityRestrictions( calendarEvents: CalendarEventWorkspaceEntity[], + workspaceId: string, userId?: string, // undefined when request is made with api key ) { const calendarChannelEventAssociationRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'calendarChannelEventAssociation', ); @@ -34,12 +38,14 @@ export class ApplyCalendarEventsVisibilityRestrictionsService { }); const connectedAccountRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'connectedAccount', ); const workspaceMemberRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'workspaceMember', ); 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 32491c1a9d..53ad647be3 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 @@ -3,7 +3,7 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { ConnectedAccountProvider } from 'twenty-shared/types'; import { type Repository } from 'typeorm'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service'; import { microsoftGraphMeResponseWithProxyAddresses } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/mocks/microsoft-api-examples'; import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/services/microsoft-email-alias-manager.service'; @@ -28,9 +28,9 @@ describe('Email Alias Manager Service', () => { const module: TestingModule = await Test.createTestingModule({ providers: [ { - provide: TwentyORMManager, + provide: TwentyORMGlobalManager, useValue: { - getRepository: jest + getRepositoryForWorkspace: jest .fn() .mockResolvedValue(connectedAccountRepository), }, @@ -86,6 +86,7 @@ describe('Email Alias Manager Service', () => { await emailAliasManagerService.refreshHandleAliases( mockConnectedAccount as ConnectedAccountWorkspaceEntity, + 'test-workspace-id', ); expect( 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 eaeeff0511..ba454cbb2b 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 @@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common'; import { ConnectedAccountProvider } from 'twenty-shared/types'; import { assertUnreachable } from 'twenty-shared/utils'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service'; import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/services/microsoft-email-alias-manager.service'; import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; @@ -13,11 +13,12 @@ export class EmailAliasManagerService { constructor( private readonly googleEmailAliasManagerService: GoogleEmailAliasManagerService, private readonly microsoftEmailAliasManagerService: MicrosoftEmailAliasManagerService, - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} public async refreshHandleAliases( connectedAccount: ConnectedAccountWorkspaceEntity, + workspaceId: string, ) { let handleAliases: string[]; @@ -46,7 +47,8 @@ export class EmailAliasManagerService { } const connectedAccountRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'connectedAccount', ); 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 10aa462fb4..6fd3bbe847 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 @@ -12,7 +12,7 @@ import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-contex import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception'; import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; @@ -21,7 +21,7 @@ export class ConnectedAccountDeleteOnePreQueryHook implements WorkspacePreQueryHookInstance { constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly workspaceEventEmitter: WorkspaceEventEmitter, @InjectRepository(ObjectMetadataEntity) private readonly objectMetadataRepository: Repository, @@ -39,7 +39,8 @@ export class ConnectedAccountDeleteOnePreQueryHook assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError); const messageChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspace.id, 'messageChannel', ); 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 f176eb2f93..0445b3e1c3 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 @@ -2,7 +2,7 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { ConnectedAccountProvider } from 'twenty-shared/types'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service'; import { MicrosoftAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/microsoft/services/microsoft-api-refresh-tokens.service'; import { @@ -17,7 +17,7 @@ describe('ConnectedAccountRefreshTokensService', () => { let service: ConnectedAccountRefreshTokensService; let googleAPIRefreshAccessTokenService: GoogleAPIRefreshAccessTokenService; let microsoftAPIRefreshAccessTokenService: MicrosoftAPIRefreshAccessTokenService; - let twentyORMManager: TwentyORMManager; + let twentyORMGlobalManager: TwentyORMGlobalManager; const mockWorkspaceId = 'workspace-123'; const mockConnectedAccountId = 'account-456'; @@ -42,9 +42,9 @@ describe('ConnectedAccountRefreshTokensService', () => { }, }, { - provide: TwentyORMManager, + provide: TwentyORMGlobalManager, useValue: { - getRepository: jest.fn(), + getRepositoryForWorkspace: jest.fn(), }, }, ], @@ -61,7 +61,9 @@ describe('ConnectedAccountRefreshTokensService', () => { module.get( MicrosoftAPIRefreshAccessTokenService, ); - twentyORMManager = module.get(TwentyORMManager); + twentyORMGlobalManager = module.get( + TwentyORMGlobalManager, + ); }); afterEach(() => { @@ -90,7 +92,9 @@ describe('ConnectedAccountRefreshTokensService', () => { expect( microsoftAPIRefreshAccessTokenService.refreshTokens, ).not.toHaveBeenCalled(); - expect(twentyORMManager.getRepository).not.toHaveBeenCalled(); + expect( + twentyORMGlobalManager.getRepositoryForWorkspace, + ).not.toHaveBeenCalled(); }); it('should refresh and save new Microsoft token when expired (lastCredentialsRefreshedAt is old)', async () => { @@ -112,7 +116,7 @@ describe('ConnectedAccountRefreshTokensService', () => { .spyOn(microsoftAPIRefreshAccessTokenService, 'refreshTokens') .mockResolvedValue(newTokens); jest - .spyOn(twentyORMManager, 'getRepository') + .spyOn(twentyORMGlobalManager, 'getRepositoryForWorkspace') .mockResolvedValue(mockRepository as any); const result = await service.refreshAndSaveTokens( @@ -152,7 +156,7 @@ describe('ConnectedAccountRefreshTokensService', () => { .spyOn(googleAPIRefreshAccessTokenService, 'refreshTokens') .mockResolvedValue(newTokens); jest - .spyOn(twentyORMManager, 'getRepository') + .spyOn(twentyORMGlobalManager, 'getRepositoryForWorkspace') .mockResolvedValue(mockRepository as any); const result = await service.refreshAndSaveTokens( @@ -192,7 +196,7 @@ describe('ConnectedAccountRefreshTokensService', () => { .spyOn(microsoftAPIRefreshAccessTokenService, 'refreshTokens') .mockResolvedValue(newTokens); jest - .spyOn(twentyORMManager, 'getRepository') + .spyOn(twentyORMGlobalManager, 'getRepositoryForWorkspace') .mockResolvedValue(mockRepository as any); const result = await service.refreshAndSaveTokens( 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 be7f4dfe80..a63c666553 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 @@ -3,7 +3,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { ConnectedAccountProvider } from 'twenty-shared/types'; import { assertUnreachable, isDefined } from 'twenty-shared/utils'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service'; import { MicrosoftAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/microsoft/services/microsoft-api-refresh-tokens.service'; import { @@ -29,7 +29,7 @@ export class ConnectedAccountRefreshTokensService { constructor( private readonly googleAPIRefreshAccessTokenService: GoogleAPIRefreshAccessTokenService, private readonly microsoftAPIRefreshAccessTokenService: MicrosoftAPIRefreshAccessTokenService, - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} async refreshAndSaveTokens( @@ -76,7 +76,8 @@ export class ConnectedAccountRefreshTokensService { ); const connectedAccountRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'connectedAccount', ); 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 cdf1f40a6d..fbcff62d4d 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 @@ -6,7 +6,7 @@ import { In, Repository } from 'typeorm'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { FAVORITE_DELETION_BATCH_SIZE } from 'src/modules/favorite/constants/favorite-deletion-batch-size'; import { type FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity'; @@ -18,7 +18,7 @@ export class FavoriteDeletionService { @InjectRepository(FieldMetadataEntity) private readonly fieldMetadataRepository: Repository, - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} async deleteFavoritesForDeletedRecords( @@ -26,7 +26,8 @@ export class FavoriteDeletionService { workspaceId: string, ): Promise { const favoriteRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'favorite', ); diff --git a/packages/twenty-server/src/modules/match-participant/match-participant.module.ts b/packages/twenty-server/src/modules/match-participant/match-participant.module.ts index 7f0904d7d3..4e3331c67e 100644 --- a/packages/twenty-server/src/modules/match-participant/match-participant.module.ts +++ b/packages/twenty-server/src/modules/match-participant/match-participant.module.ts @@ -1,11 +1,10 @@ import { Module } from '@nestjs/common'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { MatchParticipantService } from 'src/modules/match-participant/match-participant.service'; @Module({ imports: [], - providers: [ScopedWorkspaceContextFactory, MatchParticipantService], + providers: [MatchParticipantService], exports: [MatchParticipantService], }) export class MatchParticipantModule {} 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 781a568503..98c9f310d6 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 @@ -5,7 +5,6 @@ import { isDefined } from 'twenty-shared/utils'; import { Any, In } from 'typeorm'; import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { type CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity'; @@ -22,6 +21,7 @@ type MatchParticipantsForWorkspaceMembersArgs = { workspaceMemberIds: string[]; }; objectMetadataName: ObjectMetadataName; + workspaceId: string; }; type MatchParticipantsForPeopleArgs = { @@ -30,6 +30,7 @@ type MatchParticipantsForPeopleArgs = { personEmails: string[]; }; objectMetadataName: ObjectMetadataName; + workspaceId: string; }; type MatchParticipantsArgs< @@ -47,6 +48,7 @@ type MatchParticipantsArgs< objectMetadataName: ObjectMetadataName; transactionManager?: WorkspaceEntityManager; matchWith: 'workspaceMemberOnly' | 'personOnly' | 'workspaceMemberAndPerson'; + workspaceId: string; }; @Injectable() @@ -58,7 +60,6 @@ export class MatchParticipantService< constructor( private readonly workspaceEventEmitter: WorkspaceEventEmitter, private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, ) {} private async getParticipantRepository( @@ -83,17 +84,12 @@ export class MatchParticipantService< objectMetadataName, transactionManager, matchWith = 'workspaceMemberAndPerson', + workspaceId, }: MatchParticipantsArgs) { if (participants.length === 0) { return; } - const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId; - - if (!isDefined(workspaceId)) { - throw new Error('Workspace ID is required'); - } - const personRepository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspaceId, @@ -209,13 +205,8 @@ export class MatchParticipantService< public async matchParticipantsForWorkspaceMembers({ participantMatching, objectMetadataName, + workspaceId, }: MatchParticipantsForWorkspaceMembersArgs) { - const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId; - - if (!isDefined(workspaceId)) { - throw new Error('Workspace ID is required'); - } - const participantRepository = await this.getParticipantRepository( workspaceId, objectMetadataName, @@ -238,19 +229,15 @@ export class MatchParticipantService< matchWith: 'workspaceMemberOnly', participants: tobeRematchedParticipants as ParticipantWorkspaceEntity[], objectMetadataName, + workspaceId, }); } public async matchParticipantsForPeople({ participantMatching, objectMetadataName, + workspaceId, }: MatchParticipantsForPeopleArgs) { - const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId; - - if (!isDefined(workspaceId)) { - throw new Error('Workspace ID is required'); - } - const participantRepository = await this.getParticipantRepository( workspaceId, objectMetadataName, @@ -293,6 +280,7 @@ export class MatchParticipantService< matchWith: 'personOnly', participants: tobeRematchedParticipants, objectMetadataName, + workspaceId, }); } } 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 98e07e0d2d..12ad56e0af 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 @@ -7,7 +7,7 @@ import { type ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emit import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; import { type BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity'; import { type MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity'; @@ -25,7 +25,7 @@ export type BlocklistItemDeleteMessagesJobData = WorkspaceEventBatch< export class BlocklistItemDeleteMessagesJob { constructor( private readonly threadCleanerService: MessagingMessageCleanerService, - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} @Process(BlocklistItemDeleteMessagesJob.name) @@ -37,7 +37,8 @@ export class BlocklistItemDeleteMessagesJob { ); const blocklistRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'blocklist', ); @@ -67,12 +68,14 @@ export class BlocklistItemDeleteMessagesJob { ); const messageChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannel', ); const messageChannelMessageAssociationRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannelMessageAssociation', ); 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 0940cae351..80bcdc31df 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 @@ -6,7 +6,7 @@ import { type ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emit import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; import { type BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; @@ -25,7 +25,7 @@ export type BlocklistReimportMessagesJobData = WorkspaceEventBatch< }) export class BlocklistReimportMessagesJob { constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly messagingChannelSyncStatusService: MessageChannelSyncStatusService, ) {} @@ -34,7 +34,8 @@ export class BlocklistReimportMessagesJob { const workspaceId = data.workspaceId; const messageChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannel', ); 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 74aa2d6b9c..4ec789c9f6 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 @@ -2,7 +2,7 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { type MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity'; @@ -42,18 +42,20 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { findOneByOrFail: jest.fn(), }; - const mockTwentyORMManager = { - getRepository: jest.fn().mockImplementation((name) => { - if (name === 'messageChannelMessageAssociation') { - return mockMessageChannelMessageAssociationRepository; - } - if (name === 'connectedAccount') { - return mockConnectedAccountRepository; - } - if (name === 'workspaceMember') { - return mockWorkspaceMemberRepository; - } - }), + const mockTwentyORMGlobalManager = { + getRepositoryForWorkspace: jest + .fn() + .mockImplementation((workspaceId, name) => { + if (name === 'messageChannelMessageAssociation') { + return mockMessageChannelMessageAssociationRepository; + } + if (name === 'connectedAccount') { + return mockConnectedAccountRepository; + } + if (name === 'workspaceMember') { + return mockWorkspaceMemberRepository; + } + }), }; beforeEach(async () => { @@ -61,8 +63,8 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { providers: [ ApplyMessagesVisibilityRestrictionsService, { - provide: TwentyORMManager, - useValue: mockTwentyORMManager, + provide: TwentyORMGlobalManager, + useValue: mockTwentyORMGlobalManager, }, ], }).compile(); @@ -91,6 +93,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { const result = await service.applyMessagesVisibilityRestrictions( messages, + 'test-workspace-id', 'user-id', ); @@ -127,6 +130,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { const result = await service.applyMessagesVisibilityRestrictions( messages, + 'test-workspace-id', 'user-id', ); @@ -161,6 +165,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { const result = await service.applyMessagesVisibilityRestrictions( messages, + 'test-workspace-id', 'user-id', ); @@ -196,6 +201,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { const result = await service.applyMessagesVisibilityRestrictions( messages, + 'test-workspace-id', 'user-id', ); @@ -230,6 +236,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { const result = await service.applyMessagesVisibilityRestrictions( messages, + 'test-workspace-id', 'user-id', ); @@ -277,6 +284,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { const result = await service.applyMessagesVisibilityRestrictions( messages, + 'test-workspace-id', 'user-id', ); @@ -335,6 +343,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { const result = await service.applyMessagesVisibilityRestrictions( messages, + 'test-workspace-id', undefined, ); 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 d647440231..8a04faf82b 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 @@ -6,7 +6,7 @@ import { isDefined } from 'twenty-shared/utils'; import { In } from 'typeorm'; import { NotFoundError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { type MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity'; import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; @@ -15,14 +15,18 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta @Injectable() export class ApplyMessagesVisibilityRestrictionsService { - constructor(private readonly twentyORMManager: TwentyORMManager) {} + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} public async applyMessagesVisibilityRestrictions( messages: MessageWorkspaceEntity[], + workspaceId: string, userId?: string, // undefined when request is made with api key ) { const messageChannelMessageAssociationRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannelMessageAssociation', ); @@ -35,12 +39,14 @@ export class ApplyMessagesVisibilityRestrictionsService { }); const connectedAccountRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'connectedAccount', ); const workspaceMemberRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'workspaceMember', ); diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-many.post-query.hook.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-many.post-query.hook.ts index 21a6acff58..294a4ba02f 100644 --- a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-many.post-query.hook.ts +++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-many.post-query.hook.ts @@ -31,8 +31,15 @@ export class MessageFindManyPostQueryHook throw new ForbiddenError('User is required'); } + const workspace = authContext.workspace; + + if (!workspace) { + throw new ForbiddenError('Workspace is required'); + } + await this.applyMessagesVisibilityRestrictionsService.applyMessagesVisibilityRestrictions( payload, + workspace.id, user?.id, ); } diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-one.post-query.hook.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-one.post-query.hook.ts index 380deb6a35..e6cf8fe401 100644 --- a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-one.post-query.hook.ts +++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-find-one.post-query.hook.ts @@ -31,8 +31,15 @@ export class MessageFindOnePostQueryHook throw new ForbiddenError('User is required'); } + const workspace = authContext.workspace; + + if (!workspace) { + throw new ForbiddenError('Workspace is required'); + } + await this.applyMessagesVisibilityRestrictionsService.applyMessagesVisibilityRestrictions( payload, + workspace.id, user?.id, ); } 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 1b555a8ea8..32ad89c0da 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 @@ -3,7 +3,7 @@ import { Scope } from '@nestjs/common'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { isThrottled } from 'src/modules/connected-account/utils/is-throttled'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; import { @@ -30,7 +30,7 @@ export class MessagingMessageListFetchJob { constructor( private readonly messagingMessageListFetchService: MessagingMessageListFetchService, private readonly messagingMonitoringService: MessagingMonitoringService, - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly messageImportErrorHandlerService: MessageImportExceptionHandlerService, private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService, ) {} @@ -46,7 +46,8 @@ export class MessagingMessageListFetchJob { }); const messageChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannel', ); 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 7b0b39f1fe..f04d711f86 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 @@ -3,7 +3,7 @@ import { Scope } from '@nestjs/common'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { isThrottled } from 'src/modules/connected-account/utils/is-throttled'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; import { @@ -26,7 +26,7 @@ export class MessagingMessagesImportJob { private readonly messagingMessagesImportService: MessagingMessagesImportService, private readonly messagingMonitoringService: MessagingMonitoringService, private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService, - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} @Process(MessagingMessagesImportJob.name) @@ -40,7 +40,8 @@ export class MessagingMessagesImportJob { }); const messageChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannel', ); 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 3d88efc94f..8886cb8501 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 @@ -5,7 +5,7 @@ import { In } from 'typeorm'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; import { MessageChannelSyncStage, @@ -24,7 +24,7 @@ export type MessagingOngoingStaleJobData = { export class MessagingOngoingStaleJob { private readonly logger = new Logger(MessagingOngoingStaleJob.name); constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService, ) {} @@ -33,7 +33,8 @@ export class MessagingOngoingStaleJob { const { workspaceId } = data; const messageChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannel', ); 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 bf58083883..f11e006289 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 @@ -4,7 +4,7 @@ import { ConnectedAccountProvider } from 'twenty-shared/types'; import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; @@ -24,7 +24,7 @@ describe('MessagingMessageListFetchService', () => { let messagingGetMessageListService: MessagingGetMessageListService; let messagingAccountAuthenticationService: MessagingAccountAuthenticationService; let messageChannelSyncStatusService: MessageChannelSyncStatusService; - let twentyORMManager: TwentyORMManager; + let twentyORMGlobalManager: TwentyORMGlobalManager; let messagingCursorService: MessagingCursorService; let mockMicrosoftMessageChannel: MessageChannelWorkspaceEntity; @@ -196,19 +196,21 @@ describe('MessagingMessageListFetchService', () => { }, }, { - provide: TwentyORMManager, + provide: TwentyORMGlobalManager, useValue: { - getDatasource: jest.fn().mockResolvedValue({ + getDataSourceForWorkspace: jest.fn().mockResolvedValue({ manager: {}, }), - getRepository: jest.fn().mockImplementation((name) => { - if (name === 'messageChannelMessageAssociation') { - return mockMessageChannelMessageAssociationRepository; - } - if (name === 'messageFolder') { - return mockMessageFolderRepository; - } - }), + getRepositoryForWorkspace: jest + .fn() + .mockImplementation((workspaceId, name) => { + if (name === 'messageChannelMessageAssociation') { + return mockMessageChannelMessageAssociationRepository; + } + if (name === 'messageFolder') { + return mockMessageFolderRepository; + } + }), }, }, { @@ -273,7 +275,9 @@ describe('MessagingMessageListFetchService', () => { module.get( MessageChannelSyncStatusService, ); - twentyORMManager = module.get(TwentyORMManager); + twentyORMGlobalManager = module.get( + TwentyORMGlobalManager, + ); messagingCursorService = module.get( MessagingCursorService, ); @@ -316,9 +320,9 @@ describe('MessagingMessageListFetchService', () => { ], ); - expect(twentyORMManager.getRepository).toHaveBeenCalledWith( - 'messageChannelMessageAssociation', - ); + expect( + twentyORMGlobalManager.getRepositoryForWorkspace, + ).toHaveBeenCalledWith(workspaceId, 'messageChannelMessageAssociation'); expect(messagingCursorService.updateCursor).toHaveBeenCalledWith( { @@ -330,6 +334,7 @@ describe('MessagingMessageListFetchService', () => { }, }, 'new-sync-cursor', + workspaceId, 'inbox-folder-id', ); @@ -375,9 +380,9 @@ describe('MessagingMessageListFetchService', () => { ], ); - expect(twentyORMManager.getRepository).toHaveBeenCalledWith( - 'messageChannelMessageAssociation', - ); + expect( + twentyORMGlobalManager.getRepositoryForWorkspace, + ).toHaveBeenCalledWith(workspaceId, 'messageChannelMessageAssociation'); expect(messagingCursorService.updateCursor).toHaveBeenCalledWith( { @@ -389,6 +394,7 @@ describe('MessagingMessageListFetchService', () => { }, }, 'new-google-history-id', + workspaceId, undefined, ); 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 a04047c6ac..281163c70a 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 @@ -5,7 +5,7 @@ import { ConnectedAccountProvider } from 'twenty-shared/types'; import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { BlocklistRepository } from 'src/modules/blocklist/repositories/blocklist.repository'; import { EmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/services/email-alias-manager.service'; import { ConnectedAccountRefreshTokensService } from 'src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service'; @@ -106,9 +106,9 @@ describe('MessagingMessagesImportService', () => { }, }, { - provide: TwentyORMManager, + provide: TwentyORMGlobalManager, useValue: { - getRepository: jest.fn().mockResolvedValue({ + getRepositoryForWorkspace: jest.fn().mockResolvedValue({ update: jest.fn().mockResolvedValue(undefined), }), }, @@ -219,11 +219,14 @@ describe('MessagingMessagesImportService', () => { connectedAccountRefreshTokensService.refreshAndSaveTokens, ).toHaveBeenCalledWith(mockConnectedAccount, workspaceId); - expect(emailAliasManagerService.refreshHandleAliases).toHaveBeenCalledWith({ - ...mockConnectedAccount, - accessToken: 'new-access-token', - refreshToken: 'new-refresh-token', - }); + expect(emailAliasManagerService.refreshHandleAliases).toHaveBeenCalledWith( + { + ...mockConnectedAccount, + accessToken: 'new-access-token', + refreshToken: 'new-refresh-token', + }, + workspaceId, + ); expect(messagingGetMessagesService.getMessages).toHaveBeenCalledWith( ['message-id-1', 'message-id-2'], { 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 8e7191a259..f58c6376a0 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 @@ -1,24 +1,29 @@ import { Injectable } from '@nestjs/common'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; @Injectable() export class MessagingCursorService { - constructor(private readonly twentyORMManager: TwentyORMManager) {} + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} public async updateCursor( messageChannel: MessageChannelWorkspaceEntity, nextSyncCursor: string, + workspaceId: string, folderId?: string, ) { const messageChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannel', ); const folderRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageFolder', ); 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 75bcae50db..322746622b 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 @@ -5,7 +5,7 @@ import { type TwentyORMException, TwentyORMExceptionCode, } from 'src/engine/twenty-orm/exceptions/twenty-orm.exception'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; import { MessageChannelSyncStatus, @@ -27,7 +27,7 @@ export enum MessageImportSyncStep { @Injectable() export class MessageImportExceptionHandlerService { constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService, private readonly exceptionHandlerService: ExceptionHandlerService, ) {} @@ -149,7 +149,8 @@ export class MessageImportExceptionHandlerService { } const messageChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannel', ); 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 1d0fd81162..92561ff4e0 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 @@ -8,7 +8,7 @@ import { In, MoreThanOrEqual } from 'typeorm'; import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator'; import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; import { type MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity'; import { @@ -43,7 +43,7 @@ export class MessagingMessageListFetchService { @InjectCacheStorage(CacheStorageNamespace.ModuleMessaging) private readonly cacheStorage: CacheStorageService, private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService, - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly messagingGetMessageListService: MessagingGetMessageListService, private readonly messageImportErrorHandlerService: MessageImportExceptionHandlerService, private readonly messagingMessageCleanerService: MessagingMessageCleanerService, @@ -76,7 +76,8 @@ export class MessagingMessageListFetchService { ); const messageChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannel', ); @@ -116,7 +117,10 @@ export class MessagingMessageListFetchService { }, }; - const datasource = await this.twentyORMManager.getDatasource(); + const datasource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace({ + workspaceId, + }); await this.syncMessageFoldersService.syncMessageFolders({ workspaceId, @@ -125,7 +129,8 @@ export class MessagingMessageListFetchService { }); const messageFolderRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageFolder', ); @@ -176,7 +181,8 @@ export class MessagingMessageListFetchService { ); const messageChannelMessageAssociationRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannelMessageAssociation', ); @@ -228,6 +234,7 @@ export class MessagingMessageListFetchService { await this.messagingCursorService.updateCursor( messageChannelWithFreshTokens, nextSyncCursor, + workspaceId, folderId, ); } @@ -236,6 +243,7 @@ export class MessagingMessageListFetchService { ? await this.computeFullSyncMessageChannelMessageAssociationsToDelete( freshMessageChannel, messageExternalIds, + workspaceId, ) : []; @@ -342,7 +350,8 @@ export class MessagingMessageListFetchService { workspaceId: string, ): Promise { const messageFolderRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageFolder', ); @@ -373,9 +382,11 @@ export class MessagingMessageListFetchService { private async computeFullSyncMessageChannelMessageAssociationsToDelete( messageChannel: Pick, messageExternalIds: string[], + workspaceId: string, ) { const messageChannelMessageAssociationRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannelMessageAssociation', ); 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 46bc1bc670..2baca3aca7 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 @@ -5,7 +5,7 @@ import { In } from 'typeorm'; import { v4 } from 'uuid'; import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity'; import { type MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity'; import { type MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity'; @@ -38,7 +38,9 @@ type MessageAccumulator = { export class MessagingMessageService { private readonly logger = new Logger(MessagingMessageService.name); - constructor(private readonly twentyORMManager: TwentyORMManager) {} + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} public async saveMessagesWithinTransaction( messages: MessageWithParticipants[], @@ -50,17 +52,20 @@ export class MessagingMessageService { messageExternalIdsAndIdsMap: Map; }> { const messageChannelMessageAssociationRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannelMessageAssociation', ); const messageRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'message', ); const messageThreadRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageThread', ); 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 d72c706a02..62f02069ed 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 @@ -6,7 +6,7 @@ import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decora import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { BlocklistRepository } from 'src/modules/blocklist/repositories/blocklist.repository'; import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity'; import { EmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/services/email-alias-manager.service'; @@ -43,7 +43,7 @@ export class MessagingMessagesImportService { @InjectObjectMetadataRepository(BlocklistWorkspaceEntity) private readonly blocklistRepository: BlocklistRepository, private readonly emailAliasManagerService: EmailAliasManagerService, - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly messagingGetMessagesService: MessagingGetMessagesService, private readonly messageImportErrorHandlerService: MessageImportExceptionHandlerService, private readonly messagingAccountAuthenticationService: MessagingAccountAuthenticationService, @@ -93,6 +93,7 @@ export class MessagingMessagesImportService { await this.emailAliasManagerService.refreshHandleAliases( connectedAccountWithFreshTokens, + workspaceId, ); messageIdsToFetch = await this.cacheStorage.setPop( @@ -170,7 +171,8 @@ export class MessagingMessagesImportService { } const messageChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannel', ); 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 5966bbb01a..518eb138b8 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 @@ -4,7 +4,7 @@ import { isDefined } from 'twenty-shared/utils'; import { In } from 'typeorm'; import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessageFolderPendingSyncAction, @@ -19,7 +19,7 @@ export class MessagingProcessFolderActionsService { ); constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly messagingDeleteFolderMessagesService: MessagingDeleteFolderMessagesService, ) {} @@ -86,12 +86,16 @@ export class MessagingProcessFolderActionsService { } if (processedFolderIds.length > 0 || folderIdsToDelete.length > 0) { - const workspaceDataSource = await this.twentyORMManager.getDatasource(); + const workspaceDataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace({ + workspaceId, + }); await workspaceDataSource?.transaction( async (transactionManager: WorkspaceEntityManager) => { const messageFolderRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageFolder', ); 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 529e46a2a9..4a7503d0ba 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 @@ -3,7 +3,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { isDefined } from 'twenty-shared/utils'; import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { MessageChannelPendingGroupEmailsAction, MessageChannelWorkspaceEntity, @@ -18,7 +18,7 @@ export class MessagingProcessGroupEmailActionsService { ); constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly messagingDeleteGroupEmailMessagesService: MessagingDeleteGroupEmailMessagesService, ) {} @@ -28,7 +28,8 @@ export class MessagingProcessGroupEmailActionsService { pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction, ): Promise { const messageChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannel', ); @@ -59,13 +60,17 @@ export class MessagingProcessGroupEmailActionsService { `WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Processing group email action: ${pendingGroupEmailsAction}`, ); - const workspaceDataSource = await this.twentyORMManager.getDatasource(); + const workspaceDataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace({ + workspaceId, + }); await workspaceDataSource?.transaction( async (transactionManager: WorkspaceEntityManager) => { try { const messageChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannel', ); @@ -120,6 +125,7 @@ export class MessagingProcessGroupEmailActionsService { ); await this.resetCursors({ + workspaceId, messageChannelId, transactionManager, }); @@ -135,6 +141,7 @@ export class MessagingProcessGroupEmailActionsService { transactionManager: WorkspaceEntityManager, ): Promise { await this.resetCursors({ + workspaceId, messageChannelId, transactionManager, }); @@ -145,14 +152,17 @@ export class MessagingProcessGroupEmailActionsService { } private async resetCursors({ + workspaceId, messageChannelId, transactionManager, }: { + workspaceId: string; messageChannelId: string; transactionManager: WorkspaceEntityManager; }) { const messageChannelRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageChannel', ); @@ -165,7 +175,8 @@ export class MessagingProcessGroupEmailActionsService { ); const messageFolderRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageFolder', ); 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 5112e34238..71a74f9daf 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 @@ -7,7 +7,7 @@ import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queu import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-queue-token.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { CreateCompanyAndContactJob } from 'src/modules/contact-creation-manager/jobs/create-company-and-contact.job'; import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum'; @@ -132,9 +132,11 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => { }, }, { - provide: TwentyORMManager, + provide: TwentyORMGlobalManager, useValue: { - getDatasource: jest.fn().mockResolvedValue(datasourceInstance), + getDataSourceForWorkspace: jest + .fn() + .mockResolvedValue(datasourceInstance), }, }, ], 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 e8d01e502e..1b71c9868c 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 @@ -6,7 +6,7 @@ import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decora import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { CreateCompanyAndContactJob, @@ -32,7 +32,7 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService { private readonly messageQueueService: MessageQueueService, private readonly messageService: MessagingMessageService, private readonly messageParticipantService: MessagingMessageParticipantService, - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, ) {} async saveMessagesAndEnqueueContactCreation( @@ -43,7 +43,10 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService { ) { const handleAliases = connectedAccount.handleAliases?.split(',') || []; - const workspaceDataSource = await this.twentyORMManager.getDatasource(); + const workspaceDataSource = + await this.twentyORMGlobalManager.getDataSourceForWorkspace({ + workspaceId, + }); const participantsWithMessageId = await workspaceDataSource?.transaction( async (transactionManager: WorkspaceEntityManager) => { @@ -99,6 +102,7 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService { await this.messageParticipantService.saveMessageParticipants( participantsWithMessageId, + workspaceId, transactionManager, ); diff --git a/packages/twenty-server/src/modules/messaging/message-participant-manager/jobs/message-participant-match-participant.job.ts b/packages/twenty-server/src/modules/messaging/message-participant-manager/jobs/message-participant-match-participant.job.ts index e1e606330e..73558a42d7 100644 --- a/packages/twenty-server/src/modules/messaging/message-participant-manager/jobs/message-participant-match-participant.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-participant-manager/jobs/message-participant-match-participant.job.ts @@ -26,7 +26,7 @@ export class MessageParticipantMatchParticipantJob { @Process(MessageParticipantMatchParticipantJob.name) async handle(data: MessageParticipantMatchParticipantJobData): Promise { - const { participantMatching } = data; + const { participantMatching, workspaceId } = data; if ( participantMatching.personIds.length > 0 || @@ -35,6 +35,7 @@ export class MessageParticipantMatchParticipantJob { await this.matchParticipantService.matchParticipantsForPeople({ participantMatching, objectMetadataName: 'messageParticipant', + workspaceId, }); } @@ -42,6 +43,7 @@ export class MessageParticipantMatchParticipantJob { await this.matchParticipantService.matchParticipantsForWorkspaceMembers({ participantMatching, objectMetadataName: 'messageParticipant', + workspaceId, }); } } 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 43e7809b34..684e550761 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 @@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common'; import { In } from 'typeorm'; import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { MatchParticipantService } from 'src/modules/match-participant/match-participant.service'; import { type MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity'; import { type ParticipantWithMessageId } from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-message.type'; @@ -11,16 +11,18 @@ import { type ParticipantWithMessageId } from 'src/modules/messaging/message-imp @Injectable() export class MessagingMessageParticipantService { constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly matchParticipantService: MatchParticipantService, ) {} public async saveMessageParticipants( participants: ParticipantWithMessageId[], + workspaceId: string, transactionManager?: WorkspaceEntityManager, ): Promise { const messageParticipantRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'messageParticipant', ); @@ -66,6 +68,7 @@ export class MessagingMessageParticipantService { objectMetadataName: 'messageParticipant', transactionManager, matchWith: 'workspaceMemberAndPerson', + workspaceId, }); } } 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 d4ba75119e..3f23693ece 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 @@ -6,7 +6,7 @@ import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runne import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkflowVersionStatus, type WorkflowVersionWorkspaceEntity, @@ -22,7 +22,7 @@ export class WorkflowCreateManyPostQueryHook implements WorkspacePostQueryHookInstance { constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly recordPositionService: RecordPositionService, ) {} @@ -36,7 +36,8 @@ export class WorkflowCreateManyPostQueryHook assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError); const workflowVersionRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspace.id, 'workflowVersion', ); 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 8f85405f7f..ee2ed9c7b0 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 @@ -6,7 +6,7 @@ import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runne import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkflowVersionStatus, type WorkflowVersionWorkspaceEntity, @@ -22,7 +22,7 @@ export class WorkflowCreateOnePostQueryHook implements WorkspacePostQueryHookInstance { constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly recordPositionService: RecordPositionService, ) {} @@ -38,7 +38,8 @@ export class WorkflowCreateOnePostQueryHook const workflow = payload[0]; const workflowVersionRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspace.id, 'workflowVersion', ); diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts index 134a29e705..c13a4ed3ba 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts @@ -8,7 +8,6 @@ import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-t import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { type ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity'; import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { WorkflowVersionStepOperationsWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service'; @@ -105,10 +104,6 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { provide: AiAgentRoleService, useValue: aiAgentRoleService, }, - { - provide: ScopedWorkspaceContextFactory, - useValue: {}, - }, ], }).compile(); diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent-action.module.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent-action.module.ts index bb004e3edb..98047c8c71 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent-action.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent-action.module.ts @@ -6,7 +6,6 @@ import { AiAgentExecutionModule } from 'src/engine/metadata-modules/ai/ai-agent- import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; import { AiBillingModule } from 'src/engine/metadata-modules/ai/ai-billing/ai-billing.module'; import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkflowExecutionContextService } from 'src/modules/workflow/workflow-executor/services/workflow-execution-context.service'; import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.module'; @@ -21,11 +20,7 @@ import { AiAgentWorkflowAction } from './ai-agent.workflow-action'; UserWorkspaceModule, UserRoleModule, ], - providers: [ - ScopedWorkspaceContextFactory, - WorkflowExecutionContextService, - AiAgentWorkflowAction, - ], + providers: [WorkflowExecutionContextService, AiAgentWorkflowAction], exports: [AiAgentWorkflowAction], }) export class AiAgentActionModule {} diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/code/code-action.module.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/code/code-action.module.ts index c7994cbd30..d730d0600e 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/code/code-action.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/code/code-action.module.ts @@ -1,12 +1,11 @@ import { Module } from '@nestjs/common'; import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { CodeWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/code/code.workflow-action'; @Module({ imports: [ServerlessFunctionModule], - providers: [ScopedWorkspaceContextFactory, CodeWorkflowAction], + providers: [CodeWorkflowAction], exports: [CodeWorkflowAction], }) export class CodeActionModule {} diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/code/code.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/code/code.workflow-action.ts index dc82e12854..ecdc8feac4 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/code/code.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/code/code.workflow-action.ts @@ -5,7 +5,6 @@ import { resolveInput } from 'twenty-shared/utils'; import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface'; import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkflowStepExecutorException, WorkflowStepExecutorExceptionCode, @@ -20,13 +19,13 @@ import { type WorkflowCodeActionInput } from 'src/modules/workflow/workflow-exec export class CodeWorkflowAction implements WorkflowAction { constructor( private readonly serverlessFunctionService: ServerlessFunctionService, - private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, ) {} async execute({ currentStepId, steps, context, + runInfo, }: WorkflowActionInput): Promise { const step = findStepOrThrow({ stepId: currentStepId, @@ -46,14 +45,7 @@ export class CodeWorkflowAction implements WorkflowAction { ) as WorkflowCodeActionInput; try { - const { workspaceId } = this.scopedWorkspaceContextFactory.create(); - - if (!workspaceId) { - throw new WorkflowStepExecutorException( - 'Scoped workspace not found', - WorkflowStepExecutorExceptionCode.SCOPED_WORKSPACE_NOT_FOUND, - ); - } + const { workspaceId } = runInfo; const result = await this.serverlessFunctionService.executeOneServerlessFunction( diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts index 767961bebc..42850d915a 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts @@ -10,7 +10,6 @@ import { RecordCrudExceptionCode, } from 'src/engine/core-modules/record-crud/exceptions/record-crud.exception'; import { CreateRecordService } from 'src/engine/core-modules/record-crud/services/create-record.service'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkflowExecutionContextService } from 'src/modules/workflow/workflow-executor/services/workflow-execution-context.service'; import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input'; import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type'; @@ -22,7 +21,6 @@ import { type WorkflowCreateRecordActionInput } from 'src/modules/workflow/workf export class CreateRecordWorkflowAction implements WorkflowAction { constructor( private readonly createRecordService: CreateRecordService, - private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, private readonly workflowExecutionContextService: WorkflowExecutionContextService, ) {} @@ -37,14 +35,7 @@ export class CreateRecordWorkflowAction implements WorkflowAction { stepId: currentStepId, }); - const { workspaceId } = this.scopedWorkspaceContextFactory.create(); - - if (!workspaceId) { - throw new RecordCrudException( - 'Failed to create: Workspace ID is required', - RecordCrudExceptionCode.INVALID_REQUEST, - ); - } + const { workspaceId } = runInfo; const workflowActionInput = resolveInput( step.settings.input, diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action.ts index 3d5d1b2d28..58b12d7ab7 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action.ts @@ -9,7 +9,6 @@ import { RecordCrudExceptionCode, } from 'src/engine/core-modules/record-crud/exceptions/record-crud.exception'; import { DeleteRecordService } from 'src/engine/core-modules/record-crud/services/delete-record.service'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkflowStepExecutorException, WorkflowStepExecutorExceptionCode, @@ -25,7 +24,6 @@ import { type WorkflowDeleteRecordActionInput } from 'src/modules/workflow/workf export class DeleteRecordWorkflowAction implements WorkflowAction { constructor( private readonly deleteRecordService: DeleteRecordService, - private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, private readonly workflowExecutionContextService: WorkflowExecutionContextService, ) {} @@ -63,14 +61,7 @@ export class DeleteRecordWorkflowAction implements WorkflowAction { ); } - const { workspaceId } = this.scopedWorkspaceContextFactory.create(); - - if (!workspaceId) { - throw new RecordCrudException( - 'Failed to delete: Workspace ID is required', - RecordCrudExceptionCode.INVALID_REQUEST, - ); - } + const { workspaceId } = runInfo; const executionContext = await this.workflowExecutionContextService.getExecutionContext(runInfo); diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts index 25ffa8a089..9adf54170a 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts @@ -17,7 +17,6 @@ import { RecordCrudExceptionCode, } from 'src/engine/core-modules/record-crud/exceptions/record-crud.exception'; import { FindRecordsService } from 'src/engine/core-modules/record-crud/services/find-records.service'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { WorkflowStepExecutorException, @@ -34,7 +33,6 @@ import { type WorkflowFindRecordsActionInput } from 'src/modules/workflow/workfl export class FindRecordsWorkflowAction implements WorkflowAction { constructor( private readonly findRecordsService: FindRecordsService, - private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, private readonly workflowExecutionContextService: WorkflowExecutionContextService, private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService, ) {} @@ -62,14 +60,7 @@ export class FindRecordsWorkflowAction implements WorkflowAction { context, ) as WorkflowFindRecordsActionInput; - const { workspaceId } = this.scopedWorkspaceContextFactory.create(); - - if (!workspaceId) { - throw new RecordCrudException( - 'Failed to read: Workspace ID is required', - RecordCrudExceptionCode.INVALID_REQUEST, - ); - } + const { workspaceId } = runInfo; const executionContext = await this.workflowExecutionContextService.getExecutionContext(runInfo); diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module.ts index 92596b67bb..1c58cc2e60 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module.ts @@ -3,7 +3,6 @@ import { Module } from '@nestjs/common'; import { RecordCrudModule } from 'src/engine/core-modules/record-crud/record-crud.module'; import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module'; import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; import { WorkflowExecutionContextService } from 'src/modules/workflow/workflow-executor/services/workflow-execution-context.service'; import { CreateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action'; @@ -22,7 +21,6 @@ import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow WorkflowCommonModule, ], providers: [ - ScopedWorkspaceContextFactory, WorkflowExecutionContextService, CreateRecordWorkflowAction, UpsertRecordWorkflowAction, diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts index 62ec017610..de4b7d2489 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts @@ -9,7 +9,6 @@ import { RecordCrudExceptionCode, } from 'src/engine/core-modules/record-crud/exceptions/record-crud.exception'; import { UpdateRecordService } from 'src/engine/core-modules/record-crud/services/update-record.service'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkflowStepExecutorException, WorkflowStepExecutorExceptionCode, @@ -25,7 +24,6 @@ import { type WorkflowUpdateRecordActionInput } from 'src/modules/workflow/workf export class UpdateRecordWorkflowAction implements WorkflowAction { constructor( private readonly updateRecordService: UpdateRecordService, - private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, private readonly workflowExecutionContextService: WorkflowExecutionContextService, ) {} @@ -63,14 +61,7 @@ export class UpdateRecordWorkflowAction implements WorkflowAction { ); } - const { workspaceId } = this.scopedWorkspaceContextFactory.create(); - - if (!workspaceId) { - throw new RecordCrudException( - 'Failed to update: Workspace ID is required', - RecordCrudExceptionCode.INVALID_REQUEST, - ); - } + const { workspaceId } = runInfo; const executionContext = await this.workflowExecutionContextService.getExecutionContext(runInfo); diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/upsert-record.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/upsert-record.workflow-action.ts index 7ca7d47568..377c64891a 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/upsert-record.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/upsert-record.workflow-action.ts @@ -9,7 +9,6 @@ import { RecordCrudExceptionCode, } from 'src/engine/core-modules/record-crud/exceptions/record-crud.exception'; import { UpsertRecordService } from 'src/engine/core-modules/record-crud/services/upsert-record.service'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkflowStepExecutorException, WorkflowStepExecutorExceptionCode, @@ -25,7 +24,6 @@ import { WorkflowUpsertRecordActionInput } from 'src/modules/workflow/workflow-e export class UpsertRecordWorkflowAction implements WorkflowAction { constructor( private readonly upsertRecordService: UpsertRecordService, - private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, private readonly workflowExecutionContextService: WorkflowExecutionContextService, ) {} @@ -59,14 +57,7 @@ export class UpsertRecordWorkflowAction implements WorkflowAction { ); } - const { workspaceId } = this.scopedWorkspaceContextFactory.create(); - - if (!workspaceId) { - throw new RecordCrudException( - 'Failed to update: Workspace ID is required', - RecordCrudExceptionCode.INVALID_REQUEST, - ); - } + const { workspaceId } = runInfo; const executionContext = await this.workflowExecutionContextService.getExecutionContext(runInfo); diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/tool-executor-workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/tool-executor-workflow-action.ts index 34df3efb74..094a5428d7 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/tool-executor-workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/tool-executor-workflow-action.ts @@ -30,6 +30,7 @@ export class ToolExecutorWorkflowAction implements WorkflowAction { currentStepId, steps, context, + runInfo, }: WorkflowActionInput): Promise { const step = steps.find((step) => step.id === currentStepId); @@ -45,7 +46,7 @@ export class ToolExecutorWorkflowAction implements WorkflowAction { const toolInput = resolveInput(step.settings.input, context) as ToolInput; - const toolOutput = await tool.execute(toolInput); + const toolOutput = await tool.execute(toolInput, runInfo.workspaceId); return { result: toolOutput.result as object, diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts index b658f96fc4..08bbe99bdb 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts @@ -3,7 +3,6 @@ import { Module } from '@nestjs/common'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { ToolModule } from 'src/engine/core-modules/tool/tool.module'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; import { WorkflowActionFactory } from 'src/modules/workflow/workflow-executor/factories/workflow-action.factory'; import { AiAgentActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent-action.module'; @@ -36,7 +35,6 @@ import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow ], providers: [ WorkflowExecutorWorkspaceService, - ScopedWorkspaceContextFactory, WorkflowActionFactory, ToolExecutorWorkflowAction, ], diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.module.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.module.ts index 94be0de8b4..e5d3be5f8c 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.module.ts @@ -8,7 +8,6 @@ import { RecordPositionModule } from 'src/engine/core-modules/record-position/re import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; import { DeleteWorkflowRunsCommand } from 'src/modules/workflow/workflow-runner/workflow-run/command/delete-workflow-runs.command'; import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service'; @@ -25,11 +24,7 @@ import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runne MetricsModule, DataSourceModule, ], - providers: [ - WorkflowRunWorkspaceService, - ScopedWorkspaceContextFactory, - DeleteWorkflowRunsCommand, - ], + providers: [WorkflowRunWorkspaceService, DeleteWorkflowRunsCommand], exports: [WorkflowRunWorkspaceService, DeleteWorkflowRunsCommand], }) export class WorkflowRunModule {} 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 fac0da12ef..c6e9d67be5 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 @@ -10,7 +10,6 @@ import { WithLock } from 'src/engine/core-modules/cache-lock/with-lock.decorator import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type'; import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkflowRunStatus, @@ -30,7 +29,6 @@ export class WorkflowRunWorkspaceService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService, - private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, private readonly recordPositionService: RecordPositionService, private readonly metricsService: MetricsService, ) {} @@ -42,6 +40,7 @@ export class WorkflowRunWorkspaceService { status, triggerPayload, error, + workspaceId, }: { workflowVersionId: string; createdBy: ActorMetadata; @@ -52,17 +51,8 @@ export class WorkflowRunWorkspaceService { triggerPayload: object; workflowRunId?: string; error?: string; + workspaceId: string; }) { - const workspaceId = - this.scopedWorkspaceContextFactory.create()?.workspaceId; - - if (!workspaceId) { - throw new WorkflowRunException( - 'Workspace id is invalid', - WorkflowRunExceptionCode.WORKFLOW_RUN_INVALID, - ); - } - const workflowRunRepository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( workspaceId, diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workspace-services/workflow-runner.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workspace-services/workflow-runner.workspace-service.ts index cd537549bb..c3ee4e023f 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workspace-services/workflow-runner.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workspace-services/workflow-runner.workspace-service.ts @@ -83,6 +83,7 @@ export class WorkflowRunnerWorkspaceService { if (isHardThrottled) { return this.createFailedWorkflowRun({ + workspaceId, workflowVersionId, initialWorkflowRunId, source, @@ -258,11 +259,13 @@ export class WorkflowRunnerWorkspaceService { } private async createFailedWorkflowRun({ + workspaceId, workflowVersionId, initialWorkflowRunId, source, payload, }: { + workspaceId: string; workflowVersionId: string; initialWorkflowRunId?: string; source: ActorMetadata; @@ -276,6 +279,7 @@ export class WorkflowRunnerWorkspaceService { status: WorkflowRunStatus.FAILED, triggerPayload: payload, error: 'Throttle limit reached', + workspaceId, }); return { workflowRunId }; @@ -301,6 +305,7 @@ export class WorkflowRunnerWorkspaceService { createdBy: source, status: WorkflowRunStatus.ENQUEUED, triggerPayload: payload, + workspaceId, }); await this.messageQueueService.add( @@ -334,6 +339,7 @@ export class WorkflowRunnerWorkspaceService { createdBy: source, status: WorkflowRunStatus.NOT_STARTED, triggerPayload: payload, + workspaceId, }); await this.workflowThrottlingWorkspaceService.increaseWorkflowRunNotStartedCount( diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts index b1f2c12ccf..e4be08321a 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts @@ -97,9 +97,11 @@ export class WorkflowToolWorkspaceService { createUpdateWorkflowVersionPositionsTool(this.deps, context); const activateWorkflowVersion = createActivateWorkflowVersionTool( this.deps, + context, ); const deactivateWorkflowVersion = createDeactivateWorkflowVersionTool( this.deps, + context, ); const computeStepOutputSchema = createComputeStepOutputSchemaTool( this.deps, diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/activate-workflow-version.tool.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/activate-workflow-version.tool.ts index fcf8221db9..d976cee529 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/activate-workflow-version.tool.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/activate-workflow-version.tool.ts @@ -1,6 +1,9 @@ import { z } from 'zod'; -import { type WorkflowToolDependencies } from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type'; +import { + type WorkflowToolContext, + type WorkflowToolDependencies, +} from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type'; const activateWorkflowVersionSchema = z.object({ workflowVersionId: z @@ -14,6 +17,7 @@ type ActivateWorkflowVersionInput = z.infer< export const createActivateWorkflowVersionTool = ( deps: Pick, + context: WorkflowToolContext, ) => ({ name: 'activate_workflow_version' as const, description: @@ -23,6 +27,7 @@ export const createActivateWorkflowVersionTool = ( try { return await deps.workflowTriggerService.activateWorkflowVersion( parameters.workflowVersionId, + context.workspaceId, ); } catch (error) { return { 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 062c32a15c..cc2a846f36 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 @@ -155,6 +155,7 @@ This is the most efficient way for AI to create workflows as it handles all the if (parameters.activate) { await deps.workflowTriggerService.activateWorkflowVersion( workflowVersionId, + context.workspaceId, ); await updateWorkflowStatus({ diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/deactivate-workflow-version.tool.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/deactivate-workflow-version.tool.ts index adb3bf09d4..d129811724 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/deactivate-workflow-version.tool.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/deactivate-workflow-version.tool.ts @@ -1,6 +1,9 @@ import { z } from 'zod'; -import { type WorkflowToolDependencies } from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type'; +import { + type WorkflowToolContext, + type WorkflowToolDependencies, +} from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type'; const deactivateWorkflowVersionSchema = z.object({ workflowVersionId: z @@ -14,6 +17,7 @@ type DeactivateWorkflowVersionInput = z.infer< export const createDeactivateWorkflowVersionTool = ( deps: Pick, + context: WorkflowToolContext, ) => ({ name: 'deactivate_workflow_version' as const, description: @@ -23,6 +27,7 @@ export const createDeactivateWorkflowVersionTool = ( try { return await deps.workflowTriggerService.deactivateWorkflowVersion( parameters.workflowVersionId, + context.workspaceId, ); } 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 5f950309d7..c7e2493483 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 @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { type AutomatedTriggerType, type WorkflowAutomatedTriggerWorkspaceEntity, @@ -9,19 +9,24 @@ import { type AutomatedTriggerSettings } from 'src/modules/workflow/workflow-tri @Injectable() export class AutomatedTriggerWorkspaceService { - constructor(private readonly twentyORMManager: TwentyORMManager) {} + constructor( + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} async addAutomatedTrigger({ workflowId, type, settings, + workspaceId, }: { workflowId: string; type: AutomatedTriggerType; settings: AutomatedTriggerSettings; + workspaceId: string; }) { const workflowAutomatedTriggerRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'workflowAutomatedTrigger', ); @@ -32,9 +37,16 @@ export class AutomatedTriggerWorkspaceService { }); } - async deleteAutomatedTrigger({ workflowId }: { workflowId: string }) { + async deleteAutomatedTrigger({ + workflowId, + workspaceId, + }: { + workflowId: string; + workspaceId: string; + }) { const workflowAutomatedTriggerRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, 'workflowAutomatedTrigger', ); diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.module.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.module.ts index 1bbafffaf7..095d6f9fe1 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/workflow-trigger.module.ts @@ -3,7 +3,6 @@ import { Module } from '@nestjs/common'; import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; import { WorkflowRunnerModule } from 'src/modules/workflow/workflow-runner/workflow-runner.module'; import { AutomatedTriggerModule } from 'src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.module'; @@ -17,11 +16,7 @@ import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-t AutomatedTriggerModule, NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity]), ], - providers: [ - WorkflowTriggerWorkspaceService, - ScopedWorkspaceContextFactory, - WorkflowTriggerJob, - ], + providers: [WorkflowTriggerWorkspaceService, WorkflowTriggerJob], exports: [WorkflowTriggerWorkspaceService], }) export class WorkflowTriggerModule {} 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 edd9ff28a8..f2a132caba 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 @@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common'; import { msg } from '@lingui/core/macro'; import { type ActorMetadata } from 'twenty-shared/types'; -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; @@ -34,43 +33,31 @@ export class WorkflowTriggerWorkspaceService { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService, - private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, private readonly workflowRunnerWorkspaceService: WorkflowRunnerWorkspaceService, private readonly automatedTriggerWorkspaceService: AutomatedTriggerWorkspaceService, private readonly workspaceEventEmitter: WorkspaceEventEmitter, ) {} - private getWorkspaceId() { - const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId; - - if (!workspaceId) { - throw new WorkflowTriggerException( - 'No workspace id found', - WorkflowTriggerExceptionCode.INTERNAL_ERROR, - ); - } - - return workspaceId; - } - async runWorkflowVersion({ workflowVersionId, payload, createdBy, workflowRunId, + workspaceId, }: { workflowVersionId: string; payload: object; createdBy: ActorMetadata; workflowRunId?: string; + workspaceId: string; }) { await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({ workflowVersionId, - workspaceId: this.getWorkspaceId(), + workspaceId, }); return this.workflowRunnerWorkspaceService.run({ - workspaceId: this.getWorkspaceId(), + workspaceId, workflowRunId, workflowVersionId, payload, @@ -78,11 +65,13 @@ export class WorkflowTriggerWorkspaceService { }); } - async activateWorkflowVersion(workflowVersionId: string) { - const workspaceId = this.getWorkspaceId(); + async activateWorkflowVersion( + workflowVersionId: string, + workspaceId: string, + ) { const workflowVersionRepository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( - this.getWorkspaceId(), + workspaceId, 'workflowVersion', { shouldBypassPermissionChecks: true }, // settings permissions are checked at resolver-level ); @@ -121,15 +110,19 @@ export class WorkflowTriggerWorkspaceService { workflowVersion, workflowRepository, workflowVersionRepository, + workspaceId, ); return true; } - async deactivateWorkflowVersion(workflowVersionId: string) { + async deactivateWorkflowVersion( + workflowVersionId: string, + workspaceId: string, + ) { const workflowVersionRepository = await this.twentyORMGlobalManager.getRepositoryForWorkspace( - this.getWorkspaceId(), + workspaceId, 'workflowVersion', { shouldBypassPermissionChecks: true }, ); @@ -137,14 +130,15 @@ export class WorkflowTriggerWorkspaceService { await this.performDeactivationSteps( workflowVersionId, workflowVersionRepository, + workspaceId, ); return true; } - async stopWorkflowRun(workflowRunId: string) { + async stopWorkflowRun(workflowRunId: string, workspaceId: string) { return this.workflowRunnerWorkspaceService.stopWorkflowRun( - this.getWorkspaceId(), + workspaceId, workflowRunId, ); } @@ -154,6 +148,7 @@ export class WorkflowTriggerWorkspaceService { workflowVersion: WorkflowVersionWorkspaceEntity, workflowRepository: WorkspaceRepository, workflowVersionRepository: WorkspaceRepository, + workspaceId: string, ) { if ( workflow.lastPublishedVersionId && @@ -162,6 +157,7 @@ export class WorkflowTriggerWorkspaceService { await this.performDeactivationSteps( workflow.lastPublishedVersionId, workflowVersionRepository, + workspaceId, ); } @@ -175,14 +171,16 @@ export class WorkflowTriggerWorkspaceService { await this.setActiveVersionStatus( workflowVersion, workflowVersionRepository, + workspaceId, ); - await this.enableTrigger(workflowVersion); + await this.enableTrigger(workflowVersion, workspaceId); } private async performDeactivationSteps( workflowVersionId: string, workflowVersionRepository: WorkspaceRepository, + workspaceId: string, ) { const workflowVersionNullable = await workflowVersionRepository.findOne({ where: { id: workflowVersionId }, @@ -200,14 +198,16 @@ export class WorkflowTriggerWorkspaceService { await this.setDeactivatedVersionStatus( workflowVersion, workflowVersionRepository, + workspaceId, ); - await this.disableTrigger(workflowVersion); + await this.disableTrigger(workflowVersion, workspaceId); } private async setActiveVersionStatus( workflowVersion: WorkflowVersionWorkspaceEntity, workflowVersionRepository: WorkspaceRepository, + workspaceId: string, ) { const activeWorkflowVersions = await workflowVersionRepository.find({ where: { @@ -234,13 +234,14 @@ export class WorkflowTriggerWorkspaceService { await this.emitStatusUpdateEvents( workflowVersion, WorkflowVersionStatus.ACTIVE, - this.getWorkspaceId(), + workspaceId, ); } private async setDeactivatedVersionStatus( workflowVersion: WorkflowVersionWorkspaceEntity, workflowVersionRepository: WorkspaceRepository, + workspaceId: string, ) { if (workflowVersion.status !== WorkflowVersionStatus.ACTIVE) { throw new WorkflowTriggerException( @@ -260,7 +261,7 @@ export class WorkflowTriggerWorkspaceService { await this.emitStatusUpdateEvents( workflowVersion, WorkflowVersionStatus.DEACTIVATED, - this.getWorkspaceId(), + workspaceId, ); } @@ -287,7 +288,10 @@ export class WorkflowTriggerWorkspaceService { ); } - private async enableTrigger(workflowVersion: WorkflowVersionWorkspaceEntity) { + private async enableTrigger( + workflowVersion: WorkflowVersionWorkspaceEntity, + workspaceId: string, + ) { assertWorkflowVersionTriggerIsDefined(workflowVersion); switch (workflowVersion.trigger.type) { @@ -302,6 +306,7 @@ export class WorkflowTriggerWorkspaceService { workflowId: workflowVersion.workflowId, type: AutomatedTriggerType.DATABASE_EVENT, settings, + workspaceId, }); return; @@ -313,6 +318,7 @@ export class WorkflowTriggerWorkspaceService { workflowId: workflowVersion.workflowId, type: AutomatedTriggerType.CRON, settings: { pattern }, + workspaceId, }); return; @@ -325,6 +331,7 @@ export class WorkflowTriggerWorkspaceService { private async disableTrigger( workflowVersion: WorkflowVersionWorkspaceEntity, + workspaceId: string, ) { assertWorkflowVersionTriggerIsDefined(workflowVersion); @@ -333,6 +340,7 @@ export class WorkflowTriggerWorkspaceService { case WorkflowTriggerType.CRON: await this.automatedTriggerWorkspaceService.deleteAutomatedTrigger({ workflowId: workflowVersion.workflowId, + workspaceId, }); return; 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 3afd63a381..528b343080 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 @@ -15,7 +15,7 @@ import { PermissionsException, PermissionsExceptionCode, } from 'src/engine/metadata-modules/permissions/permissions.exception'; -import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkspaceMemberPreQueryHookService } from 'src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @@ -27,7 +27,7 @@ export class WorkspaceMemberDeleteOnePostQueryHook implements WorkspacePostQueryHookInstance { constructor( - private readonly twentyORMManager: TwentyORMManager, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, @InjectRepository(UserWorkspaceEntity) private readonly userWorkspaceRepository: Repository, private readonly workspaceMemberPreQueryHookService: WorkspaceMemberPreQueryHookService, @@ -61,7 +61,8 @@ export class WorkspaceMemberDeleteOnePostQueryHook ); const workspaceMemberRepository = - await this.twentyORMManager.getRepository( + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspace.id, 'workspaceMember', );