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
This commit is contained in:
Weiko
2025-12-08 17:05:00 +01:00
committed by GitHub
parent c2d3fbcd21
commit 859004f4fc
101 changed files with 647 additions and 597 deletions
@@ -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()
@@ -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<void> {
const workspaceMemberRepository =
await this.twentyORMManager.getRepository<WorkspaceMemberWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
data.workspaceId,
'workspaceMember',
);
@@ -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,
},
@@ -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,
});
@@ -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<TimelineCalendarEventsWithTotalDTO> {
const offset = (page - 1) * pageSize;
const calendarEventRepository =
await this.twentyORMManager.getRepository<CalendarEventWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarEventWorkspaceEntity>(
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<TimelineCalendarEventsWithTotalDTO> {
const personRepository =
await this.twentyORMManager.getRepository<PersonWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<PersonWorkspaceEntity>(
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<TimelineCalendarEventsWithTotalDTO> {
const opportunityRepository =
await this.twentyORMManager.getRepository<OpportunityWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<OpportunityWorkspaceEntity>(
workspaceId,
'opportunity',
);
@@ -238,6 +250,7 @@ export class TimelineCalendarEventService {
const calendarEvents = await this.getCalendarEventsFromCompanyId({
currentWorkspaceMemberId,
companyId: opportunity.companyId,
workspaceId,
page,
pageSize,
});
@@ -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';
@@ -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<TimelineThreadsWithTotalDTO> {
@@ -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<TimelineThreadsWithTotalDTO> {
const personRepository =
await this.twentyORMManager.getRepository<PersonWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<PersonWorkspaceEntity>(
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<TimelineThreadsWithTotalDTO> {
const opportunityRepository =
await this.twentyORMManager.getRepository<OpportunityWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<OpportunityWorkspaceEntity>(
workspaceId,
'opportunity',
);
@@ -130,6 +139,7 @@ export class GetMessagesService {
const messageThreads = await this.getMessagesFromCompanyId(
workspaceMemberId,
opportunity.companyId,
workspaceId,
page,
pageSize,
);
@@ -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<MessageThreadWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageThreadWorkspaceEntity>(
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<MessageParticipantWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageParticipantWorkspaceEntity>(
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<MessageThreadWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageThreadWorkspaceEntity>(
workspaceId,
'messageThread',
);
@@ -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,
);
@@ -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: {} },
],
@@ -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 {}
@@ -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({
@@ -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<FlatFieldMetadata>;
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<ObjectRecord>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ObjectRecord>(
workspaceId,
flatObjectMetadata.nameSingular,
rolePermissionConfig,
);
return {
@@ -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),
};
}
}
@@ -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,
}),
],
@@ -20,7 +20,10 @@ export class HttpTool implements Tool {
constructor(private readonly twentyConfigService: TwentyConfigService) {}
async execute(parameters: ToolInput): Promise<ToolOutput> {
async execute(
parameters: ToolInput,
_workspaceId: string,
): Promise<ToolOutput> {
const { url, method, headers, body } = parameters as HttpRequestInput;
const headersCopy = { ...headers };
const isMethodForBody = ['POST', 'PUT', 'PATCH'].includes(method);
@@ -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<ToolOutput> {
const { workspaceId } = this.scopedWorkspaceContextFactory.create();
async execute(
parameters: SendEmailInput,
workspaceId: string,
): Promise<ToolOutput> {
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);
@@ -7,6 +7,6 @@ import { type ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.
export type Tool = {
description: string;
inputSchema: FlexibleSchema<unknown>;
execute(input: ToolInput): Promise<ToolOutput>;
execute(input: ToolInput, workspaceId: string): Promise<ToolOutput>;
flag?: PermissionFlagType;
};
@@ -141,6 +141,7 @@ export class WorkflowTriggerController {
name: 'Webhook',
context: {},
},
workspaceId,
});
return {
@@ -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,
);
}
}
@@ -150,14 +150,18 @@ export class WorkflowVersionStepResolver {
@Mutation(() => TestHttpRequestOutput)
async testHttpRequest(
@AuthWorkspace() workspace: WorkspaceEntity,
@Args('input')
{ url, method, headers, body }: TestHttpRequestInput,
): Promise<TestHttpRequestOutput> {
return this.httpTool.execute({
url,
method,
headers,
body,
});
return this.httpTool.execute(
{
url,
method,
headers,
body,
},
workspace.id,
);
}
}
@@ -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
@@ -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',
@@ -17,8 +17,8 @@ export class ToolAdapterService {
) {}
async getTools(
workspaceId: string,
rolePermissionConfig?: RolePermissionConfig,
workspaceId?: string,
): Promise<ToolSet> {
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),
};
}
}
@@ -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,
@@ -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 {}
@@ -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,
];
@@ -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,
};
}
}
@@ -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<RoleTargetEntity>,
private readonly workspaceDataSourceFactory: WorkspaceDatasourceFactory,
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
) {}
async getRepository<T extends ObjectLiteral>(
workspaceEntity: Type<T>,
): Promise<WorkspaceRepository<T>>;
async getRepository<T extends ObjectLiteral>(
objectMetadataName: string,
): Promise<WorkspaceRepository<T>>;
async getRepository<T extends ObjectLiteral>(
workspaceEntityOrObjectMetadataName: Type<T> | string,
): Promise<WorkspaceRepository<T>> {
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<T>(
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);
}
}
@@ -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 {}
@@ -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<BlocklistWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<BlocklistWorkspaceEntity>(
workspaceId,
'blocklist',
);
@@ -67,12 +68,14 @@ export class BlocklistItemDeleteCalendarEventsJob {
);
const calendarChannelRepository =
await this.twentyORMManager.getRepository<CalendarChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarChannelWorkspaceEntity>(
workspaceId,
'calendarChannel',
);
const calendarChannelEventAssociationRepository =
await this.twentyORMManager.getRepository<CalendarChannelEventAssociationWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarChannelEventAssociationWorkspaceEntity>(
workspaceId,
'calendarChannelEventAssociation',
);
@@ -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<CalendarChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarChannelWorkspaceEntity>(
workspaceId,
'calendarChannel',
);
@@ -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,
@@ -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<CalendarChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarChannelWorkspaceEntity>(
workspaceId,
'calendarChannel',
);
@@ -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<CalendarChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarChannelWorkspaceEntity>(
workspaceId,
'calendarChannel',
);
const calendarChannel = await calendarChannelRepository.findOne({
@@ -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<CalendarChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarChannelWorkspaceEntity>(
workspaceId,
'calendarChannel',
);
@@ -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<CalendarChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarChannelWorkspaceEntity>(
workspaceId,
'calendarChannel',
);
@@ -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<CalendarChannelEventAssociationWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarChannelEventAssociationWorkspaceEntity>(
workspaceId,
'calendarChannelEventAssociation',
);
@@ -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<CalendarChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarChannelWorkspaceEntity>(
workspaceId,
'calendarChannel',
);
@@ -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<void> {
const calendarEventRepository =
await this.twentyORMManager.getRepository<CalendarEventWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarEventWorkspaceEntity>(
workspaceId,
'calendarEvent',
);
const calendarChannelEventAssociationRepository =
await this.twentyORMManager.getRepository<CalendarChannelEventAssociationWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarChannelEventAssociationWorkspaceEntity>(
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) => {
@@ -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,
});
}
}
@@ -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<CalendarEventParticipantWorkspaceEntity>,
@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<CalendarEventParticipantWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarEventParticipantWorkspaceEntity>(
workspaceId,
'calendarEventParticipant',
);
@@ -172,6 +173,7 @@ export class CalendarEventParticipantService {
objectMetadataName: 'calendarEventParticipant',
transactionManager,
matchWith: 'workspaceMemberAndPerson',
workspaceId,
});
}
}
@@ -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,
);
}
@@ -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,
);
}
@@ -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,
);
@@ -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<CalendarChannelEventAssociationWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarChannelEventAssociationWorkspaceEntity>(
workspaceId,
'calendarChannelEventAssociation',
);
@@ -34,12 +38,14 @@ export class ApplyCalendarEventsVisibilityRestrictionsService {
});
const connectedAccountRepository =
await this.twentyORMManager.getRepository<ConnectedAccountWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ConnectedAccountWorkspaceEntity>(
workspaceId,
'connectedAccount',
);
const workspaceMemberRepository =
await this.twentyORMManager.getRepository<WorkspaceMemberWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
);
@@ -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(
@@ -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<ConnectedAccountWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ConnectedAccountWorkspaceEntity>(
workspaceId,
'connectedAccount',
);
@@ -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<ObjectMetadataEntity>,
@@ -39,7 +39,8 @@ export class ConnectedAccountDeleteOnePreQueryHook
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
const messageChannelRepository =
await this.twentyORMManager.getRepository<MessageChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
workspace.id,
'messageChannel',
);
@@ -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>(
MicrosoftAPIRefreshAccessTokenService,
);
twentyORMManager = module.get<TwentyORMManager>(TwentyORMManager);
twentyORMGlobalManager = module.get<TwentyORMGlobalManager>(
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(
@@ -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<ConnectedAccountWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ConnectedAccountWorkspaceEntity>(
workspaceId,
'connectedAccount',
);
@@ -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<FieldMetadataEntity>,
private readonly twentyORMManager: TwentyORMManager,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
async deleteFavoritesForDeletedRecords(
@@ -26,7 +26,8 @@ export class FavoriteDeletionService {
workspaceId: string,
): Promise<void> {
const favoriteRepository =
await this.twentyORMManager.getRepository<FavoriteWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<FavoriteWorkspaceEntity>(
workspaceId,
'favorite',
);
@@ -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 {}
@@ -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<ParticipantWorkspaceEntity>) {
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<PersonWorkspaceEntity>(
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,
});
}
}
@@ -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<BlocklistWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<BlocklistWorkspaceEntity>(
workspaceId,
'blocklist',
);
@@ -67,12 +68,14 @@ export class BlocklistItemDeleteMessagesJob {
);
const messageChannelRepository =
await this.twentyORMManager.getRepository<MessageChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
workspaceId,
'messageChannel',
);
const messageChannelMessageAssociationRepository =
await this.twentyORMManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelMessageAssociationWorkspaceEntity>(
workspaceId,
'messageChannelMessageAssociation',
);
@@ -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<MessageChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
workspaceId,
'messageChannel',
);
@@ -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,
);
@@ -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<MessageChannelMessageAssociationWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelMessageAssociationWorkspaceEntity>(
workspaceId,
'messageChannelMessageAssociation',
);
@@ -35,12 +39,14 @@ export class ApplyMessagesVisibilityRestrictionsService {
});
const connectedAccountRepository =
await this.twentyORMManager.getRepository<ConnectedAccountWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ConnectedAccountWorkspaceEntity>(
workspaceId,
'connectedAccount',
);
const workspaceMemberRepository =
await this.twentyORMManager.getRepository<WorkspaceMemberWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
);
@@ -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,
);
}
@@ -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,
);
}
@@ -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<MessageChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
workspaceId,
'messageChannel',
);
@@ -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<MessageChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
workspaceId,
'messageChannel',
);
@@ -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<MessageChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
workspaceId,
'messageChannel',
);
@@ -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>(
MessageChannelSyncStatusService,
);
twentyORMManager = module.get<TwentyORMManager>(TwentyORMManager);
twentyORMGlobalManager = module.get<TwentyORMGlobalManager>(
TwentyORMGlobalManager,
);
messagingCursorService = module.get<MessagingCursorService>(
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,
);
@@ -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'],
{
@@ -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<MessageChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
workspaceId,
'messageChannel',
);
const folderRepository =
await this.twentyORMManager.getRepository<MessageFolderWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageFolderWorkspaceEntity>(
workspaceId,
'messageFolder',
);
@@ -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<MessageChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
workspaceId,
'messageChannel',
);
@@ -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<MessageChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
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<MessageFolderWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageFolderWorkspaceEntity>(
workspaceId,
'messageFolder',
);
@@ -176,7 +181,8 @@ export class MessagingMessageListFetchService {
);
const messageChannelMessageAssociationRepository =
await this.twentyORMManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelMessageAssociationWorkspaceEntity>(
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<boolean> {
const messageFolderRepository =
await this.twentyORMManager.getRepository<MessageFolderWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageFolderWorkspaceEntity>(
workspaceId,
'messageFolder',
);
@@ -373,9 +382,11 @@ export class MessagingMessageListFetchService {
private async computeFullSyncMessageChannelMessageAssociationsToDelete(
messageChannel: Pick<MessageChannelWorkspaceEntity, 'id'>,
messageExternalIds: string[],
workspaceId: string,
) {
const messageChannelMessageAssociationRepository =
await this.twentyORMManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelMessageAssociationWorkspaceEntity>(
workspaceId,
'messageChannelMessageAssociation',
);
@@ -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<string, string>;
}> {
const messageChannelMessageAssociationRepository =
await this.twentyORMManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelMessageAssociationWorkspaceEntity>(
workspaceId,
'messageChannelMessageAssociation',
);
const messageRepository =
await this.twentyORMManager.getRepository<MessageWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageWorkspaceEntity>(
workspaceId,
'message',
);
const messageThreadRepository =
await this.twentyORMManager.getRepository<MessageThreadWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageThreadWorkspaceEntity>(
workspaceId,
'messageThread',
);
@@ -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<MessageChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
workspaceId,
'messageChannel',
);
@@ -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<MessageFolderWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageFolderWorkspaceEntity>(
workspaceId,
'messageFolder',
);
@@ -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<void> {
const messageChannelRepository =
await this.twentyORMManager.getRepository<MessageChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
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<MessageChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
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<void> {
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<MessageChannelWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
workspaceId,
'messageChannel',
);
@@ -165,7 +175,8 @@ export class MessagingProcessGroupEmailActionsService {
);
const messageFolderRepository =
await this.twentyORMManager.getRepository<MessageFolderWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageFolderWorkspaceEntity>(
workspaceId,
'messageFolder',
);
@@ -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),
},
},
],
@@ -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,
);
@@ -26,7 +26,7 @@ export class MessageParticipantMatchParticipantJob {
@Process(MessageParticipantMatchParticipantJob.name)
async handle(data: MessageParticipantMatchParticipantJobData): Promise<void> {
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,
});
}
}
@@ -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<MessageParticipantWorkspaceEntity>,
) {}
public async saveMessageParticipants(
participants: ParticipantWithMessageId[],
workspaceId: string,
transactionManager?: WorkspaceEntityManager,
): Promise<void> {
const messageParticipantRepository =
await this.twentyORMManager.getRepository<MessageParticipantWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageParticipantWorkspaceEntity>(
workspaceId,
'messageParticipant',
);
@@ -66,6 +68,7 @@ export class MessagingMessageParticipantService {
objectMetadataName: 'messageParticipant',
transactionManager,
matchWith: 'workspaceMemberAndPerson',
workspaceId,
});
}
}
@@ -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<WorkflowVersionWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
workspace.id,
'workflowVersion',
);
@@ -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<WorkflowVersionWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowVersionWorkspaceEntity>(
workspace.id,
'workflowVersion',
);
@@ -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();
@@ -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 {}
@@ -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 {}
@@ -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<WorkflowActionOutput> {
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(
@@ -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,
@@ -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);
@@ -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);
@@ -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,
@@ -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);
@@ -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);
@@ -30,6 +30,7 @@ export class ToolExecutorWorkflowAction implements WorkflowAction {
currentStepId,
steps,
context,
runInfo,
}: WorkflowActionInput): Promise<WorkflowActionOutput> {
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,
@@ -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,
],
@@ -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 {}
@@ -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<WorkflowRunWorkspaceEntity>(
workspaceId,
@@ -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<RunWorkflowJobData>(
@@ -334,6 +339,7 @@ export class WorkflowRunnerWorkspaceService {
createdBy: source,
status: WorkflowRunStatus.NOT_STARTED,
triggerPayload: payload,
workspaceId,
});
await this.workflowThrottlingWorkspaceService.increaseWorkflowRunNotStartedCount(
@@ -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,
@@ -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<WorkflowToolDependencies, 'workflowTriggerService'>,
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 {
@@ -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({
@@ -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<WorkflowToolDependencies, 'workflowTriggerService'>,
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 {
@@ -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<WorkflowAutomatedTriggerWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowAutomatedTriggerWorkspaceEntity>(
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<WorkflowAutomatedTriggerWorkspaceEntity>(
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkflowAutomatedTriggerWorkspaceEntity>(
workspaceId,
'workflowAutomatedTrigger',
);
@@ -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 {}
@@ -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<WorkflowVersionWorkspaceEntity>(
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<WorkflowVersionWorkspaceEntity>(
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<WorkflowWorkspaceEntity>,
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>,
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<WorkflowVersionWorkspaceEntity>,
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<WorkflowVersionWorkspaceEntity>,
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<WorkflowVersionWorkspaceEntity>,
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;

Some files were not shown because too many files have changed in this diff Show More