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:
+4
-3
@@ -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',
|
||||
);
|
||||
|
||||
|
||||
+8
@@ -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,
|
||||
},
|
||||
|
||||
+10
-5
@@ -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,
|
||||
});
|
||||
|
||||
+18
-5
@@ -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';
|
||||
|
||||
+14
-4
@@ -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,
|
||||
);
|
||||
|
||||
+13
-5
@@ -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',
|
||||
);
|
||||
|
||||
|
||||
+3
@@ -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,
|
||||
);
|
||||
|
||||
+2
-2
@@ -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 {
|
||||
|
||||
+2
-2
@@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -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);
|
||||
|
||||
+4
-12
@@ -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;
|
||||
};
|
||||
|
||||
+1
@@ -141,6 +141,7 @@ export class WorkflowTriggerController {
|
||||
name: 'Webhook',
|
||||
context: {},
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
+10
-1
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-6
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user