feat: migrate ConnectedAccount infrastructure entities to metadata schema (#18784)
## Summary - Migrates 4 entities (`connectedAccount`, `messageChannel`, `calendarChannel`, `messageFolder`) from per-workspace schemas to the shared `core` metadata schema - Introduces a `IS_CONNECTED_ACCOUNT_MIGRATED` feature flag to control the migration: when enabled, reads come from core metadata and all writes are dual-written to both workspace and core - Extracts 12 enums from workspace entity files to `twenty-shared` for reuse across frontend and backend - Creates new TypeORM entities, metadata services, GraphQL resolvers/DTOs, and exception interceptors per entity - Each entity owns its own data access module (`ConnectedAccountDataAccessModule`, `MessageChannelDataAccessModule`, `CalendarChannelDataAccessModule`, `MessageFolderDataAccessModule`) — no umbrella infrastructure module - Adds a 1.20 upgrade command that backfills data from workspace schemas to core (preserving UUIDs) and enables the feature flag - Replaces direct repository access with data access service calls across ~50 files in messaging, calendar, and connected-account modules - Adds `lastSignedInAt` and `oidcTokenClaims` fields to the new `ConnectedAccountEntity` - Drops unused `lastSyncHistoryId` field from the migrated connected account entity ## Test plan - [x] Lint passes (`npx nx lint:diff-with-main twenty-server`) - [x] Typecheck passes (`npx nx typecheck twenty-server`) - [x] All unit tests pass (477 suites, 4267 tests, 0 failures) - [ ] Manual test: verify messaging sync works with feature flag disabled (existing behavior) - [ ] Manual test: run upgrade command on a workspace, verify data backfilled to core tables - [ ] Manual test: verify messaging/calendar sync works with feature flag enabled (dual-write path) - [ ] Manual test: verify GraphQL metadata resolvers return correct data when flag enabled
This commit is contained in:
+6
-1
@@ -1,5 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { CalendarChannelDataAccessModule } from 'src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module';
|
||||
import { BlocklistItemDeleteCalendarEventsJob } from 'src/modules/calendar/blocklist-manager/jobs/blocklist-item-delete-calendar-events.job';
|
||||
import { BlocklistReimportCalendarEventsJob } from 'src/modules/calendar/blocklist-manager/jobs/blocklist-reimport-calendar-events.job';
|
||||
import { CalendarBlocklistListener } from 'src/modules/calendar/blocklist-manager/listeners/calendar-blocklist.listener';
|
||||
@@ -7,7 +8,11 @@ import { CalendarEventCleanerModule } from 'src/modules/calendar/calendar-event-
|
||||
import { CalendarCommonModule } from 'src/modules/calendar/common/calendar-common.module';
|
||||
|
||||
@Module({
|
||||
imports: [CalendarEventCleanerModule, CalendarCommonModule],
|
||||
imports: [
|
||||
CalendarEventCleanerModule,
|
||||
CalendarCommonModule,
|
||||
CalendarChannelDataAccessModule,
|
||||
],
|
||||
providers: [
|
||||
CalendarBlocklistListener,
|
||||
BlocklistItemDeleteCalendarEventsJob,
|
||||
|
||||
+24
-23
@@ -7,13 +7,13 @@ import { type ObjectRecordCreateEvent } from 'twenty-shared/database-events';
|
||||
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 { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
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';
|
||||
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';
|
||||
|
||||
export type BlocklistItemDeleteCalendarEventsJobData = WorkspaceEventBatch<
|
||||
ObjectRecordCreateEvent<BlocklistWorkspaceEntity>
|
||||
@@ -26,6 +26,7 @@ export type BlocklistItemDeleteCalendarEventsJobData = WorkspaceEventBatch<
|
||||
export class BlocklistItemDeleteCalendarEventsJob {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
|
||||
private readonly calendarEventCleanerService: CalendarEventCleanerService,
|
||||
) {}
|
||||
|
||||
@@ -71,12 +72,6 @@ export class BlocklistItemDeleteCalendarEventsJob {
|
||||
new Map<string, string[]>(),
|
||||
);
|
||||
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const calendarChannelEventAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelEventAssociationWorkspaceEntity>(
|
||||
workspaceId,
|
||||
@@ -91,29 +86,35 @@ export class BlocklistItemDeleteCalendarEventsJob {
|
||||
continue;
|
||||
}
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
select: {
|
||||
id: true,
|
||||
handle: true,
|
||||
connectedAccount: {
|
||||
handleAliases: true,
|
||||
const calendarChannels =
|
||||
await this.calendarChannelDataAccessService.find(workspaceId, {
|
||||
select: {
|
||||
id: true,
|
||||
handle: true,
|
||||
connectedAccount: {
|
||||
handleAliases: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
},
|
||||
},
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
const calendarChannelHandles = [calendarChannel.handle];
|
||||
|
||||
if (calendarChannel.connectedAccount.handleAliases) {
|
||||
calendarChannelHandles.push(
|
||||
...calendarChannel.connectedAccount.handleAliases.split(','),
|
||||
);
|
||||
const rawAliases = calendarChannel.connectedAccount
|
||||
.handleAliases as string | string[];
|
||||
|
||||
const aliasList = Array.isArray(rawAliases)
|
||||
? rawAliases
|
||||
: rawAliases.split(',').map((alias: string) => alias.trim());
|
||||
|
||||
calendarChannelHandles.push(...aliasList);
|
||||
}
|
||||
|
||||
const handleConditions = handles.map((handle) => {
|
||||
|
||||
+14
-20
@@ -6,15 +6,13 @@ import { type ObjectRecordDeleteEvent } from 'twenty-shared/database-events';
|
||||
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 { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
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';
|
||||
import {
|
||||
CalendarChannelSyncStage,
|
||||
type CalendarChannelWorkspaceEntity,
|
||||
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
import { CalendarChannelSyncStage } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
|
||||
export type BlocklistReimportCalendarEventsJobData = WorkspaceEventBatch<
|
||||
ObjectRecordDeleteEvent<BlocklistWorkspaceEntity>
|
||||
@@ -27,6 +25,7 @@ export type BlocklistReimportCalendarEventsJobData = WorkspaceEventBatch<
|
||||
export class BlocklistReimportCalendarEventsJob {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
|
||||
private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService,
|
||||
) {}
|
||||
|
||||
@@ -37,27 +36,22 @@ export class BlocklistReimportCalendarEventsJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
for (const eventPayload of data.events) {
|
||||
const workspaceMemberId =
|
||||
eventPayload.properties.before.workspaceMemberId;
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
select: ['id'],
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
const calendarChannels =
|
||||
await this.calendarChannelDataAccessService.find(workspaceId, {
|
||||
select: ['id'],
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
},
|
||||
syncStage: Not(
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
),
|
||||
},
|
||||
syncStage: Not(
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await this.calendarChannelSyncStatusService.resetAndMarkAsCalendarEventListFetchPending(
|
||||
calendarChannels.map((calendarChannel) => calendarChannel.id),
|
||||
|
||||
+4
@@ -6,6 +6,8 @@ import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-
|
||||
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { CalendarChannelDataAccessModule } from 'src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module';
|
||||
import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module';
|
||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity';
|
||||
@@ -57,6 +59,8 @@ import { RefreshTokensManagerModule } from 'src/modules/connected-account/refres
|
||||
ConnectedAccountModule,
|
||||
CalendarCommonModule,
|
||||
MetricsModule,
|
||||
CalendarChannelDataAccessModule,
|
||||
ConnectedAccountDataAccessModule,
|
||||
],
|
||||
providers: [
|
||||
CalendarAccountAuthenticationService,
|
||||
|
||||
+23
-26
@@ -5,16 +5,14 @@ import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
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 { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import {
|
||||
CalendarEventListFetchJob,
|
||||
type CalendarEventListFetchJobData,
|
||||
} from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job';
|
||||
import {
|
||||
CalendarChannelSyncStage,
|
||||
type CalendarChannelWorkspaceEntity,
|
||||
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
import { CalendarChannelSyncStage } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
|
||||
type CalendarTriggerEventListFetchCommandOptions = {
|
||||
workspaceId: string;
|
||||
@@ -33,6 +31,7 @@ export class CalendarTriggerEventListFetchCommand extends CommandRunner {
|
||||
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
|
||||
@InjectMessageQueue(MessageQueue.calendarQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
) {
|
||||
@@ -52,23 +51,17 @@ export class CalendarTriggerEventListFetchCommand extends CommandRunner {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const whereCondition: Record<string, unknown> = {
|
||||
isSyncEnabled: true,
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
};
|
||||
|
||||
if (calendarChannelId) {
|
||||
whereCondition.id = calendarChannelId;
|
||||
}
|
||||
|
||||
const calendarChannels =
|
||||
await calendarChannelRepository.find(whereCondition);
|
||||
const calendarChannels = await this.calendarChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{
|
||||
where: {
|
||||
isSyncEnabled: true,
|
||||
syncStage:
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
...(calendarChannelId ? { id: calendarChannelId } : {}),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (calendarChannels.length === 0) {
|
||||
this.logger.warn(
|
||||
@@ -83,11 +76,15 @@ export class CalendarTriggerEventListFetchCommand extends CommandRunner {
|
||||
);
|
||||
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
await calendarChannelRepository.update(calendarChannel.id, {
|
||||
syncStage:
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
await this.calendarChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: calendarChannel.id },
|
||||
{
|
||||
syncStage:
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
},
|
||||
);
|
||||
|
||||
await this.messageQueueService.add<CalendarEventListFetchJobData>(
|
||||
CalendarEventListFetchJob.name,
|
||||
|
||||
+16
-19
@@ -3,6 +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 { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { CalendarFetchEventsService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service';
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
CalendarChannelSyncStage,
|
||||
type CalendarChannelWorkspaceEntity,
|
||||
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { isThrottled } from 'src/modules/connected-account/utils/is-throttled';
|
||||
|
||||
export type CalendarEventListFetchJobData = {
|
||||
@@ -25,6 +27,7 @@ export type CalendarEventListFetchJobData = {
|
||||
export class CalendarEventListFetchJob {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
|
||||
private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService,
|
||||
private readonly calendarFetchEventsService: CalendarFetchEventsService,
|
||||
) {}
|
||||
@@ -36,19 +39,14 @@ export class CalendarEventListFetchJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const calendarChannel = await calendarChannelRepository.findOne({
|
||||
where: {
|
||||
id: calendarChannelId,
|
||||
isSyncEnabled: true,
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
const calendarChannel =
|
||||
await this.calendarChannelDataAccessService.findOne(workspaceId, {
|
||||
where: {
|
||||
id: calendarChannelId,
|
||||
isSyncEnabled: true,
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
|
||||
if (!calendarChannel) {
|
||||
return;
|
||||
@@ -61,11 +59,10 @@ export class CalendarEventListFetchJob {
|
||||
return;
|
||||
}
|
||||
|
||||
const syncStageStartedAt = calendarChannel.syncStageStartedAt;
|
||||
|
||||
if (
|
||||
isThrottled(
|
||||
calendarChannel.syncStageStartedAt,
|
||||
calendarChannel.throttleFailureCount,
|
||||
)
|
||||
isThrottled(syncStageStartedAt, calendarChannel.throttleFailureCount)
|
||||
) {
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending(
|
||||
[calendarChannel.id],
|
||||
@@ -77,8 +74,8 @@ export class CalendarEventListFetchJob {
|
||||
}
|
||||
|
||||
await this.calendarFetchEventsService.fetchCalendarEvents(
|
||||
calendarChannel,
|
||||
calendarChannel.connectedAccount,
|
||||
calendarChannel as unknown as CalendarChannelWorkspaceEntity,
|
||||
calendarChannel.connectedAccount as unknown as ConnectedAccountWorkspaceEntity,
|
||||
workspaceId,
|
||||
);
|
||||
}, authContext);
|
||||
|
||||
+16
-18
@@ -3,6 +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 { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { CalendarEventsImportService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service';
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
CalendarChannelSyncStage,
|
||||
type CalendarChannelWorkspaceEntity,
|
||||
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { isThrottled } from 'src/modules/connected-account/utils/is-throttled';
|
||||
|
||||
export type CalendarEventsImportJobData = {
|
||||
@@ -27,6 +29,7 @@ export class CalendarEventsImportJob {
|
||||
private readonly calendarEventsImportService: CalendarEventsImportService,
|
||||
private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
|
||||
) {}
|
||||
|
||||
@Process(CalendarEventsImportJob.name)
|
||||
@@ -36,18 +39,14 @@ export class CalendarEventsImportJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
const calendarChannel = await calendarChannelRepository.findOne({
|
||||
where: {
|
||||
id: calendarChannelId,
|
||||
isSyncEnabled: true,
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
const calendarChannel =
|
||||
await this.calendarChannelDataAccessService.findOne(workspaceId, {
|
||||
where: {
|
||||
id: calendarChannelId,
|
||||
isSyncEnabled: true,
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
|
||||
if (!calendarChannel?.isSyncEnabled) {
|
||||
return;
|
||||
@@ -60,11 +59,10 @@ export class CalendarEventsImportJob {
|
||||
return;
|
||||
}
|
||||
|
||||
const syncStageStartedAt = calendarChannel.syncStageStartedAt;
|
||||
|
||||
if (
|
||||
isThrottled(
|
||||
calendarChannel.syncStageStartedAt,
|
||||
calendarChannel.throttleFailureCount,
|
||||
)
|
||||
isThrottled(syncStageStartedAt, calendarChannel.throttleFailureCount)
|
||||
) {
|
||||
await this.calendarChannelSyncStatusService.markAsCalendarEventsImportPending(
|
||||
[calendarChannel.id],
|
||||
@@ -76,8 +74,8 @@ export class CalendarEventsImportJob {
|
||||
}
|
||||
|
||||
await this.calendarEventsImportService.processCalendarEventsImport(
|
||||
calendarChannel,
|
||||
calendarChannel.connectedAccount,
|
||||
calendarChannel as unknown as CalendarChannelWorkspaceEntity,
|
||||
calendarChannel.connectedAccount as unknown as ConnectedAccountWorkspaceEntity,
|
||||
workspaceId,
|
||||
);
|
||||
}, authContext);
|
||||
|
||||
+18
-20
@@ -5,14 +5,12 @@ 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 { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
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 {
|
||||
CalendarChannelSyncStage,
|
||||
type CalendarChannelWorkspaceEntity,
|
||||
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
import { CalendarChannelSyncStage } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
|
||||
export type CalendarOngoingStaleJobData = {
|
||||
workspaceId: string;
|
||||
@@ -26,6 +24,7 @@ export class CalendarOngoingStaleJob {
|
||||
private readonly logger = new Logger(CalendarOngoingStaleJob.name);
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
|
||||
private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService,
|
||||
) {}
|
||||
|
||||
@@ -36,25 +35,24 @@ export class CalendarOngoingStaleJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
where: {
|
||||
syncStage: In([
|
||||
CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING,
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING,
|
||||
CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED,
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
]),
|
||||
const calendarChannels = await this.calendarChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{
|
||||
where: {
|
||||
syncStage: In([
|
||||
CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING,
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING,
|
||||
CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED,
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
]),
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
if (isSyncStale(calendarChannel.syncStageStartedAt)) {
|
||||
const syncStageStartedAt = calendarChannel.syncStageStartedAt;
|
||||
|
||||
if (isSyncStale(syncStageStartedAt)) {
|
||||
await this.calendarChannelSyncStatusService.resetSyncStageStartedAt(
|
||||
[calendarChannel.id],
|
||||
workspaceId,
|
||||
|
||||
+15
-21
@@ -3,12 +3,12 @@ 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 { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import {
|
||||
CalendarChannelSyncStage,
|
||||
CalendarChannelSyncStatus,
|
||||
CalendarChannelWorkspaceEntity,
|
||||
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
|
||||
export type CalendarRelaunchFailedCalendarChannelJobData = {
|
||||
@@ -23,6 +23,7 @@ export type CalendarRelaunchFailedCalendarChannelJobData = {
|
||||
export class CalendarRelaunchFailedCalendarChannelJob {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
|
||||
) {}
|
||||
|
||||
@Process(CalendarRelaunchFailedCalendarChannelJob.name)
|
||||
@@ -32,23 +33,12 @@ export class CalendarRelaunchFailedCalendarChannelJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const calendarChannel = await calendarChannelRepository.findOne({
|
||||
where: {
|
||||
id: calendarChannelId,
|
||||
},
|
||||
relations: {
|
||||
connectedAccount: {
|
||||
accountOwner: true,
|
||||
const calendarChannel =
|
||||
await this.calendarChannelDataAccessService.findOne(workspaceId, {
|
||||
where: {
|
||||
id: calendarChannelId,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (
|
||||
!calendarChannel ||
|
||||
@@ -58,10 +48,14 @@ export class CalendarRelaunchFailedCalendarChannelJob {
|
||||
return;
|
||||
}
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelId, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
syncStatus: CalendarChannelSyncStatus.ACTIVE,
|
||||
});
|
||||
await this.calendarChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: calendarChannelId },
|
||||
{
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
syncStatus: CalendarChannelSyncStatus.ACTIVE,
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-22
@@ -1,12 +1,11 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
|
||||
import {
|
||||
type TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { CALENDAR_THROTTLE_MAX_ATTEMPTS } from 'src/modules/calendar/calendar-event-import-manager/constants/calendar-throttle-max-attempts';
|
||||
import {
|
||||
type CalendarEventImportDriverException,
|
||||
@@ -29,7 +28,7 @@ export class CalendarEventImportErrorHandlerService {
|
||||
CalendarEventImportErrorHandlerService.name,
|
||||
);
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
|
||||
private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService,
|
||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||
) {}
|
||||
@@ -133,25 +132,14 @@ export class CalendarEventImportErrorHandlerService {
|
||||
throw calendarEventImportException;
|
||||
}
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.increment(
|
||||
{
|
||||
id: calendarChannel.id,
|
||||
},
|
||||
'throttleFailureCount',
|
||||
1,
|
||||
undefined,
|
||||
['throttleFailureCount', 'id'],
|
||||
);
|
||||
}, authContext);
|
||||
await this.calendarChannelDataAccessService.increment(
|
||||
workspaceId,
|
||||
{
|
||||
id: calendarChannel.id,
|
||||
},
|
||||
'throttleFailureCount',
|
||||
1,
|
||||
);
|
||||
|
||||
switch (syncStep) {
|
||||
case CalendarEventImportSyncStep.CALENDAR_EVENT_LIST_FETCH:
|
||||
|
||||
+6
-8
@@ -5,6 +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 { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import {
|
||||
@@ -29,6 +30,7 @@ export class CalendarFetchEventsService {
|
||||
@InjectCacheStorage(CacheStorageNamespace.ModuleCalendar)
|
||||
private readonly cacheStorage: CacheStorageService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
|
||||
private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService,
|
||||
private readonly getCalendarEventsService: CalendarGetCalendarEventsService,
|
||||
private readonly calendarEventImportErrorHandlerService: CalendarEventImportErrorHandlerService,
|
||||
@@ -90,14 +92,9 @@ export class CalendarFetchEventsService {
|
||||
const calendarEventIds = getCalendarEventsResponse.calendarEventIds;
|
||||
const nextSyncCursor = getCalendarEventsResponse.nextSyncCursor;
|
||||
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
if (!calendarEvents || calendarEvents?.length === 0) {
|
||||
await calendarChannelRepository.update(
|
||||
await this.calendarChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{
|
||||
id: calendarChannel.id,
|
||||
},
|
||||
@@ -112,7 +109,8 @@ export class CalendarFetchEventsService {
|
||||
);
|
||||
}
|
||||
|
||||
await calendarChannelRepository.update(
|
||||
await this.calendarChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{
|
||||
id: calendarChannel.id,
|
||||
},
|
||||
|
||||
@@ -3,6 +3,8 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
|
||||
import { CalendarChannelDataAccessModule } from 'src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module';
|
||||
import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service';
|
||||
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
|
||||
@@ -12,6 +14,8 @@ import { ConnectedAccountModule } from 'src/modules/connected-account/connected-
|
||||
WorkspaceDataSourceModule,
|
||||
TypeOrmModule.forFeature([FeatureFlagEntity]),
|
||||
ConnectedAccountModule,
|
||||
CalendarChannelDataAccessModule,
|
||||
ConnectedAccountDataAccessModule,
|
||||
MetricsModule,
|
||||
],
|
||||
providers: [CalendarChannelSyncStatusService],
|
||||
|
||||
+15
-13
@@ -2,6 +2,7 @@ import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants';
|
||||
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.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';
|
||||
@@ -44,22 +45,19 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
|
||||
find: jest.fn(),
|
||||
};
|
||||
|
||||
const mockConnectedAccountRepository = {
|
||||
find: jest.fn(),
|
||||
};
|
||||
|
||||
const mockWorkspaceMemberRepository = {
|
||||
findOneByOrFail: jest.fn(),
|
||||
};
|
||||
|
||||
const mockConnectedAccountDataAccessService = {
|
||||
find: jest.fn(),
|
||||
};
|
||||
|
||||
const mockGlobalWorkspaceOrmManager = {
|
||||
getRepository: jest.fn().mockImplementation((workspaceId, name) => {
|
||||
if (name === 'calendarChannelEventAssociation') {
|
||||
return mockCalendarEventAssociationRepository;
|
||||
}
|
||||
if (name === 'connectedAccount') {
|
||||
return mockConnectedAccountRepository;
|
||||
}
|
||||
if (name === 'workspaceMember') {
|
||||
return mockWorkspaceMemberRepository;
|
||||
}
|
||||
@@ -77,6 +75,10 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
|
||||
provide: GlobalWorkspaceOrmManager,
|
||||
useValue: mockGlobalWorkspaceOrmManager,
|
||||
},
|
||||
{
|
||||
provide: ConnectedAccountDataAccessService,
|
||||
useValue: mockConnectedAccountDataAccessService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -121,7 +123,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
|
||||
item.description !== FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(mockConnectedAccountRepository.find).not.toHaveBeenCalled();
|
||||
expect(mockConnectedAccountDataAccessService.find).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return calendar event with obfuscated title and description if the visibility is METADATA', async () => {
|
||||
@@ -143,7 +145,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
|
||||
id: 'workspace-member-id',
|
||||
});
|
||||
|
||||
mockConnectedAccountRepository.find.mockResolvedValue([]);
|
||||
mockConnectedAccountDataAccessService.find.mockResolvedValue([]);
|
||||
|
||||
const result = await service.applyCalendarEventsVisibilityRestrictions(
|
||||
calendarEvents,
|
||||
@@ -179,7 +181,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
|
||||
id: 'workspace-member-account-owner-id',
|
||||
});
|
||||
|
||||
mockConnectedAccountRepository.find.mockResolvedValue([{ id: '1' }]);
|
||||
mockConnectedAccountDataAccessService.find.mockResolvedValue([{ id: '1' }]);
|
||||
|
||||
const result = await service.applyCalendarEventsVisibilityRestrictions(
|
||||
calendarEvents,
|
||||
@@ -215,7 +217,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
|
||||
id: 'workspace-member-not-account-owner-id',
|
||||
});
|
||||
|
||||
mockConnectedAccountRepository.find.mockResolvedValue([]);
|
||||
mockConnectedAccountDataAccessService.find.mockResolvedValue([]);
|
||||
|
||||
const result = await service.applyCalendarEventsVisibilityRestrictions(
|
||||
calendarEvents,
|
||||
@@ -261,7 +263,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
mockConnectedAccountRepository.find
|
||||
mockConnectedAccountDataAccessService.find
|
||||
.mockResolvedValueOnce([]) // request for calendar event 3
|
||||
.mockResolvedValueOnce([{ id: '1' }]); // request for calendar event 2
|
||||
|
||||
@@ -317,7 +319,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
mockConnectedAccountRepository.find
|
||||
mockConnectedAccountDataAccessService.find
|
||||
.mockResolvedValueOnce([]) // request for calendar event 3
|
||||
.mockResolvedValueOnce([{ id: '1' }]); // request for calendar event 2
|
||||
|
||||
|
||||
+5
-12
@@ -5,18 +5,19 @@ import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { In } from 'typeorm';
|
||||
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
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';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class ApplyCalendarEventsVisibilityRestrictionsService {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
|
||||
) {}
|
||||
|
||||
public async applyCalendarEventsVisibilityRestrictions(
|
||||
@@ -42,12 +43,6 @@ export class ApplyCalendarEventsVisibilityRestrictionsService {
|
||||
relations: ['calendarChannel'],
|
||||
});
|
||||
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
@@ -84,15 +79,13 @@ export class ApplyCalendarEventsVisibilityRestrictionsService {
|
||||
userId: userId,
|
||||
});
|
||||
|
||||
const connectedAccounts = await connectedAccountRepository.find({
|
||||
select: ['id'],
|
||||
where: {
|
||||
const connectedAccounts =
|
||||
await this.connectedAccountDataAccessService.find(workspaceId, {
|
||||
calendarChannels: {
|
||||
id: In(calendarChannels.map((channel) => channel.id)),
|
||||
},
|
||||
accountOwnerId: workspaceMember.id,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (connectedAccounts.length > 0) {
|
||||
continue;
|
||||
|
||||
+2
@@ -1,10 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module';
|
||||
import { CalendarEventFindManyPostQueryHook } from 'src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-many.post-query.hook';
|
||||
import { CalendarEventFindOnePostQueryHook } from 'src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.post-query.hook';
|
||||
import { ApplyCalendarEventsVisibilityRestrictionsService } from 'src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service';
|
||||
|
||||
@Module({
|
||||
imports: [ConnectedAccountDataAccessModule],
|
||||
providers: [
|
||||
ApplyCalendarEventsVisibilityRestrictionsService,
|
||||
CalendarEventFindOnePostQueryHook,
|
||||
|
||||
+125
-125
@@ -1,22 +1,23 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { Any } from 'typeorm';
|
||||
import { Any, In } 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 { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
|
||||
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
|
||||
import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import {
|
||||
CalendarChannelSyncStage,
|
||||
CalendarChannelSyncStatus,
|
||||
type CalendarChannelWorkspaceEntity,
|
||||
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class CalendarChannelSyncStatusService {
|
||||
@@ -24,6 +25,8 @@ export class CalendarChannelSyncStatusService {
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
@InjectCacheStorage(CacheStorageNamespace.ModuleCalendar)
|
||||
private readonly cacheStorage: CacheStorageService,
|
||||
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
|
||||
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
|
||||
private readonly accountsToReconnectService: AccountsToReconnectService,
|
||||
private readonly metricsService: MetricsService,
|
||||
) {}
|
||||
@@ -40,16 +43,14 @@ export class CalendarChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
});
|
||||
await this.calendarChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(calendarChannelIds) },
|
||||
{
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
@@ -64,17 +65,15 @@ export class CalendarChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING,
|
||||
syncStatus: CalendarChannelSyncStatus.ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
await this.calendarChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(calendarChannelIds) },
|
||||
{
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING,
|
||||
syncStatus: CalendarChannelSyncStatus.ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
@@ -95,17 +94,15 @@ export class CalendarChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
throttleFailureCount: 0,
|
||||
});
|
||||
await this.calendarChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(calendarChannelIds) },
|
||||
{
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
throttleFailureCount: 0,
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
|
||||
await this.markAsCalendarEventListFetchPending(
|
||||
@@ -125,15 +122,13 @@ export class CalendarChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStageStartedAt: null,
|
||||
});
|
||||
await this.calendarChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(calendarChannelIds) },
|
||||
{
|
||||
syncStageStartedAt: null,
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
@@ -149,16 +144,14 @@ export class CalendarChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
});
|
||||
await this.calendarChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(calendarChannelIds) },
|
||||
{
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
@@ -173,17 +166,15 @@ export class CalendarChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING,
|
||||
syncStatus: CalendarChannelSyncStatus.ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
await this.calendarChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(calendarChannelIds) },
|
||||
{
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING,
|
||||
syncStatus: CalendarChannelSyncStatus.ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
@@ -198,19 +189,17 @@ export class CalendarChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
syncStatus: CalendarChannelSyncStatus.ACTIVE,
|
||||
throttleFailureCount: 0,
|
||||
syncStageStartedAt: null,
|
||||
syncedAt: new Date().toISOString(),
|
||||
});
|
||||
await this.calendarChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(calendarChannelIds) },
|
||||
{
|
||||
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
syncStatus: CalendarChannelSyncStatus.ACTIVE,
|
||||
throttleFailureCount: 0,
|
||||
syncStageStartedAt: null,
|
||||
syncedAt: new Date().toISOString(),
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
|
||||
await this.markAsCalendarEventListFetchPending(
|
||||
@@ -241,16 +230,14 @@ export class CalendarChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStatus: CalendarChannelSyncStatus.FAILED_UNKNOWN,
|
||||
syncStage: CalendarChannelSyncStage.FAILED,
|
||||
});
|
||||
await this.calendarChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(calendarChannelIds) },
|
||||
{
|
||||
syncStatus: CalendarChannelSyncStatus.FAILED_UNKNOWN,
|
||||
syncStage: CalendarChannelSyncStage.FAILED,
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
|
||||
await this.metricsService.batchIncrementCounter({
|
||||
@@ -276,33 +263,29 @@ export class CalendarChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
await this.calendarChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(calendarChannelIds) },
|
||||
{
|
||||
syncStatus: CalendarChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS,
|
||||
syncStage: CalendarChannelSyncStage.FAILED,
|
||||
},
|
||||
);
|
||||
|
||||
await calendarChannelRepository.update(calendarChannelIds, {
|
||||
syncStatus: CalendarChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS,
|
||||
syncStage: CalendarChannelSyncStage.FAILED,
|
||||
});
|
||||
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
select: ['id', 'connectedAccountId'],
|
||||
where: { id: Any(calendarChannelIds) },
|
||||
});
|
||||
const calendarChannels = await this.calendarChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{
|
||||
select: ['id', 'connectedAccountId'],
|
||||
where: { id: Any(calendarChannelIds) },
|
||||
},
|
||||
);
|
||||
|
||||
const connectedAccountIds = calendarChannels.map(
|
||||
(calendarChannel) => calendarChannel.connectedAccountId,
|
||||
);
|
||||
|
||||
await connectedAccountRepository.update(
|
||||
await this.connectedAccountDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: Any(connectedAccountIds) },
|
||||
{
|
||||
authFailedAt: new Date(),
|
||||
@@ -329,26 +312,43 @@ export class CalendarChannelSyncStatusService {
|
||||
return;
|
||||
}
|
||||
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
where: {
|
||||
id: Any(calendarChannelIds),
|
||||
},
|
||||
relations: {
|
||||
connectedAccount: {
|
||||
accountOwner: true,
|
||||
const calendarChannels = await this.calendarChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{
|
||||
select: ['id', 'connectedAccountId'],
|
||||
where: {
|
||||
id: Any(calendarChannelIds),
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workspaceMember',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
const userId = calendarChannel.connectedAccount.accountOwner.userId;
|
||||
const connectedAccountId = calendarChannel.connectedAccount.id;
|
||||
const connectedAccount =
|
||||
await this.connectedAccountDataAccessService.findOne(workspaceId, {
|
||||
where: { id: calendarChannel.connectedAccountId },
|
||||
});
|
||||
|
||||
if (!connectedAccount) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const workspaceMember = await workspaceMemberRepository.findOne({
|
||||
where: { id: connectedAccount.accountOwnerId },
|
||||
});
|
||||
|
||||
if (!workspaceMember) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const userId = workspaceMember.userId;
|
||||
const connectedAccountId = connectedAccount.id;
|
||||
|
||||
await this.accountsToReconnectService.addAccountToReconnectByKey(
|
||||
AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS,
|
||||
|
||||
+13
-31
@@ -1,6 +1,12 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
CalendarChannelContactAutoCreationPolicy,
|
||||
CalendarChannelSyncStage,
|
||||
CalendarChannelSyncStatus,
|
||||
CalendarChannelVisibility,
|
||||
FieldMetadataType,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { type FieldTypeAndNameMetadata } from 'src/engine/workspace-manager/utils/get-ts-vector-column-expression.util';
|
||||
@@ -8,36 +14,12 @@ import { type EntityRelation } from 'src/engine/workspace-manager/workspace-migr
|
||||
import { type CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
export enum CalendarChannelVisibility {
|
||||
METADATA = 'METADATA',
|
||||
SHARE_EVERYTHING = 'SHARE_EVERYTHING',
|
||||
}
|
||||
|
||||
export enum CalendarChannelSyncStatus {
|
||||
NOT_SYNCED = 'NOT_SYNCED',
|
||||
ONGOING = 'ONGOING',
|
||||
ACTIVE = 'ACTIVE',
|
||||
FAILED_INSUFFICIENT_PERMISSIONS = 'FAILED_INSUFFICIENT_PERMISSIONS',
|
||||
FAILED_UNKNOWN = 'FAILED_UNKNOWN',
|
||||
}
|
||||
|
||||
export enum CalendarChannelSyncStage {
|
||||
PENDING_CONFIGURATION = 'PENDING_CONFIGURATION',
|
||||
CALENDAR_EVENT_LIST_FETCH_PENDING = 'CALENDAR_EVENT_LIST_FETCH_PENDING',
|
||||
CALENDAR_EVENT_LIST_FETCH_SCHEDULED = 'CALENDAR_EVENT_LIST_FETCH_SCHEDULED',
|
||||
CALENDAR_EVENT_LIST_FETCH_ONGOING = 'CALENDAR_EVENT_LIST_FETCH_ONGOING',
|
||||
CALENDAR_EVENTS_IMPORT_PENDING = 'CALENDAR_EVENTS_IMPORT_PENDING',
|
||||
CALENDAR_EVENTS_IMPORT_SCHEDULED = 'CALENDAR_EVENTS_IMPORT_SCHEDULED',
|
||||
CALENDAR_EVENTS_IMPORT_ONGOING = 'CALENDAR_EVENTS_IMPORT_ONGOING',
|
||||
FAILED = 'FAILED',
|
||||
}
|
||||
|
||||
export enum CalendarChannelContactAutoCreationPolicy {
|
||||
AS_PARTICIPANT_AND_ORGANIZER = 'AS_PARTICIPANT_AND_ORGANIZER',
|
||||
AS_PARTICIPANT = 'AS_PARTICIPANT',
|
||||
AS_ORGANIZER = 'AS_ORGANIZER',
|
||||
NONE = 'NONE',
|
||||
}
|
||||
export {
|
||||
CalendarChannelContactAutoCreationPolicy,
|
||||
CalendarChannelSyncStage,
|
||||
CalendarChannelSyncStatus,
|
||||
CalendarChannelVisibility,
|
||||
};
|
||||
|
||||
registerEnumType(CalendarChannelVisibility, {
|
||||
name: 'CalendarChannelVisibility',
|
||||
|
||||
+4
@@ -1,5 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { CalendarChannelDataAccessModule } from 'src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module';
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { ChannelSyncResolver } from 'src/modules/connected-account/channel-sync/channel-sync.resolver';
|
||||
@@ -8,6 +10,8 @@ import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-co
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CalendarChannelDataAccessModule,
|
||||
MessageChannelDataAccessModule,
|
||||
PermissionsModule,
|
||||
WorkspaceDataSourceModule,
|
||||
MessagingCommonModule,
|
||||
|
||||
+26
-30
@@ -3,6 +3,8 @@ import { Injectable } from '@nestjs/common';
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
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 { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import {
|
||||
@@ -12,13 +14,9 @@ import {
|
||||
import {
|
||||
CalendarChannelSyncStage,
|
||||
CalendarChannelSyncStatus,
|
||||
type CalendarChannelWorkspaceEntity,
|
||||
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessagingMessageListFetchJob,
|
||||
type MessagingMessageListFetchJobData,
|
||||
@@ -37,7 +35,9 @@ export class ChannelSyncService {
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
@InjectMessageQueue(MessageQueue.calendarQueue)
|
||||
private readonly calendarQueueService: MessageQueueService,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
|
||||
) {}
|
||||
|
||||
async startChannelSync(input: StartChannelSyncInput): Promise<void> {
|
||||
@@ -54,18 +54,13 @@ export class ChannelSyncService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: {
|
||||
const messageChannels = await this.messageChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{
|
||||
connectedAccountId,
|
||||
syncStage: MessageChannelSyncStage.PENDING_CONFIGURATION,
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await this.messageChannelSyncStatusService.markAsMessagesListFetchScheduled(
|
||||
@@ -91,25 +86,26 @@ export class ChannelSyncService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
where: {
|
||||
connectedAccountId,
|
||||
syncStage: CalendarChannelSyncStage.PENDING_CONFIGURATION,
|
||||
const calendarChannels = await this.calendarChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{
|
||||
where: {
|
||||
connectedAccountId,
|
||||
syncStage: CalendarChannelSyncStage.PENDING_CONFIGURATION,
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
await calendarChannelRepository.update(calendarChannel.id, {
|
||||
syncStage:
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
syncStatus: CalendarChannelSyncStatus.ONGOING,
|
||||
});
|
||||
await this.calendarChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: calendarChannel.id },
|
||||
{
|
||||
syncStage:
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
syncStatus: CalendarChannelSyncStatus.ONGOING,
|
||||
},
|
||||
);
|
||||
|
||||
await this.calendarQueueService.add<CalendarEventListFetchJobData>(
|
||||
CalendarEventListFetchJob.name,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { UserVarsModule } from 'src/engine/core-modules/user/user-vars/user-vars.module';
|
||||
import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module';
|
||||
import { DeleteWorkspaceMemberConnectedAccountsCleanupJob } from 'src/modules/connected-account/jobs/delete-workspace-member-connected-accounts.job';
|
||||
import { ConnectedAccountWorkspaceMemberListener } from 'src/modules/connected-account/listeners/connected-account-workspace-member.listener';
|
||||
import { ConnectedAccountListener } from 'src/modules/connected-account/listeners/connected-account.listener';
|
||||
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
|
||||
|
||||
@Module({
|
||||
imports: [UserVarsModule],
|
||||
imports: [ConnectedAccountDataAccessModule, UserVarsModule],
|
||||
providers: [
|
||||
AccountsToReconnectService,
|
||||
ConnectedAccountListener,
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module';
|
||||
import { GmailEmailAliasErrorHandlerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-error-handler.service';
|
||||
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';
|
||||
@@ -7,7 +8,7 @@ import { EmailAliasManagerService } from 'src/modules/connected-account/email-al
|
||||
import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-client-manager/oauth2-client-manager.module';
|
||||
|
||||
@Module({
|
||||
imports: [OAuth2ClientManagerModule],
|
||||
imports: [OAuth2ClientManagerModule, ConnectedAccountDataAccessModule],
|
||||
providers: [
|
||||
EmailAliasManagerService,
|
||||
GoogleEmailAliasManagerService,
|
||||
|
||||
+11
-13
@@ -1,8 +1,8 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { type Repository } from 'typeorm';
|
||||
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.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';
|
||||
@@ -15,30 +15,27 @@ import { EmailAliasManagerService } from './email-alias-manager.service';
|
||||
describe('Email Alias Manager Service', () => {
|
||||
let emailAliasManagerService: EmailAliasManagerService;
|
||||
let microsoftEmailAliasManagerService: MicrosoftEmailAliasManagerService;
|
||||
let connectedAccountRepository: Partial<
|
||||
Repository<ConnectedAccountWorkspaceEntity>
|
||||
>;
|
||||
const mockConnectedAccountDataAccessService = {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
update: jest.fn().mockResolvedValue((arg) => arg),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
connectedAccountRepository = {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
update: jest.fn().mockResolvedValue((arg) => arg),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: GlobalWorkspaceOrmManager,
|
||||
useValue: {
|
||||
getRepository: jest
|
||||
.fn()
|
||||
.mockResolvedValue(connectedAccountRepository),
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
},
|
||||
},
|
||||
EmailAliasManagerService,
|
||||
{
|
||||
provide: ConnectedAccountDataAccessService,
|
||||
useValue: mockConnectedAccountDataAccessService,
|
||||
},
|
||||
{
|
||||
provide: GoogleEmailAliasManagerService,
|
||||
useValue: {},
|
||||
@@ -96,7 +93,8 @@ describe('Email Alias Manager Service', () => {
|
||||
microsoftEmailAliasManagerService.getHandleAliases,
|
||||
).toHaveBeenCalledWith(mockConnectedAccount);
|
||||
|
||||
expect(connectedAccountRepository.update).toHaveBeenCalledWith(
|
||||
expect(mockConnectedAccountDataAccessService.update).toHaveBeenCalledWith(
|
||||
'test-workspace-id',
|
||||
{ id: mockConnectedAccount.id },
|
||||
{
|
||||
handleAliases: expectedAliases,
|
||||
|
||||
+4
-7
@@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service';
|
||||
@@ -15,6 +16,7 @@ export class EmailAliasManagerService {
|
||||
private readonly googleEmailAliasManagerService: GoogleEmailAliasManagerService,
|
||||
private readonly microsoftEmailAliasManagerService: MicrosoftEmailAliasManagerService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
|
||||
) {}
|
||||
|
||||
public async refreshHandleAliases(
|
||||
@@ -50,13 +52,8 @@ export class EmailAliasManagerService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
await connectedAccountRepository.update(
|
||||
await this.connectedAccountDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: connectedAccount.id },
|
||||
{
|
||||
handleAliases: handleAliases.join(','), // TODO: modify handleAliases to be of fieldmetadatatype array
|
||||
|
||||
@@ -5,6 +5,9 @@ import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { MessageQueueModule } from 'src/engine/core-modules/message-queue/message-queue.module';
|
||||
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
|
||||
import { CalendarChannelDataAccessModule } from 'src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module';
|
||||
import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module';
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module';
|
||||
@@ -19,6 +22,9 @@ import { ImapSmtpCalDavAPIService } from 'src/modules/connected-account/services
|
||||
TwentyORMModule,
|
||||
FeatureFlagModule,
|
||||
AuthModule,
|
||||
CalendarChannelDataAccessModule,
|
||||
ConnectedAccountDataAccessModule,
|
||||
MessageChannelDataAccessModule,
|
||||
],
|
||||
providers: [ImapSmtpCalDavAPIService],
|
||||
exports: [ImapSmtpCalDavAPIService],
|
||||
|
||||
+3
-8
@@ -1,9 +1,9 @@
|
||||
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 { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
export type DeleteWorkspaceMemberConnectedAccountsCleanupJobData = {
|
||||
workspaceId: string;
|
||||
@@ -14,6 +14,7 @@ export type DeleteWorkspaceMemberConnectedAccountsCleanupJobData = {
|
||||
export class DeleteWorkspaceMemberConnectedAccountsCleanupJob {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
|
||||
) {}
|
||||
|
||||
@Process(DeleteWorkspaceMemberConnectedAccountsCleanupJob.name)
|
||||
@@ -25,13 +26,7 @@ export class DeleteWorkspaceMemberConnectedAccountsCleanupJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
await connectedAccountRepository.delete({
|
||||
await this.connectedAccountDataAccessService.delete(workspaceId, {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
});
|
||||
}, authContext);
|
||||
|
||||
+3
-9
@@ -10,16 +10,16 @@ import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/wo
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.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';
|
||||
|
||||
@WorkspaceQueryHook(`connectedAccount.destroyOne`)
|
||||
export class ConnectedAccountDeleteOnePreQueryHook
|
||||
implements WorkspacePreQueryHookInstance
|
||||
{
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
) {}
|
||||
@@ -38,13 +38,7 @@ export class ConnectedAccountDeleteOnePreQueryHook
|
||||
const messageChannels =
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
return messageChannelRepository.findBy({
|
||||
return this.messageChannelDataAccessService.find(workspace.id, {
|
||||
connectedAccountId,
|
||||
});
|
||||
},
|
||||
|
||||
+2
@@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ConnectedAccountDeleteOnePreQueryHook } from 'src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook';
|
||||
|
||||
@@ -10,6 +11,7 @@ import { ConnectedAccountDeleteOnePreQueryHook } from 'src/modules/connected-acc
|
||||
imports: [
|
||||
NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
MessageChannelDataAccessModule,
|
||||
],
|
||||
providers: [ConnectedAccountDeleteOnePreQueryHook],
|
||||
})
|
||||
|
||||
+2
@@ -1,6 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { JwtModule } from 'src/engine/core-modules/jwt/jwt.module';
|
||||
import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module';
|
||||
import { GoogleAPIRefreshAccessTokenModule } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/google-api-refresh-access-token.module';
|
||||
import { MicrosoftAPIRefreshAccessTokenModule } from 'src/modules/connected-account/refresh-tokens-manager/drivers/microsoft/microsoft-api-refresh-access-token.module';
|
||||
import { ConnectedAccountRefreshTokensService } from 'src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service';
|
||||
@@ -8,6 +9,7 @@ import { ConnectedAccountRefreshTokensService } from 'src/modules/connected-acco
|
||||
@Module({
|
||||
imports: [
|
||||
JwtModule,
|
||||
ConnectedAccountDataAccessModule,
|
||||
GoogleAPIRefreshAccessTokenModule,
|
||||
MicrosoftAPIRefreshAccessTokenModule,
|
||||
],
|
||||
|
||||
+19
-21
@@ -2,6 +2,7 @@ import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.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';
|
||||
@@ -17,7 +18,7 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
let service: ConnectedAccountRefreshTokensService;
|
||||
let googleAPIRefreshAccessTokenService: GoogleAPIRefreshAccessTokenService;
|
||||
let microsoftAPIRefreshAccessTokenService: MicrosoftAPIRefreshAccessTokenService;
|
||||
let globalWorkspaceOrmManager: GlobalWorkspaceOrmManager;
|
||||
let connectedAccountDataAccessService: ConnectedAccountDataAccessService;
|
||||
|
||||
const mockWorkspaceId = 'workspace-123';
|
||||
const mockConnectedAccountId = 'account-456';
|
||||
@@ -44,13 +45,18 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
{
|
||||
provide: GlobalWorkspaceOrmManager,
|
||||
useValue: {
|
||||
getRepository: jest.fn(),
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: ConnectedAccountDataAccessService,
|
||||
useValue: {
|
||||
update: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -65,9 +71,10 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
module.get<MicrosoftAPIRefreshAccessTokenService>(
|
||||
MicrosoftAPIRefreshAccessTokenService,
|
||||
);
|
||||
globalWorkspaceOrmManager = module.get<GlobalWorkspaceOrmManager>(
|
||||
GlobalWorkspaceOrmManager,
|
||||
);
|
||||
connectedAccountDataAccessService =
|
||||
module.get<ConnectedAccountDataAccessService>(
|
||||
ConnectedAccountDataAccessService,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -96,7 +103,7 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
expect(
|
||||
microsoftAPIRefreshAccessTokenService.refreshTokens,
|
||||
).not.toHaveBeenCalled();
|
||||
expect(globalWorkspaceOrmManager.getRepository).not.toHaveBeenCalled();
|
||||
expect(connectedAccountDataAccessService.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should refresh and save new Microsoft token when expired (lastCredentialsRefreshedAt is old)', async () => {
|
||||
@@ -108,7 +115,6 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
lastCredentialsRefreshedAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago
|
||||
} as ConnectedAccountWorkspaceEntity;
|
||||
|
||||
const mockRepository = { update: jest.fn() };
|
||||
const newTokens = {
|
||||
accessToken: mockNewAccessToken,
|
||||
refreshToken: mockRefreshToken,
|
||||
@@ -117,9 +123,6 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
jest
|
||||
.spyOn(microsoftAPIRefreshAccessTokenService, 'refreshTokens')
|
||||
.mockResolvedValue(newTokens);
|
||||
jest
|
||||
.spyOn(globalWorkspaceOrmManager, 'getRepository')
|
||||
.mockResolvedValue(mockRepository as any);
|
||||
|
||||
const result = await service.refreshAndSaveTokens(
|
||||
connectedAccount,
|
||||
@@ -130,7 +133,8 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
expect(
|
||||
microsoftAPIRefreshAccessTokenService.refreshTokens,
|
||||
).toHaveBeenCalledWith(mockRefreshToken);
|
||||
expect(mockRepository.update).toHaveBeenCalledWith(
|
||||
expect(connectedAccountDataAccessService.update).toHaveBeenCalledWith(
|
||||
mockWorkspaceId,
|
||||
{ id: mockConnectedAccountId },
|
||||
expect.objectContaining({
|
||||
...newTokens,
|
||||
@@ -148,7 +152,6 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
lastCredentialsRefreshedAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago
|
||||
} as ConnectedAccountWorkspaceEntity;
|
||||
|
||||
const mockRepository = { update: jest.fn() };
|
||||
const newTokens = {
|
||||
accessToken: mockNewAccessToken,
|
||||
refreshToken: mockRefreshToken,
|
||||
@@ -157,9 +160,6 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
jest
|
||||
.spyOn(googleAPIRefreshAccessTokenService, 'refreshTokens')
|
||||
.mockResolvedValue(newTokens);
|
||||
jest
|
||||
.spyOn(globalWorkspaceOrmManager, 'getRepository')
|
||||
.mockResolvedValue(mockRepository as any);
|
||||
|
||||
const result = await service.refreshAndSaveTokens(
|
||||
connectedAccount,
|
||||
@@ -170,7 +170,8 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
expect(
|
||||
googleAPIRefreshAccessTokenService.refreshTokens,
|
||||
).toHaveBeenCalledWith(mockRefreshToken);
|
||||
expect(mockRepository.update).toHaveBeenCalledWith(
|
||||
expect(connectedAccountDataAccessService.update).toHaveBeenCalledWith(
|
||||
mockWorkspaceId,
|
||||
{ id: mockConnectedAccountId },
|
||||
expect.objectContaining({
|
||||
...newTokens,
|
||||
@@ -188,7 +189,6 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
lastCredentialsRefreshedAt: null,
|
||||
} as ConnectedAccountWorkspaceEntity;
|
||||
|
||||
const mockRepository = { update: jest.fn() };
|
||||
const newTokens = {
|
||||
accessToken: mockNewAccessToken,
|
||||
refreshToken: mockRefreshToken,
|
||||
@@ -197,9 +197,6 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
jest
|
||||
.spyOn(microsoftAPIRefreshAccessTokenService, 'refreshTokens')
|
||||
.mockResolvedValue(newTokens);
|
||||
jest
|
||||
.spyOn(globalWorkspaceOrmManager, 'getRepository')
|
||||
.mockResolvedValue(mockRepository as any);
|
||||
|
||||
const result = await service.refreshAndSaveTokens(
|
||||
connectedAccount,
|
||||
@@ -210,7 +207,8 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
expect(
|
||||
microsoftAPIRefreshAccessTokenService.refreshTokens,
|
||||
).toHaveBeenCalledWith(mockRefreshToken);
|
||||
expect(mockRepository.update).toHaveBeenCalledWith(
|
||||
expect(connectedAccountDataAccessService.update).toHaveBeenCalledWith(
|
||||
mockWorkspaceId,
|
||||
{ id: mockConnectedAccountId },
|
||||
expect.objectContaining({
|
||||
...newTokens,
|
||||
|
||||
+4
-7
@@ -3,6 +3,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service';
|
||||
@@ -30,6 +31,7 @@ export class ConnectedAccountRefreshTokensService {
|
||||
private readonly googleAPIRefreshAccessTokenService: GoogleAPIRefreshAccessTokenService,
|
||||
private readonly microsoftAPIRefreshAccessTokenService: MicrosoftAPIRefreshAccessTokenService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
|
||||
) {}
|
||||
|
||||
async refreshAndSaveTokens(
|
||||
@@ -78,13 +80,8 @@ export class ConnectedAccountRefreshTokensService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
await connectedAccountRepository.update(
|
||||
await this.connectedAccountDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: connectedAccount.id },
|
||||
{
|
||||
...connectedAccountTokens,
|
||||
|
||||
+56
-51
@@ -5,6 +5,9 @@ import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { CreateCalendarChannelService } from 'src/engine/core-modules/auth/services/create-calendar-channel.service';
|
||||
import { CreateMessageChannelService } from 'src/engine/core-modules/auth/services/create-message-channel.service';
|
||||
import { type EmailAccountConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto';
|
||||
import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
import { ImapSmtpCalDavAPIService } from 'src/modules/connected-account/services/imap-smtp-caldav-apis.service';
|
||||
@@ -18,19 +21,17 @@ jest.mock('uuid', () => ({
|
||||
describe('ImapSmtpCalDavAPIService', () => {
|
||||
let service: ImapSmtpCalDavAPIService;
|
||||
|
||||
const mockConnectedAccountRepository = {
|
||||
const mockConnectedAccountDataAccessService = {
|
||||
findOne: jest.fn(),
|
||||
save: jest.fn(),
|
||||
};
|
||||
|
||||
const mockMessageChannelRepository = {
|
||||
const mockMessageChannelDataAccessService = {
|
||||
findOne: jest.fn(),
|
||||
save: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCalendarChannelRepository = {
|
||||
const mockCalendarChannelDataAccessService = {
|
||||
findOne: jest.fn(),
|
||||
save: jest.fn(),
|
||||
};
|
||||
|
||||
const mockWorkspaceDataSource = {
|
||||
@@ -52,18 +53,6 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
{
|
||||
provide: GlobalWorkspaceOrmManager,
|
||||
useValue: {
|
||||
getRepository: jest
|
||||
.fn()
|
||||
.mockImplementation((_workspaceId, entity) => {
|
||||
if (entity === 'connectedAccount')
|
||||
return mockConnectedAccountRepository;
|
||||
if (entity === 'messageChannel')
|
||||
return mockMessageChannelRepository;
|
||||
if (entity === 'calendarChannel')
|
||||
return mockCalendarChannelRepository;
|
||||
|
||||
return {};
|
||||
}),
|
||||
getGlobalWorkspaceDataSource: jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockWorkspaceDataSource),
|
||||
@@ -81,6 +70,18 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
provide: CreateCalendarChannelService,
|
||||
useValue: mockCreateCalendarChannelService,
|
||||
},
|
||||
{
|
||||
provide: ConnectedAccountDataAccessService,
|
||||
useValue: mockConnectedAccountDataAccessService,
|
||||
},
|
||||
{
|
||||
provide: MessageChannelDataAccessService,
|
||||
useValue: mockMessageChannelDataAccessService,
|
||||
},
|
||||
{
|
||||
provide: CalendarChannelDataAccessService,
|
||||
useValue: mockCalendarChannelDataAccessService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -112,13 +113,14 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
};
|
||||
|
||||
it('should create new account with message channel when account does not exist and IMAP is configured', async () => {
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(baseInput);
|
||||
|
||||
expect(mockConnectedAccountRepository.save).toHaveBeenCalledWith(
|
||||
expect(mockConnectedAccountDataAccessService.save).toHaveBeenCalledWith(
|
||||
'workspace-id',
|
||||
{
|
||||
id: 'mocked-uuid',
|
||||
handle: 'test@example.com',
|
||||
@@ -127,7 +129,6 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
accountOwnerId: 'workspace-member-id',
|
||||
},
|
||||
{},
|
||||
{},
|
||||
);
|
||||
|
||||
expect(
|
||||
@@ -162,11 +163,13 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
connectedAccountId: 'existing-account-id',
|
||||
} as CalendarChannelWorkspaceEntity;
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(existingAccount);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(
|
||||
existingAccount,
|
||||
);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(
|
||||
existingMessageChannel,
|
||||
);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(
|
||||
existingCalendarChannel,
|
||||
);
|
||||
|
||||
@@ -177,7 +180,8 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
|
||||
await service.processAccount(inputWithConnectedAccountId);
|
||||
|
||||
expect(mockConnectedAccountRepository.save).toHaveBeenCalledWith(
|
||||
expect(mockConnectedAccountDataAccessService.save).toHaveBeenCalledWith(
|
||||
'workspace-id',
|
||||
{
|
||||
id: 'existing-account-id',
|
||||
handle: 'test@example.com',
|
||||
@@ -186,7 +190,6 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
accountOwnerId: 'workspace-member-id',
|
||||
},
|
||||
{},
|
||||
{},
|
||||
);
|
||||
|
||||
expect(
|
||||
@@ -210,9 +213,9 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
} as EmailAccountConnectionParameters,
|
||||
};
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(imapOnlyInput);
|
||||
|
||||
@@ -238,9 +241,9 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
} as EmailAccountConnectionParameters,
|
||||
};
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(caldavOnlyInput);
|
||||
|
||||
@@ -272,9 +275,9 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
} as EmailAccountConnectionParameters,
|
||||
};
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(imapSmtpInput);
|
||||
|
||||
@@ -313,9 +316,9 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
} as EmailAccountConnectionParameters,
|
||||
};
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(fullConfigInput);
|
||||
|
||||
@@ -335,23 +338,26 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV,
|
||||
} as ConnectedAccountWorkspaceEntity;
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValueOnce(
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValueOnce(
|
||||
existingAccount,
|
||||
);
|
||||
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(baseInput);
|
||||
|
||||
expect(mockConnectedAccountRepository.findOne).toHaveBeenCalledWith({
|
||||
expect(
|
||||
mockConnectedAccountDataAccessService.findOne,
|
||||
).toHaveBeenCalledWith('workspace-id', {
|
||||
where: {
|
||||
handle: 'test@example.com',
|
||||
accountOwnerId: 'workspace-member-id',
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockConnectedAccountRepository.save).toHaveBeenCalledWith(
|
||||
expect(mockConnectedAccountDataAccessService.save).toHaveBeenCalledWith(
|
||||
'workspace-id',
|
||||
{
|
||||
id: 'existing-account-id',
|
||||
handle: 'test@example.com',
|
||||
@@ -360,7 +366,6 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
accountOwnerId: 'workspace-member-id',
|
||||
},
|
||||
{},
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -378,9 +383,9 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
} as EmailAccountConnectionParameters,
|
||||
};
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(smtpOnlyInput);
|
||||
|
||||
@@ -393,9 +398,9 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
});
|
||||
|
||||
it('should handle transaction correctly', async () => {
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(baseInput);
|
||||
|
||||
|
||||
+17
-34
@@ -7,17 +7,21 @@ import { v4 } from 'uuid';
|
||||
import { CreateCalendarChannelService } from 'src/engine/core-modules/auth/services/create-calendar-channel.service';
|
||||
import { CreateMessageChannelService } from 'src/engine/core-modules/auth/services/create-message-channel.service';
|
||||
import { type EmailAccountConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto';
|
||||
import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class ImapSmtpCalDavAPIService {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
|
||||
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly createMessageChannelService: CreateMessageChannelService,
|
||||
private readonly createCalendarChannelService: CreateCalendarChannelService,
|
||||
) {}
|
||||
@@ -30,15 +34,12 @@ export class ImapSmtpCalDavAPIService {
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
const connectedAccount =
|
||||
await this.connectedAccountDataAccessService.findOne(workspaceId, {
|
||||
where: { id, provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV },
|
||||
});
|
||||
|
||||
return connectedAccountRepository.findOne({
|
||||
where: { id, provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV },
|
||||
});
|
||||
return connectedAccount as ConnectedAccountWorkspaceEntity | null;
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
@@ -58,29 +59,11 @@ export class ImapSmtpCalDavAPIService {
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const existingAccount = connectedAccountId
|
||||
? await connectedAccountRepository.findOne({
|
||||
? await this.connectedAccountDataAccessService.findOne(workspaceId, {
|
||||
where: { id: connectedAccountId },
|
||||
})
|
||||
: await connectedAccountRepository.findOne({
|
||||
: await this.connectedAccountDataAccessService.findOne(workspaceId, {
|
||||
where: { handle, accountOwnerId: workspaceMemberId },
|
||||
});
|
||||
|
||||
@@ -91,13 +74,13 @@ export class ImapSmtpCalDavAPIService {
|
||||
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
|
||||
|
||||
const existingMessageChannel = existingAccount
|
||||
? await messageChannelRepository.findOne({
|
||||
? await this.messageChannelDataAccessService.findOne(workspaceId, {
|
||||
where: { connectedAccountId: existingAccount.id },
|
||||
})
|
||||
: null;
|
||||
|
||||
const existingCalendarChannel = existingAccount
|
||||
? await calendarChannelRepository.findOne({
|
||||
? await this.calendarChannelDataAccessService.findOne(workspaceId, {
|
||||
where: { connectedAccountId: existingAccount.id },
|
||||
})
|
||||
: null;
|
||||
@@ -112,7 +95,8 @@ export class ImapSmtpCalDavAPIService {
|
||||
|
||||
await workspaceDataSource.transaction(
|
||||
async (manager: WorkspaceEntityManager) => {
|
||||
await connectedAccountRepository.save(
|
||||
await this.connectedAccountDataAccessService.save(
|
||||
workspaceId,
|
||||
{
|
||||
id: newOrExistingAccountId,
|
||||
handle,
|
||||
@@ -120,7 +104,6 @@ export class ImapSmtpCalDavAPIService {
|
||||
connectionParameters: input.connectionParameters,
|
||||
accountOwnerId: workspaceMemberId,
|
||||
},
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
|
||||
|
||||
+38
-27
@@ -1,19 +1,30 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
|
||||
import { type ObjectRecordCreateEvent } from 'twenty-shared/database-events';
|
||||
import { MessageParticipantRole } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { And, Any, ILike, In, IsNull, Not, Or } from 'typeorm';
|
||||
import { type ObjectRecordCreateEvent } from 'twenty-shared/database-events';
|
||||
import {
|
||||
And,
|
||||
Any,
|
||||
type FindManyOptions,
|
||||
ILike,
|
||||
In,
|
||||
IsNull,
|
||||
Not,
|
||||
Or,
|
||||
} from 'typeorm';
|
||||
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
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 { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
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';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service';
|
||||
|
||||
export type BlocklistItemDeleteMessagesJobData = WorkspaceEventBatch<
|
||||
@@ -28,6 +39,7 @@ export class BlocklistItemDeleteMessagesJob {
|
||||
constructor(
|
||||
private readonly threadCleanerService: MessagingMessageCleanerService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
) {}
|
||||
|
||||
@Process(BlocklistItemDeleteMessagesJob.name)
|
||||
@@ -72,12 +84,6 @@ export class BlocklistItemDeleteMessagesJob {
|
||||
new Map<string, string[]>(),
|
||||
);
|
||||
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannelMessageAssociationRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
|
||||
workspaceId,
|
||||
@@ -97,30 +103,35 @@ export class BlocklistItemDeleteMessagesJob {
|
||||
MessageParticipantRole.TO,
|
||||
] as const;
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
select: {
|
||||
id: true,
|
||||
handle: true,
|
||||
connectedAccount: {
|
||||
handleAliases: true,
|
||||
const messageChannels =
|
||||
await this.messageChannelDataAccessService.findMany(workspaceId, {
|
||||
select: {
|
||||
id: true,
|
||||
handle: true,
|
||||
connectedAccount: {
|
||||
handleAliases: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
deletedAt: IsNull(),
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
},
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
});
|
||||
relations: ['connectedAccount'],
|
||||
} as FindManyOptions<MessageChannelWorkspaceEntity>);
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
const messageChannelHandles = [messageChannel.handle];
|
||||
|
||||
if (isDefined(messageChannel.connectedAccount.handleAliases)) {
|
||||
messageChannelHandles.push(
|
||||
...messageChannel.connectedAccount.handleAliases.split(','),
|
||||
);
|
||||
const handleAliases = messageChannel.connectedAccount?.handleAliases;
|
||||
|
||||
if (isDefined(handleAliases)) {
|
||||
const aliasList: string[] = Array.isArray(handleAliases)
|
||||
? handleAliases
|
||||
: (handleAliases as string).split(',');
|
||||
|
||||
messageChannelHandles.push(...aliasList);
|
||||
}
|
||||
|
||||
const handleConditions = handles.map((handle) => {
|
||||
|
||||
+7
-14
@@ -6,15 +6,13 @@ import { type ObjectRecordDeleteEvent } from 'twenty-shared/database-events';
|
||||
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 { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
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';
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
export type BlocklistReimportMessagesJobData = WorkspaceEventBatch<
|
||||
ObjectRecordDeleteEvent<BlocklistWorkspaceEntity>
|
||||
@@ -27,6 +25,7 @@ export type BlocklistReimportMessagesJobData = WorkspaceEventBatch<
|
||||
export class BlocklistReimportMessagesJob {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messagingChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
) {}
|
||||
|
||||
@@ -37,25 +36,19 @@ export class BlocklistReimportMessagesJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
for (const eventPayload of data.events) {
|
||||
const workspaceMemberId =
|
||||
eventPayload.properties.before.workspaceMemberId;
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
select: ['id'],
|
||||
where: {
|
||||
const messageChannels = await this.messageChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
},
|
||||
syncStage: Not(MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING),
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
await this.messagingChannelSyncStatusService.resetAndMarkAsMessagesListFetchPending(
|
||||
messageChannels.map((messageChannel) => messageChannel.id),
|
||||
|
||||
+6
-1
@@ -1,5 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { BlocklistItemDeleteMessagesJob } from 'src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job';
|
||||
import { BlocklistReimportMessagesJob } from 'src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-reimport-messages.job';
|
||||
import { MessagingBlocklistListener } from 'src/modules/messaging/blocklist-manager/listeners/messaging-blocklist.listener';
|
||||
@@ -7,7 +8,11 @@ import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-co
|
||||
import { MessagingMessageCleanerModule } from 'src/modules/messaging/message-cleaner/messaging-message-cleaner.module';
|
||||
|
||||
@Module({
|
||||
imports: [MessagingCommonModule, MessagingMessageCleanerModule],
|
||||
imports: [
|
||||
MessagingCommonModule,
|
||||
MessagingMessageCleanerModule,
|
||||
MessageChannelDataAccessModule,
|
||||
],
|
||||
providers: [
|
||||
MessagingBlocklistListener,
|
||||
BlocklistItemDeleteMessagesJob,
|
||||
|
||||
@@ -3,6 +3,9 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
|
||||
import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module';
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { MessageFolderDataAccessModule } from 'src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
@@ -12,6 +15,9 @@ import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/se
|
||||
WorkspaceDataSourceModule,
|
||||
TypeOrmModule.forFeature([FeatureFlagEntity]),
|
||||
ConnectedAccountModule,
|
||||
ConnectedAccountDataAccessModule,
|
||||
MessageChannelDataAccessModule,
|
||||
MessageFolderDataAccessModule,
|
||||
MetricsModule,
|
||||
],
|
||||
providers: [MessageChannelSyncStatusService],
|
||||
|
||||
+16
-14
@@ -2,6 +2,7 @@ import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants';
|
||||
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.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';
|
||||
@@ -34,22 +35,19 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
find: jest.fn(),
|
||||
};
|
||||
|
||||
const mockConnectedAccountRepository = {
|
||||
find: jest.fn(),
|
||||
};
|
||||
|
||||
const mockWorkspaceMemberRepository = {
|
||||
findOneByOrFail: jest.fn(),
|
||||
};
|
||||
|
||||
const mockConnectedAccountDataAccessService = {
|
||||
find: jest.fn(),
|
||||
};
|
||||
|
||||
const mockGlobalWorkspaceOrmManager = {
|
||||
getRepository: jest.fn().mockImplementation((workspaceId, name) => {
|
||||
if (name === 'messageChannelMessageAssociation') {
|
||||
return mockMessageChannelMessageAssociationRepository;
|
||||
}
|
||||
if (name === 'connectedAccount') {
|
||||
return mockConnectedAccountRepository;
|
||||
}
|
||||
if (name === 'workspaceMember') {
|
||||
return mockWorkspaceMemberRepository;
|
||||
}
|
||||
@@ -67,6 +65,10 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
provide: GlobalWorkspaceOrmManager,
|
||||
useValue: mockGlobalWorkspaceOrmManager,
|
||||
},
|
||||
{
|
||||
provide: ConnectedAccountDataAccessService,
|
||||
useValue: mockConnectedAccountDataAccessService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -105,7 +107,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
item.subject === 'Test Subject' && item.text === 'Test Message',
|
||||
),
|
||||
).toBe(true);
|
||||
expect(mockConnectedAccountRepository.find).not.toHaveBeenCalled();
|
||||
expect(mockConnectedAccountDataAccessService.find).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return message without obfuscated subject and with obfuscated text if the visibility is SUBJECT', async () => {
|
||||
@@ -123,7 +125,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
mockConnectedAccountRepository.find.mockResolvedValue([]);
|
||||
mockConnectedAccountDataAccessService.find.mockResolvedValue([]);
|
||||
|
||||
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
|
||||
id: 'workspace-member-id',
|
||||
@@ -158,7 +160,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
mockConnectedAccountRepository.find.mockResolvedValue([]);
|
||||
mockConnectedAccountDataAccessService.find.mockResolvedValue([]);
|
||||
|
||||
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
|
||||
id: 'workspace-member-id',
|
||||
@@ -198,7 +200,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
id: 'workspace-member-account-owner-id',
|
||||
});
|
||||
|
||||
mockConnectedAccountRepository.find.mockResolvedValue([{ id: '1' }]);
|
||||
mockConnectedAccountDataAccessService.find.mockResolvedValue([{ id: '1' }]);
|
||||
|
||||
const result = await service.applyMessagesVisibilityRestrictions(
|
||||
messages,
|
||||
@@ -233,7 +235,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
id: 'workspace-member-not-account-owner-id',
|
||||
});
|
||||
|
||||
mockConnectedAccountRepository.find.mockResolvedValue([]);
|
||||
mockConnectedAccountDataAccessService.find.mockResolvedValue([]);
|
||||
|
||||
const result = await service.applyMessagesVisibilityRestrictions(
|
||||
messages,
|
||||
@@ -279,7 +281,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
id: 'workspace-member-id',
|
||||
});
|
||||
|
||||
mockConnectedAccountRepository.find
|
||||
mockConnectedAccountDataAccessService.find
|
||||
.mockResolvedValueOnce([]) // request for message 3
|
||||
.mockResolvedValueOnce([]); // request for message 2
|
||||
|
||||
@@ -338,7 +340,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
id: 'workspace-member-id',
|
||||
});
|
||||
|
||||
mockConnectedAccountRepository.find
|
||||
mockConnectedAccountDataAccessService.find
|
||||
.mockResolvedValueOnce([]) // request for message 3
|
||||
.mockResolvedValueOnce([]); // request for message 2
|
||||
|
||||
|
||||
+5
-12
@@ -6,9 +6,9 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { In } from 'typeorm';
|
||||
|
||||
import { NotFoundError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
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';
|
||||
import { type MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity';
|
||||
@@ -18,6 +18,7 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta
|
||||
export class ApplyMessagesVisibilityRestrictionsService {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
|
||||
) {}
|
||||
|
||||
public async applyMessagesVisibilityRestrictions(
|
||||
@@ -43,12 +44,6 @@ export class ApplyMessagesVisibilityRestrictionsService {
|
||||
relations: ['messageChannel'],
|
||||
});
|
||||
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
@@ -91,15 +86,13 @@ export class ApplyMessagesVisibilityRestrictionsService {
|
||||
userId,
|
||||
});
|
||||
|
||||
const connectedAccounts = await connectedAccountRepository.find({
|
||||
select: ['id'],
|
||||
where: {
|
||||
const connectedAccounts =
|
||||
await this.connectedAccountDataAccessService.find(workspaceId, {
|
||||
messageChannels: {
|
||||
id: In(messageChannels.map((channel) => channel.id)),
|
||||
},
|
||||
accountOwnerId: workspaceMember.id,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
if (connectedAccounts.length > 0) {
|
||||
continue;
|
||||
|
||||
+25
-30
@@ -14,6 +14,8 @@ import {
|
||||
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
|
||||
import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import {
|
||||
@@ -21,10 +23,7 @@ import {
|
||||
MessageChannelSyncStage,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessageFolderPendingSyncAction,
|
||||
type MessageFolderWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessagingProcessGroupEmailActionsService } from 'src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service';
|
||||
|
||||
const ONGOING_SYNC_STAGES = [
|
||||
@@ -41,6 +40,8 @@ export class MessageChannelUpdateOnePreQueryHook
|
||||
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messageFolderDataAccessService: MessageFolderDataAccessService,
|
||||
private readonly messagingProcessGroupEmailActionsService: MessagingProcessGroupEmailActionsService,
|
||||
) {}
|
||||
|
||||
@@ -57,15 +58,10 @@ export class MessageChannelUpdateOnePreQueryHook
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannel = await messageChannelRepository.findOne({
|
||||
where: { id: payload.id },
|
||||
});
|
||||
const messageChannel =
|
||||
await this.messageChannelDataAccessService.findOne(workspace.id, {
|
||||
where: { id: payload.id },
|
||||
});
|
||||
|
||||
if (!isDefined(messageChannel)) {
|
||||
throw new WorkspaceQueryRunnerException(
|
||||
@@ -77,29 +73,27 @@ export class MessageChannelUpdateOnePreQueryHook
|
||||
);
|
||||
}
|
||||
|
||||
const messageChannelWorkspace =
|
||||
messageChannel as unknown as MessageChannelWorkspaceEntity;
|
||||
|
||||
const isSyncOngoing = ONGOING_SYNC_STAGES.includes(
|
||||
messageChannel.syncStage,
|
||||
messageChannelWorkspace.syncStage,
|
||||
);
|
||||
|
||||
const messageFolderRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageFolderWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'messageFolder',
|
||||
);
|
||||
const messageFoldersWithPendingAction =
|
||||
await this.messageFolderDataAccessService.find(workspace.id, {
|
||||
messageChannelId: messageChannel.id,
|
||||
pendingSyncAction: Not(MessageFolderPendingSyncAction.NONE),
|
||||
});
|
||||
|
||||
const messageFoldersWithPendingActionCount =
|
||||
await messageFolderRepository.count({
|
||||
where: {
|
||||
messageChannelId: messageChannel.id,
|
||||
pendingSyncAction: Not(MessageFolderPendingSyncAction.NONE),
|
||||
},
|
||||
});
|
||||
messageFoldersWithPendingAction.length;
|
||||
|
||||
const hasPendingFolderActions =
|
||||
messageFoldersWithPendingActionCount > 0;
|
||||
|
||||
const hasPendingGroupEmailsAction =
|
||||
messageChannel.pendingGroupEmailsAction !==
|
||||
messageChannelWorkspace.pendingGroupEmailsAction !==
|
||||
MessageChannelPendingGroupEmailsAction.NONE;
|
||||
|
||||
if (
|
||||
@@ -116,12 +110,12 @@ export class MessageChannelUpdateOnePreQueryHook
|
||||
}
|
||||
|
||||
const hasCompletedConfiguration =
|
||||
messageChannel.syncStage !==
|
||||
messageChannelWorkspace.syncStage !==
|
||||
MessageChannelSyncStage.PENDING_CONFIGURATION;
|
||||
|
||||
if (!hasCompletedConfiguration) {
|
||||
this.logger.log(
|
||||
`MessageChannelId: ${messageChannel.id} - Skipping pending action for message channel in PENDING_CONFIGURATION state`,
|
||||
`MessageChannelId: ${messageChannelWorkspace.id} - Skipping pending action for message channel in PENDING_CONFIGURATION state`,
|
||||
);
|
||||
|
||||
return payload;
|
||||
@@ -129,11 +123,12 @@ export class MessageChannelUpdateOnePreQueryHook
|
||||
|
||||
const excludeGroupEmailsChanged =
|
||||
isDefined(payload.data.excludeGroupEmails) &&
|
||||
payload.data.excludeGroupEmails !== messageChannel.excludeGroupEmails;
|
||||
payload.data.excludeGroupEmails !==
|
||||
messageChannelWorkspace.excludeGroupEmails;
|
||||
|
||||
if (excludeGroupEmailsChanged) {
|
||||
await this.messagingProcessGroupEmailActionsService.markMessageChannelAsPendingGroupEmailsAction(
|
||||
messageChannel,
|
||||
messageChannelWorkspace,
|
||||
workspace.id,
|
||||
payload.data.excludeGroupEmails
|
||||
? MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_DELETION
|
||||
|
||||
+9
-1
@@ -1,5 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module';
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { MessageFolderDataAccessModule } from 'src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module';
|
||||
import { ApplyMessagesVisibilityRestrictionsService } from 'src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service';
|
||||
import { MessageChannelUpdateOnePreQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-channel-update-one.pre-query.hook';
|
||||
import { MessageFindManyPostQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-find-many.post-query.hook';
|
||||
@@ -7,7 +10,12 @@ import { MessageFindOnePostQueryHook } from 'src/modules/messaging/common/query-
|
||||
import { MessagingImportManagerModule } from 'src/modules/messaging/message-import-manager/messaging-import-manager.module';
|
||||
|
||||
@Module({
|
||||
imports: [MessagingImportManagerModule],
|
||||
imports: [
|
||||
MessagingImportManagerModule,
|
||||
ConnectedAccountDataAccessModule,
|
||||
MessageChannelDataAccessModule,
|
||||
MessageFolderDataAccessModule,
|
||||
],
|
||||
providers: [
|
||||
ApplyMessagesVisibilityRestrictionsService,
|
||||
MessageFindOnePostQueryHook,
|
||||
|
||||
+132
-148
@@ -7,10 +7,12 @@ import { CacheStorageService } from 'src/engine/core-modules/cache-storage/servi
|
||||
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
|
||||
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type';
|
||||
import {
|
||||
MessageChannelPendingGroupEmailsAction,
|
||||
@@ -18,10 +20,8 @@ import {
|
||||
MessageChannelSyncStatus,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessageFolderPendingSyncAction,
|
||||
MessageFolderWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class MessageChannelSyncStatusService {
|
||||
@@ -29,6 +29,9 @@ export class MessageChannelSyncStatusService {
|
||||
@InjectCacheStorage(CacheStorageNamespace.ModuleMessaging)
|
||||
private readonly cacheStorage: CacheStorageService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messageFolderDataAccessService: MessageFolderDataAccessService,
|
||||
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
|
||||
private readonly accountsToReconnectService: AccountsToReconnectService,
|
||||
private readonly metricsService: MetricsService,
|
||||
) {}
|
||||
@@ -45,16 +48,14 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
});
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
@@ -70,16 +71,14 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
});
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
@@ -100,27 +99,20 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
{
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
throttleFailureCount: 0,
|
||||
throttleRetryAfter: null,
|
||||
pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction.NONE,
|
||||
},
|
||||
);
|
||||
|
||||
const messageFolderRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageFolderWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageFolder',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
throttleFailureCount: 0,
|
||||
throttleRetryAfter: null,
|
||||
pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction.NONE,
|
||||
});
|
||||
|
||||
await messageFolderRepository.update(
|
||||
await this.messageFolderDataAccessService.update(
|
||||
workspaceId,
|
||||
{ messageChannelId: In(messageChannelIds) },
|
||||
{
|
||||
syncCursor: '',
|
||||
@@ -143,15 +135,11 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStageStartedAt: null,
|
||||
});
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
{ syncStageStartedAt: null },
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
@@ -166,17 +154,15 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
@@ -191,17 +177,15 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
@@ -216,20 +200,18 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStatus: MessageChannelSyncStatus.ACTIVE,
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
throttleFailureCount: 0,
|
||||
throttleRetryAfter: null,
|
||||
syncStageStartedAt: null,
|
||||
syncedAt: new Date().toISOString(),
|
||||
});
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
{
|
||||
syncStatus: MessageChannelSyncStatus.ACTIVE,
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
throttleFailureCount: 0,
|
||||
throttleRetryAfter: null,
|
||||
syncStageStartedAt: null,
|
||||
syncedAt: new Date().toISOString(),
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
|
||||
await this.metricsService.batchIncrementCounter({
|
||||
@@ -249,15 +231,13 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED,
|
||||
});
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED,
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
@@ -272,17 +252,15 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
|
||||
@@ -300,17 +278,15 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(messageChannelIds, {
|
||||
syncStage: MessageChannelSyncStage.FAILED,
|
||||
syncStatus: syncStatus,
|
||||
throttleRetryAfter: null,
|
||||
});
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.FAILED,
|
||||
syncStatus: syncStatus,
|
||||
throttleRetryAfter: null,
|
||||
},
|
||||
);
|
||||
|
||||
const metricsKey =
|
||||
syncStatus === MessageChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS
|
||||
@@ -325,22 +301,17 @@ export class MessageChannelSyncStatusService {
|
||||
if (
|
||||
syncStatus === MessageChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS
|
||||
) {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
select: ['id', 'connectedAccountId'],
|
||||
where: { id: Any(messageChannelIds) },
|
||||
});
|
||||
const messageChannels = await this.messageChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
);
|
||||
|
||||
const connectedAccountIds = messageChannels.map(
|
||||
(messageChannel) => messageChannel.connectedAccountId,
|
||||
);
|
||||
|
||||
await connectedAccountRepository.update(
|
||||
await this.connectedAccountDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: Any(connectedAccountIds) },
|
||||
{
|
||||
authFailedAt: new Date(),
|
||||
@@ -363,30 +334,43 @@ export class MessageChannelSyncStatusService {
|
||||
return;
|
||||
}
|
||||
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
const messageChannels = await this.messageChannelDataAccessService.findMany(
|
||||
workspaceId,
|
||||
{
|
||||
where: { id: In(messageChannelIds) },
|
||||
},
|
||||
);
|
||||
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
'workspaceMember',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: { id: Any(messageChannelIds) },
|
||||
relations: {
|
||||
connectedAccount: {
|
||||
accountOwner: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
const userId = messageChannel.connectedAccount.accountOwner.userId;
|
||||
const connectedAccountId = messageChannel.connectedAccount.id;
|
||||
const connectedAccount =
|
||||
await this.connectedAccountDataAccessService.findOne(workspaceId, {
|
||||
where: { id: messageChannel.connectedAccountId },
|
||||
});
|
||||
|
||||
if (!connectedAccount) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const workspaceMember = await workspaceMemberRepository.findOne({
|
||||
where: { id: connectedAccount.accountOwnerId },
|
||||
});
|
||||
|
||||
if (!workspaceMember) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await this.accountsToReconnectService.addAccountToReconnectByKey(
|
||||
AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS,
|
||||
userId,
|
||||
workspaceMember.userId,
|
||||
workspaceId,
|
||||
connectedAccountId,
|
||||
connectedAccount.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+19
-47
@@ -1,6 +1,15 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
MessageChannelContactAutoCreationPolicy,
|
||||
MessageChannelPendingGroupEmailsAction,
|
||||
MessageChannelSyncStage,
|
||||
MessageChannelSyncStatus,
|
||||
MessageChannelType,
|
||||
MessageChannelVisibility,
|
||||
MessageFolderImportPolicy,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { type FieldTypeAndNameMetadata } from 'src/engine/workspace-manager/utils/get-ts-vector-column-expression.util';
|
||||
@@ -9,52 +18,15 @@ import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-acco
|
||||
import { type MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
|
||||
import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
|
||||
export enum MessageChannelSyncStatus {
|
||||
NOT_SYNCED = 'NOT_SYNCED',
|
||||
ONGOING = 'ONGOING',
|
||||
ACTIVE = 'ACTIVE',
|
||||
FAILED_INSUFFICIENT_PERMISSIONS = 'FAILED_INSUFFICIENT_PERMISSIONS',
|
||||
FAILED_UNKNOWN = 'FAILED_UNKNOWN',
|
||||
}
|
||||
|
||||
export enum MessageChannelSyncStage {
|
||||
PENDING_CONFIGURATION = 'PENDING_CONFIGURATION',
|
||||
MESSAGE_LIST_FETCH_PENDING = 'MESSAGE_LIST_FETCH_PENDING',
|
||||
MESSAGE_LIST_FETCH_SCHEDULED = 'MESSAGE_LIST_FETCH_SCHEDULED',
|
||||
MESSAGE_LIST_FETCH_ONGOING = 'MESSAGE_LIST_FETCH_ONGOING',
|
||||
MESSAGES_IMPORT_PENDING = 'MESSAGES_IMPORT_PENDING',
|
||||
MESSAGES_IMPORT_SCHEDULED = 'MESSAGES_IMPORT_SCHEDULED',
|
||||
MESSAGES_IMPORT_ONGOING = 'MESSAGES_IMPORT_ONGOING',
|
||||
FAILED = 'FAILED',
|
||||
}
|
||||
|
||||
export enum MessageChannelVisibility {
|
||||
METADATA = 'METADATA',
|
||||
SUBJECT = 'SUBJECT',
|
||||
SHARE_EVERYTHING = 'SHARE_EVERYTHING',
|
||||
}
|
||||
|
||||
export enum MessageChannelType {
|
||||
EMAIL = 'EMAIL',
|
||||
SMS = 'SMS',
|
||||
}
|
||||
|
||||
export enum MessageChannelContactAutoCreationPolicy {
|
||||
SENT_AND_RECEIVED = 'SENT_AND_RECEIVED',
|
||||
SENT = 'SENT',
|
||||
NONE = 'NONE',
|
||||
}
|
||||
|
||||
export enum MessageFolderImportPolicy {
|
||||
ALL_FOLDERS = 'ALL_FOLDERS',
|
||||
SELECTED_FOLDERS = 'SELECTED_FOLDERS',
|
||||
}
|
||||
|
||||
export enum MessageChannelPendingGroupEmailsAction {
|
||||
GROUP_EMAILS_DELETION = 'GROUP_EMAILS_DELETION',
|
||||
GROUP_EMAILS_IMPORT = 'GROUP_EMAILS_IMPORT',
|
||||
NONE = 'NONE',
|
||||
}
|
||||
export {
|
||||
MessageChannelContactAutoCreationPolicy,
|
||||
MessageChannelPendingGroupEmailsAction,
|
||||
MessageChannelSyncStage,
|
||||
MessageChannelSyncStatus,
|
||||
MessageChannelType,
|
||||
MessageChannelVisibility,
|
||||
MessageFolderImportPolicy,
|
||||
};
|
||||
|
||||
registerEnumType(MessageChannelVisibility, {
|
||||
name: 'MessageChannelVisibility',
|
||||
|
||||
+5
-5
@@ -1,6 +1,9 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
MessageFolderPendingSyncAction,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { type FieldTypeAndNameMetadata } from 'src/engine/workspace-manager/utils/get-ts-vector-column-expression.util';
|
||||
@@ -8,10 +11,7 @@ import { type EntityRelation } from 'src/engine/workspace-manager/workspace-migr
|
||||
import { type MessageChannelMessageAssociationMessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association-message-folder.workspace-entity';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
export enum MessageFolderPendingSyncAction {
|
||||
FOLDER_DELETION = 'FOLDER_DELETION',
|
||||
NONE = 'NONE',
|
||||
}
|
||||
export { MessageFolderPendingSyncAction };
|
||||
|
||||
registerEnumType(MessageFolderPendingSyncAction, {
|
||||
name: 'MessageFolderPendingSyncAction',
|
||||
|
||||
+6
-12
@@ -3,10 +3,10 @@ import { Logger } from '@nestjs/common';
|
||||
import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
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 { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service';
|
||||
|
||||
type MessagingResetChannelCommandOptions = {
|
||||
@@ -24,6 +24,7 @@ export class MessagingResetChannelCommand extends CommandRunner {
|
||||
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messagingChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
private readonly messagingMessageCleanerService: MessagingMessageCleanerService,
|
||||
) {
|
||||
@@ -39,21 +40,14 @@ export class MessagingResetChannelCommand extends CommandRunner {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`No message channel ID provided, resetting all message channels in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: {
|
||||
...(isDefined(messageChannelId) ? { id: messageChannelId } : {}),
|
||||
},
|
||||
});
|
||||
const messageChannels = await this.messageChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
isDefined(messageChannelId) ? { id: messageChannelId } : {},
|
||||
);
|
||||
|
||||
if (messageChannels.length === 0) {
|
||||
this.logger.log(
|
||||
|
||||
+2
@@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module';
|
||||
import { MessagingMessageCleanerRemoveOrphansCommand } from 'src/modules/messaging/message-cleaner/commands/messaging-message-clearner-remove-orphans.command';
|
||||
import { MessagingResetChannelCommand } from 'src/modules/messaging/message-cleaner/commands/messaging-reset-channel.command';
|
||||
@@ -15,6 +16,7 @@ import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cl
|
||||
TypeOrmModule.forFeature([WorkspaceEntity]),
|
||||
DataSourceModule,
|
||||
MessagingCommonModule,
|
||||
MessageChannelDataAccessModule,
|
||||
],
|
||||
providers: [
|
||||
MessagingConnectedAccountDeletionCleanupJob,
|
||||
|
||||
+2
@@ -4,6 +4,7 @@ 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 { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { MessageFolderDataAccessModule } from 'src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-client-manager/oauth2-client-manager.module';
|
||||
import { GmailFoldersErrorHandlerService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-folders-error-handler.service';
|
||||
@@ -20,6 +21,7 @@ import { MessagingMicrosoftDriverModule } from 'src/modules/messaging/message-im
|
||||
FeatureFlagModule,
|
||||
WorkspaceDataSourceModule,
|
||||
DataSourceModule,
|
||||
MessageFolderDataAccessModule,
|
||||
TypeOrmModule.forFeature([WorkspaceEntity]),
|
||||
OAuth2ClientManagerModule,
|
||||
MessagingGmailDriverModule,
|
||||
|
||||
+115
-103
@@ -1,9 +1,11 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { In } from 'typeorm';
|
||||
|
||||
import { type DiscoveredMessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import {
|
||||
MessageChannelContactAutoCreationPolicy,
|
||||
@@ -83,39 +85,73 @@ const createMockExistingFolder = (
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const getInValuesFromWhere = (externalIdClause: unknown): string[] => {
|
||||
if (
|
||||
externalIdClause &&
|
||||
typeof externalIdClause === 'object' &&
|
||||
'_value' in externalIdClause
|
||||
) {
|
||||
return (externalIdClause as { _value: string[] })._value;
|
||||
}
|
||||
|
||||
if (
|
||||
externalIdClause &&
|
||||
typeof externalIdClause === 'object' &&
|
||||
'value' in externalIdClause
|
||||
) {
|
||||
const value = (externalIdClause as { value: unknown }).value;
|
||||
|
||||
return Array.isArray(value) ? value : [];
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
describe('SyncMessageFoldersService', () => {
|
||||
let service: SyncMessageFoldersService;
|
||||
let gmailGetAllFoldersService: jest.Mocked<GmailGetAllFoldersService>;
|
||||
|
||||
let mockRepository: {
|
||||
let mockMessageFolderDataAccessService: {
|
||||
delete: jest.Mock;
|
||||
update: jest.Mock;
|
||||
updateMany: jest.Mock;
|
||||
save: jest.Mock;
|
||||
find: jest.Mock;
|
||||
};
|
||||
let mockTransactionManager: object;
|
||||
|
||||
let createdFolderRecords: Array<
|
||||
Partial<MessageFolderWorkspaceEntity> & {
|
||||
id: string;
|
||||
externalId: string;
|
||||
}
|
||||
>;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockRepository = {
|
||||
createdFolderRecords = [];
|
||||
|
||||
mockMessageFolderDataAccessService = {
|
||||
delete: jest.fn(),
|
||||
update: jest.fn(),
|
||||
updateMany: jest.fn(),
|
||||
save: jest.fn().mockImplementation((folders) =>
|
||||
folders.map((folder: Partial<MessageFolderWorkspaceEntity>) => ({
|
||||
update: jest.fn().mockResolvedValue(undefined),
|
||||
save: jest.fn().mockImplementation(async (_workspaceId, folder) => {
|
||||
createdFolderRecords.push({
|
||||
...folder,
|
||||
id: `new-folder-${Math.random().toString(36).substring(7)}`,
|
||||
id: `new-folder-${createdFolderRecords.length}-${Math.random().toString(36).substring(7)}`,
|
||||
isSynced: false,
|
||||
syncCursor: null,
|
||||
})),
|
||||
),
|
||||
};
|
||||
pendingSyncAction: MessageFolderPendingSyncAction.NONE,
|
||||
externalId: folder.externalId as string,
|
||||
});
|
||||
}),
|
||||
find: jest.fn().mockImplementation(async (_workspaceId, where) => {
|
||||
if (!where?.externalId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
mockTransactionManager = {};
|
||||
const externalIds = getInValuesFromWhere(where.externalId);
|
||||
|
||||
const mockDataSource = {
|
||||
transaction: jest
|
||||
.fn()
|
||||
.mockImplementation((callback) => callback(mockTransactionManager)),
|
||||
return createdFolderRecords.filter((folder) =>
|
||||
externalIds.includes(folder.externalId as string),
|
||||
);
|
||||
}),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@@ -129,15 +165,15 @@ describe('SyncMessageFoldersService', () => {
|
||||
.mockImplementation((callback: () => any, _authContext?: any) =>
|
||||
callback(),
|
||||
),
|
||||
getRepository: jest.fn().mockResolvedValue(mockRepository),
|
||||
getDataSourceForWorkspace: jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockDataSource),
|
||||
getGlobalWorkspaceDataSource: jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockDataSource),
|
||||
getRepository: jest.fn(),
|
||||
getDataSourceForWorkspace: jest.fn(),
|
||||
getGlobalWorkspaceDataSource: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MessageFolderDataAccessService,
|
||||
useValue: mockMessageFolderDataAccessService,
|
||||
},
|
||||
{
|
||||
provide: GmailGetAllFoldersService,
|
||||
useValue: {
|
||||
@@ -192,23 +228,23 @@ describe('SyncMessageFoldersService', () => {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(mockRepository.save).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: 'INBOX',
|
||||
externalId: 'inbox-ext',
|
||||
messageChannelId: 'channel-123',
|
||||
isSentFolder: false,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: 'Sent',
|
||||
externalId: 'sent-ext',
|
||||
messageChannelId: 'channel-123',
|
||||
isSentFolder: true,
|
||||
}),
|
||||
]),
|
||||
{},
|
||||
mockTransactionManager,
|
||||
expect(mockMessageFolderDataAccessService.save).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
expect.objectContaining({
|
||||
name: 'INBOX',
|
||||
externalId: 'inbox-ext',
|
||||
messageChannelId: 'channel-123',
|
||||
isSentFolder: false,
|
||||
}),
|
||||
);
|
||||
expect(mockMessageFolderDataAccessService.save).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
expect.objectContaining({
|
||||
name: 'Sent',
|
||||
externalId: 'sent-ext',
|
||||
messageChannelId: 'channel-123',
|
||||
isSentFolder: true,
|
||||
}),
|
||||
);
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
@@ -239,15 +275,12 @@ describe('SyncMessageFoldersService', () => {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(mockRepository.save).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: 'Projects',
|
||||
parentFolderId: 'parent-folder-id',
|
||||
}),
|
||||
]),
|
||||
{},
|
||||
mockTransactionManager,
|
||||
expect(mockMessageFolderDataAccessService.save).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
expect.objectContaining({
|
||||
name: 'Projects',
|
||||
parentFolderId: 'parent-folder-id',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -278,14 +311,10 @@ describe('SyncMessageFoldersService', () => {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(mockRepository.updateMany).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
criteria: 'folder-1',
|
||||
partialEntity: expect.objectContaining({ name: 'Primary Inbox' }),
|
||||
}),
|
||||
]),
|
||||
mockTransactionManager,
|
||||
expect(mockMessageFolderDataAccessService.update).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
{ id: 'folder-1' },
|
||||
expect.objectContaining({ name: 'Primary Inbox' }),
|
||||
);
|
||||
expect(result).toContainEqual(
|
||||
expect.objectContaining({
|
||||
@@ -322,16 +351,12 @@ describe('SyncMessageFoldersService', () => {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(mockRepository.updateMany).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
criteria: 'folder-1',
|
||||
partialEntity: expect.objectContaining({
|
||||
parentFolderId: 'new-parent-id',
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
mockTransactionManager,
|
||||
expect(mockMessageFolderDataAccessService.update).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
{ id: 'folder-1' },
|
||||
expect.objectContaining({
|
||||
parentFolderId: 'new-parent-id',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -364,7 +389,9 @@ describe('SyncMessageFoldersService', () => {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(mockRepository.updateMany).not.toHaveBeenCalled();
|
||||
expect(
|
||||
mockMessageFolderDataAccessService.update,
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -401,16 +428,12 @@ describe('SyncMessageFoldersService', () => {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(mockRepository.updateMany).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
criteria: 'folder-2',
|
||||
partialEntity: expect.objectContaining({
|
||||
pendingSyncAction: 'FOLDER_DELETION',
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
mockTransactionManager,
|
||||
expect(mockMessageFolderDataAccessService.update).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
{ id: In(['folder-2']) },
|
||||
expect.objectContaining({
|
||||
pendingSyncAction: 'FOLDER_DELETION',
|
||||
}),
|
||||
);
|
||||
expect(result).toContainEqual(
|
||||
expect.objectContaining({
|
||||
@@ -468,32 +491,21 @@ describe('SyncMessageFoldersService', () => {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(mockRepository.updateMany).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
criteria: 'folder-to-delete',
|
||||
partialEntity: expect.objectContaining({
|
||||
pendingSyncAction: 'FOLDER_DELETION',
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
mockTransactionManager,
|
||||
expect(mockMessageFolderDataAccessService.update).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
{ id: In(['folder-to-delete']) },
|
||||
expect.objectContaining({
|
||||
pendingSyncAction: 'FOLDER_DELETION',
|
||||
}),
|
||||
);
|
||||
expect(mockRepository.updateMany).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
criteria: 'folder-to-update',
|
||||
partialEntity: expect.objectContaining({ name: 'New Name' }),
|
||||
}),
|
||||
]),
|
||||
mockTransactionManager,
|
||||
expect(mockMessageFolderDataAccessService.update).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
{ id: 'folder-to-update' },
|
||||
expect.objectContaining({ name: 'New Name' }),
|
||||
);
|
||||
expect(mockRepository.save).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ externalId: 'new-ext' }),
|
||||
]),
|
||||
{},
|
||||
mockTransactionManager,
|
||||
expect(mockMessageFolderDataAccessService.save).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
expect.objectContaining({ externalId: 'new-ext' }),
|
||||
);
|
||||
expect(result).toHaveLength(4);
|
||||
expect(result).toContainEqual(
|
||||
|
||||
+53
-50
@@ -1,21 +1,20 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { In } from 'typeorm';
|
||||
|
||||
import {
|
||||
DiscoveredMessageFolder,
|
||||
MessageFolder,
|
||||
} from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessageFolderPendingSyncAction,
|
||||
MessageFolderWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { GmailGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-get-all-folders.service';
|
||||
import { ImapGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service';
|
||||
import { MicrosoftGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/microsoft/services/microsoft-get-all-folders.service';
|
||||
@@ -28,6 +27,7 @@ import { computeUpdatedFolders } from 'src/modules/messaging/message-folder-mana
|
||||
export class SyncMessageFoldersService {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageFolderDataAccessService: MessageFolderDataAccessService,
|
||||
private readonly gmailGetAllFoldersService: GmailGetAllFoldersService,
|
||||
private readonly microsoftGetAllFoldersService: MicrosoftGetAllFoldersService,
|
||||
private readonly imapGetAllFoldersService: ImapGetAllFoldersService,
|
||||
@@ -132,60 +132,63 @@ export class SyncMessageFoldersService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
// TODO: Restore transaction wrapper once migration is complete — folder
|
||||
// sync operations (create/update/delete) are no longer atomic since
|
||||
// the data access layer routes writes across workspace and core schemas.
|
||||
// Acceptable during transition as sync is idempotent and self-corrects.
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const messageFolderRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageFolderWorkspaceEntity>(
|
||||
if (folderIdsToDelete.length > 0) {
|
||||
await this.messageFolderDataAccessService.update(
|
||||
workspaceId,
|
||||
'messageFolder',
|
||||
{ id: In(folderIdsToDelete) },
|
||||
{
|
||||
pendingSyncAction: MessageFolderPendingSyncAction.FOLDER_DELETION,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
|
||||
if (foldersToUpdate.size > 0) {
|
||||
for (const [id, data] of foldersToUpdate.entries()) {
|
||||
await this.messageFolderDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id },
|
||||
data,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return workspaceDataSource.transaction(
|
||||
async (transactionManager: WorkspaceEntityManager) => {
|
||||
if (folderIdsToDelete.length > 0) {
|
||||
await messageFolderRepository.updateMany(
|
||||
folderIdsToDelete.map((id) => ({
|
||||
criteria: id,
|
||||
partialEntity: {
|
||||
pendingSyncAction:
|
||||
MessageFolderPendingSyncAction.FOLDER_DELETION,
|
||||
},
|
||||
})),
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
if (foldersToCreate.length > 0) {
|
||||
for (const folderToCreate of foldersToCreate) {
|
||||
await this.messageFolderDataAccessService.save(
|
||||
workspaceId,
|
||||
folderToCreate,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (foldersToUpdate.size > 0) {
|
||||
await messageFolderRepository.updateMany(
|
||||
Array.from(foldersToUpdate.entries()).map(([id, data]) => ({
|
||||
criteria: id,
|
||||
partialEntity: data,
|
||||
})),
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
const createdFolders =
|
||||
foldersToCreate.length > 0
|
||||
? await this.messageFolderDataAccessService.find(workspaceId, {
|
||||
messageChannelId,
|
||||
externalId: In(
|
||||
foldersToCreate
|
||||
.map((folder) => folder.externalId)
|
||||
.filter(isDefined),
|
||||
),
|
||||
})
|
||||
: [];
|
||||
|
||||
const createdFolders =
|
||||
foldersToCreate.length > 0
|
||||
? await messageFolderRepository.save(
|
||||
foldersToCreate,
|
||||
{},
|
||||
transactionManager,
|
||||
)
|
||||
: [];
|
||||
const updatedExistingFolders = computeUpdatedFolders({
|
||||
existingFolders,
|
||||
foldersToUpdate,
|
||||
folderIdsToDelete,
|
||||
});
|
||||
|
||||
const updatedExistingFolders = computeUpdatedFolders({
|
||||
existingFolders,
|
||||
foldersToUpdate,
|
||||
folderIdsToDelete,
|
||||
});
|
||||
|
||||
return [...updatedExistingFolders, ...createdFolders];
|
||||
},
|
||||
);
|
||||
return [
|
||||
...updatedExistingFolders,
|
||||
...(createdFolders as MessageFolder[]),
|
||||
];
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
|
||||
+19
-25
@@ -5,16 +5,14 @@ import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
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 { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import {
|
||||
MessagingMessageListFetchJob,
|
||||
type MessagingMessageListFetchJobData,
|
||||
} from 'src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job';
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
type MessagingTriggerMessageListFetchCommandOptions = {
|
||||
workspaceId: string;
|
||||
@@ -33,6 +31,7 @@ export class MessagingTriggerMessageListFetchCommand extends CommandRunner {
|
||||
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
@InjectMessageQueue(MessageQueue.messagingQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
) {
|
||||
@@ -52,23 +51,14 @@ export class MessagingTriggerMessageListFetchCommand extends CommandRunner {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const whereCondition: Record<string, unknown> = {
|
||||
isSyncEnabled: true,
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
};
|
||||
|
||||
if (messageChannelId) {
|
||||
whereCondition.id = messageChannelId;
|
||||
}
|
||||
|
||||
const messageChannels =
|
||||
await messageChannelRepository.find(whereCondition);
|
||||
const messageChannels = await this.messageChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{
|
||||
isSyncEnabled: true,
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
...(messageChannelId ? { id: messageChannelId } : {}),
|
||||
},
|
||||
);
|
||||
|
||||
if (messageChannels.length === 0) {
|
||||
this.logger.warn(
|
||||
@@ -83,10 +73,14 @@ export class MessagingTriggerMessageListFetchCommand extends CommandRunner {
|
||||
);
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await messageChannelRepository.update(messageChannel.id, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
});
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: messageChannel.id },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
},
|
||||
);
|
||||
|
||||
await this.messageQueueService.add<MessagingMessageListFetchJobData>(
|
||||
MessagingMessageListFetchJob.name,
|
||||
|
||||
+24
-15
@@ -3,6 +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 { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { isThrottled } from 'src/modules/connected-account/utils/is-throttled';
|
||||
@@ -18,6 +19,16 @@ import {
|
||||
import { MessagingMessageListFetchService } from 'src/modules/messaging/message-import-manager/services/messaging-message-list-fetch.service';
|
||||
import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service';
|
||||
|
||||
const toIsoStringOrNull = (
|
||||
value: string | Date | null | undefined,
|
||||
): string | null => {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return value instanceof Date ? value.toISOString() : value;
|
||||
};
|
||||
|
||||
export type MessagingMessageListFetchJobData = {
|
||||
messageChannelId: string;
|
||||
workspaceId: string;
|
||||
@@ -32,6 +43,7 @@ export class MessagingMessageListFetchJob {
|
||||
private readonly messagingMessageListFetchService: MessagingMessageListFetchService,
|
||||
private readonly messagingMonitoringService: MessagingMonitoringService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messageImportErrorHandlerService: MessageImportExceptionHandlerService,
|
||||
private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
) {}
|
||||
@@ -49,18 +61,15 @@ export class MessagingMessageListFetchJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannel = await messageChannelRepository.findOne({
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
const messageChannel = await this.messageChannelDataAccessService.findOne(
|
||||
workspaceId,
|
||||
{
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
},
|
||||
relations: ['connectedAccount', 'messageFolders'],
|
||||
},
|
||||
relations: ['connectedAccount', 'messageFolders'],
|
||||
});
|
||||
);
|
||||
|
||||
if (!messageChannel) {
|
||||
await this.messagingMonitoringService.track({
|
||||
@@ -82,9 +91,9 @@ export class MessagingMessageListFetchJob {
|
||||
try {
|
||||
if (
|
||||
isThrottled(
|
||||
messageChannel.syncStageStartedAt,
|
||||
toIsoStringOrNull(messageChannel.syncStageStartedAt),
|
||||
messageChannel.throttleFailureCount,
|
||||
messageChannel.throttleRetryAfter,
|
||||
toIsoStringOrNull(messageChannel.throttleRetryAfter),
|
||||
)
|
||||
) {
|
||||
await this.messageChannelSyncStatusService.markAsMessagesListFetchPending(
|
||||
@@ -104,7 +113,7 @@ export class MessagingMessageListFetchJob {
|
||||
});
|
||||
|
||||
await this.messagingMessageListFetchService.processMessageListFetch(
|
||||
messageChannel,
|
||||
messageChannel as unknown as MessageChannelWorkspaceEntity,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
@@ -118,7 +127,7 @@ export class MessagingMessageListFetchJob {
|
||||
await this.messageImportErrorHandlerService.handleDriverException(
|
||||
error,
|
||||
MessageImportSyncStep.MESSAGE_LIST_FETCH,
|
||||
messageChannel,
|
||||
messageChannel as unknown as MessageChannelWorkspaceEntity,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
+27
-15
@@ -3,6 +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 { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { isThrottled } from 'src/modules/connected-account/utils/is-throttled';
|
||||
@@ -14,6 +15,16 @@ import {
|
||||
import { MessagingMessagesImportService } from 'src/modules/messaging/message-import-manager/services/messaging-messages-import.service';
|
||||
import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service';
|
||||
|
||||
const toIsoStringOrNull = (
|
||||
value: string | Date | null | undefined,
|
||||
): string | null => {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return value instanceof Date ? value.toISOString() : value;
|
||||
};
|
||||
|
||||
export type MessagingMessagesImportJobData = {
|
||||
messageChannelId: string;
|
||||
workspaceId: string;
|
||||
@@ -29,6 +40,7 @@ export class MessagingMessagesImportJob {
|
||||
private readonly messagingMonitoringService: MessagingMonitoringService,
|
||||
private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
) {}
|
||||
|
||||
@Process(MessagingMessagesImportJob.name)
|
||||
@@ -44,18 +56,15 @@ export class MessagingMessagesImportJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannel = await messageChannelRepository.findOne({
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
const messageChannel = await this.messageChannelDataAccessService.findOne(
|
||||
workspaceId,
|
||||
{
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
},
|
||||
relations: ['connectedAccount', 'messageFolders'],
|
||||
},
|
||||
relations: ['connectedAccount', 'messageFolders'],
|
||||
});
|
||||
);
|
||||
|
||||
if (!messageChannel) {
|
||||
await this.messagingMonitoringService.track({
|
||||
@@ -80,9 +89,9 @@ export class MessagingMessagesImportJob {
|
||||
|
||||
if (
|
||||
isThrottled(
|
||||
messageChannel.syncStageStartedAt,
|
||||
toIsoStringOrNull(messageChannel.syncStageStartedAt),
|
||||
messageChannel.throttleFailureCount,
|
||||
messageChannel.throttleRetryAfter,
|
||||
toIsoStringOrNull(messageChannel.throttleRetryAfter),
|
||||
)
|
||||
) {
|
||||
await this.messageChannelSyncStatusService.markAsMessagesImportPending(
|
||||
@@ -94,9 +103,12 @@ export class MessagingMessagesImportJob {
|
||||
return;
|
||||
}
|
||||
|
||||
const messageChannelWorkspace =
|
||||
messageChannel as unknown as MessageChannelWorkspaceEntity;
|
||||
|
||||
await this.messagingMessagesImportService.processMessageBatchImport(
|
||||
messageChannel,
|
||||
messageChannel.connectedAccount,
|
||||
messageChannelWorkspace,
|
||||
messageChannelWorkspace.connectedAccount,
|
||||
workspaceId,
|
||||
);
|
||||
}, authContext);
|
||||
|
||||
+18
-14
@@ -5,15 +5,23 @@ 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 { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { isSyncStale } from 'src/modules/messaging/message-import-manager/utils/is-sync-stale.util';
|
||||
|
||||
const toIsoStringOrNull = (
|
||||
value: string | Date | null | undefined,
|
||||
): string | null => {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return value instanceof Date ? value.toISOString() : value;
|
||||
};
|
||||
|
||||
export type MessagingOngoingStaleJobData = {
|
||||
workspaceId: string;
|
||||
};
|
||||
@@ -26,6 +34,7 @@ export class MessagingOngoingStaleJob {
|
||||
private readonly logger = new Logger(MessagingOngoingStaleJob.name);
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
) {}
|
||||
|
||||
@@ -36,14 +45,9 @@ export class MessagingOngoingStaleJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: {
|
||||
const messageChannels = await this.messageChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{
|
||||
syncStage: In([
|
||||
MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING,
|
||||
MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING,
|
||||
@@ -51,10 +55,10 @@ export class MessagingOngoingStaleJob {
|
||||
MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
]),
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
if (isSyncStale(messageChannel.syncStageStartedAt)) {
|
||||
if (isSyncStale(toIsoStringOrNull(messageChannel.syncStageStartedAt))) {
|
||||
await this.messageChannelSyncStatusService.resetSyncStageStartedAt(
|
||||
[messageChannel.id],
|
||||
workspaceId,
|
||||
|
||||
+16
-20
@@ -3,12 +3,12 @@ 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 { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
MessageChannelSyncStatus,
|
||||
MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
export type MessagingRelaunchFailedMessageChannelJobData = {
|
||||
@@ -23,6 +23,7 @@ export type MessagingRelaunchFailedMessageChannelJobData = {
|
||||
export class MessagingRelaunchFailedMessageChannelJob {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
) {}
|
||||
|
||||
@Process(MessagingRelaunchFailedMessageChannelJob.name)
|
||||
@@ -32,23 +33,14 @@ export class MessagingRelaunchFailedMessageChannelJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const messageChannel = await messageChannelRepository.findOne({
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
},
|
||||
relations: {
|
||||
connectedAccount: {
|
||||
accountOwner: true,
|
||||
const messageChannel = await this.messageChannelDataAccessService.findOne(
|
||||
workspaceId,
|
||||
{
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
if (
|
||||
!messageChannel ||
|
||||
@@ -58,10 +50,14 @@ export class MessagingRelaunchFailedMessageChannelJob {
|
||||
return;
|
||||
}
|
||||
|
||||
await messageChannelRepository.update(messageChannelId, {
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
syncStatus: MessageChannelSyncStatus.ACTIVE,
|
||||
});
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: messageChannelId },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
syncStatus: MessageChannelSyncStatus.ACTIVE,
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -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 { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { MessageFolderDataAccessModule } from 'src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module';
|
||||
@@ -61,6 +63,8 @@ import { MessagingMonitoringModule } from 'src/modules/messaging/monitoring/mess
|
||||
MessagingIMAPDriverModule,
|
||||
MessagingSmtpDriverModule,
|
||||
MessagingCommonModule,
|
||||
MessageChannelDataAccessModule,
|
||||
MessageFolderDataAccessModule,
|
||||
TypeOrmModule.forFeature([
|
||||
WorkspaceEntity,
|
||||
DataSourceEntity,
|
||||
|
||||
+7
@@ -24,6 +24,7 @@ import { MessagingMessageListFetchService } from 'src/modules/messaging/message-
|
||||
import { MessagingMessagesImportService } from 'src/modules/messaging/message-import-manager/services/messaging-messages-import.service';
|
||||
import { MessagingProcessFolderActionsService } from 'src/modules/messaging/message-import-manager/services/messaging-process-folder-actions.service';
|
||||
import { MessagingProcessGroupEmailActionsService } from 'src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
|
||||
describe('MessagingMessageListFetchService', () => {
|
||||
let messagingMessageListFetchService: MessagingMessageListFetchService;
|
||||
@@ -229,6 +230,12 @@ describe('MessagingMessageListFetchService', () => {
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MessageChannelDataAccessService,
|
||||
useValue: {
|
||||
findOne: jest.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MessagingCursorService,
|
||||
useValue: {
|
||||
|
||||
+7
@@ -22,6 +22,7 @@ import { MessagingGetMessagesService } from 'src/modules/messaging/message-impor
|
||||
import { MessageImportExceptionHandlerService } from 'src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service';
|
||||
import { MessagingMessagesImportService } from 'src/modules/messaging/message-import-manager/services/messaging-messages-import.service';
|
||||
import { MessagingSaveMessagesAndEnqueueContactCreationService } from 'src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service';
|
||||
|
||||
describe('MessagingMessagesImportService', () => {
|
||||
@@ -127,6 +128,12 @@ describe('MessagingMessagesImportService', () => {
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MessageChannelDataAccessService,
|
||||
useValue: {
|
||||
update: jest.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MessagingGetMessagesService,
|
||||
useValue: {
|
||||
|
||||
+10
-15
@@ -1,14 +1,17 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
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 globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messageFolderDataAccessService: MessageFolderDataAccessService,
|
||||
) {}
|
||||
|
||||
public async updateCursor(
|
||||
@@ -20,19 +23,9 @@ export class MessagingCursorService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
const folderRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageFolderWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageFolder',
|
||||
);
|
||||
|
||||
if (!folderId) {
|
||||
await messageChannelRepository.update(
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{
|
||||
id: messageChannel.id,
|
||||
},
|
||||
@@ -48,7 +41,8 @@ export class MessagingCursorService {
|
||||
},
|
||||
);
|
||||
} else {
|
||||
await folderRepository.update(
|
||||
await this.messageFolderDataAccessService.update(
|
||||
workspaceId,
|
||||
{
|
||||
id: folderId,
|
||||
},
|
||||
@@ -56,7 +50,8 @@ export class MessagingCursorService {
|
||||
syncCursor: nextSyncCursor,
|
||||
},
|
||||
);
|
||||
await messageChannelRepository.update(
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{
|
||||
id: messageChannel.id,
|
||||
},
|
||||
|
||||
+21
-32
@@ -7,8 +7,7 @@ import {
|
||||
type TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
import {
|
||||
MessageChannelSyncStatus,
|
||||
@@ -30,7 +29,7 @@ export enum MessageImportSyncStep {
|
||||
@Injectable()
|
||||
export class MessageImportExceptionHandlerService {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||
) {}
|
||||
@@ -152,37 +151,27 @@ export class MessageImportExceptionHandlerService {
|
||||
return;
|
||||
}
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
await this.messageChannelDataAccessService.increment(
|
||||
workspaceId,
|
||||
{ id: messageChannel.id },
|
||||
'throttleFailureCount',
|
||||
1,
|
||||
);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
const throttleRetryAfter =
|
||||
exception instanceof MessageImportDriverException
|
||||
? exception.throttleRetryAfter
|
||||
: undefined;
|
||||
|
||||
await messageChannelRepository.increment(
|
||||
{ id: messageChannel.id },
|
||||
'throttleFailureCount',
|
||||
1,
|
||||
undefined,
|
||||
['throttleFailureCount', 'id'],
|
||||
);
|
||||
|
||||
const throttleRetryAfter =
|
||||
exception instanceof MessageImportDriverException
|
||||
? exception.throttleRetryAfter
|
||||
: undefined;
|
||||
|
||||
await messageChannelRepository.update(
|
||||
{ id: messageChannel.id },
|
||||
{
|
||||
throttleRetryAfter: isDefined(throttleRetryAfter)
|
||||
? throttleRetryAfter.toISOString()
|
||||
: null,
|
||||
},
|
||||
);
|
||||
}, authContext);
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: messageChannel.id },
|
||||
{
|
||||
throttleRetryAfter: isDefined(throttleRetryAfter)
|
||||
? throttleRetryAfter.toISOString()
|
||||
: null,
|
||||
},
|
||||
);
|
||||
|
||||
switch (syncStep) {
|
||||
case MessageImportSyncStep.MESSAGE_LIST_FETCH:
|
||||
|
||||
+3
-7
@@ -8,6 +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 { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
@@ -41,6 +42,7 @@ export class MessagingMessageListFetchService {
|
||||
private readonly cacheStorage: CacheStorageService,
|
||||
private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messagingGetMessageListService: MessagingGetMessageListService,
|
||||
private readonly messageImportErrorHandlerService: MessageImportExceptionHandlerService,
|
||||
private readonly messagingMessageCleanerService: MessagingMessageCleanerService,
|
||||
@@ -78,15 +80,9 @@ export class MessagingMessageListFetchService {
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Processing message list fetch`,
|
||||
);
|
||||
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const freshMessageChannel =
|
||||
pendingGroupEmailActionsProcessed || pendingFolderActionsProcessed
|
||||
? await messageChannelRepository.findOne({
|
||||
? await this.messageChannelDataAccessService.findOne(workspaceId, {
|
||||
where: {
|
||||
id: messageChannel.id,
|
||||
},
|
||||
|
||||
+4
-7
@@ -5,6 +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 { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
@@ -46,6 +47,7 @@ export class MessagingMessagesImportService {
|
||||
private readonly blocklistRepository: BlocklistRepository,
|
||||
private readonly emailAliasManagerService: EmailAliasManagerService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messagingGetMessagesService: MessagingGetMessagesService,
|
||||
private readonly messageImportErrorHandlerService: MessageImportExceptionHandlerService,
|
||||
private readonly messagingAccountAuthenticationService: MessagingAccountAuthenticationService,
|
||||
@@ -196,13 +198,8 @@ export class MessagingMessagesImportService {
|
||||
);
|
||||
}
|
||||
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{
|
||||
id: messageChannel.id,
|
||||
},
|
||||
|
||||
+20
-33
@@ -3,7 +3,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { In } from 'typeorm';
|
||||
|
||||
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
@@ -21,6 +21,7 @@ export class MessagingProcessFolderActionsService {
|
||||
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageFolderDataAccessService: MessageFolderDataAccessService,
|
||||
private readonly messagingDeleteFolderMessagesService: MessagingDeleteFolderMessagesService,
|
||||
) {}
|
||||
|
||||
@@ -91,41 +92,27 @@ export class MessagingProcessFolderActionsService {
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const workspaceDataSource =
|
||||
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
|
||||
if (processedFolderIds.length > 0) {
|
||||
await this.messageFolderDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(processedFolderIds) },
|
||||
{ pendingSyncAction: MessageFolderPendingSyncAction.NONE },
|
||||
);
|
||||
|
||||
await workspaceDataSource?.transaction(
|
||||
async (transactionManager: WorkspaceEntityManager) => {
|
||||
const messageFolderRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageFolderWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageFolder',
|
||||
);
|
||||
this.logger.debug(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Reset pendingSyncAction to NONE for ${processedFolderIds.length} folders`,
|
||||
);
|
||||
}
|
||||
|
||||
if (processedFolderIds.length > 0) {
|
||||
await messageFolderRepository.update(
|
||||
{ id: In(processedFolderIds) },
|
||||
{ pendingSyncAction: MessageFolderPendingSyncAction.NONE },
|
||||
transactionManager,
|
||||
);
|
||||
if (folderIdsToDelete.length > 0) {
|
||||
await this.messageFolderDataAccessService.delete(workspaceId, {
|
||||
id: In(folderIdsToDelete),
|
||||
});
|
||||
|
||||
this.logger.debug(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Reset pendingSyncAction to NONE for ${processedFolderIds.length} folders`,
|
||||
);
|
||||
}
|
||||
|
||||
if (folderIdsToDelete.length > 0) {
|
||||
await messageFolderRepository.delete(
|
||||
{ id: In(folderIdsToDelete) },
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Deleted ${folderIdsToDelete.length} folders`,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
this.logger.log(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Deleted ${folderIdsToDelete.length} folders`,
|
||||
);
|
||||
}
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
|
||||
+19
-40
@@ -2,6 +2,8 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service';
|
||||
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
@@ -9,7 +11,6 @@ import {
|
||||
MessageChannelPendingGroupEmailsAction,
|
||||
MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessagingDeleteGroupEmailMessagesService } from 'src/modules/messaging/message-import-manager/services/messaging-delete-group-email-messages.service';
|
||||
|
||||
@Injectable()
|
||||
@@ -20,6 +21,8 @@ export class MessagingProcessGroupEmailActionsService {
|
||||
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messageFolderDataAccessService: MessageFolderDataAccessService,
|
||||
private readonly messagingDeleteGroupEmailMessagesService: MessagingDeleteGroupEmailMessagesService,
|
||||
) {}
|
||||
|
||||
@@ -28,24 +31,15 @@ export class MessagingProcessGroupEmailActionsService {
|
||||
workspaceId: string,
|
||||
pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction,
|
||||
): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: messageChannel.id },
|
||||
{ pendingGroupEmailsAction },
|
||||
);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(
|
||||
{ id: messageChannel.id },
|
||||
{ pendingGroupEmailsAction },
|
||||
);
|
||||
|
||||
this.logger.debug(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Marked message channel as pending group emails action: ${pendingGroupEmailsAction}`,
|
||||
);
|
||||
}, authContext);
|
||||
this.logger.debug(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Marked message channel as pending group emails action: ${pendingGroupEmailsAction}`,
|
||||
);
|
||||
}
|
||||
|
||||
async processGroupEmailActions(
|
||||
@@ -74,12 +68,6 @@ export class MessagingProcessGroupEmailActionsService {
|
||||
await workspaceDataSource?.transaction(
|
||||
async (transactionManager: WorkspaceEntityManager) => {
|
||||
try {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
switch (pendingGroupEmailsAction) {
|
||||
case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_DELETION:
|
||||
await this.handleGroupEmailsDeletion(
|
||||
@@ -97,7 +85,8 @@ export class MessagingProcessGroupEmailActionsService {
|
||||
break;
|
||||
}
|
||||
|
||||
await messageChannelRepository.update(
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: messageChannel.id },
|
||||
{
|
||||
pendingGroupEmailsAction:
|
||||
@@ -167,27 +156,17 @@ export class MessagingProcessGroupEmailActionsService {
|
||||
messageChannelId: string;
|
||||
transactionManager: WorkspaceEntityManager;
|
||||
}) {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
await messageChannelRepository.update(
|
||||
messageChannelId,
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: messageChannelId },
|
||||
{
|
||||
syncCursor: '',
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
const messageFolderRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageFolderWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageFolder',
|
||||
);
|
||||
|
||||
await messageFolderRepository.update(
|
||||
await this.messageFolderDataAccessService.update(
|
||||
workspaceId,
|
||||
{ messageChannelId },
|
||||
{ syncCursor: '' },
|
||||
transactionManager,
|
||||
|
||||
+7
-7
@@ -10,9 +10,9 @@ import { Process } from 'src/engine/core-modules/message-queue/decorators/proces
|
||||
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 { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service';
|
||||
|
||||
export const MESSAGING_MESSAGE_CHANNEL_SYNC_STATUS_MONITORING_CRON_PATTERN =
|
||||
@@ -25,6 +25,7 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob {
|
||||
private readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
private readonly messagingMonitoringService: MessagingMonitoringService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||
) {}
|
||||
|
||||
@@ -51,14 +52,13 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob {
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
const messageChannels =
|
||||
await this.messageChannelDataAccessService.findMany(
|
||||
activeWorkspace.id,
|
||||
'messageChannel',
|
||||
{
|
||||
select: ['id', 'syncStatus', 'connectedAccountId'],
|
||||
},
|
||||
);
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
select: ['id', 'syncStatus', 'connectedAccountId'],
|
||||
});
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
if (!messageChannel.syncStatus) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module';
|
||||
import { MessagingMessageChannelSyncStatusMonitoringCronCommand } from 'src/modules/messaging/monitoring/crons/commands/messaging-message-channel-sync-status-monitoring.cron.command';
|
||||
import { MessagingMessageChannelSyncStatusMonitoringCronJob } from 'src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job';
|
||||
@@ -12,6 +13,7 @@ import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/ser
|
||||
@Module({
|
||||
imports: [
|
||||
AuditModule,
|
||||
MessageChannelDataAccessModule,
|
||||
MessagingCommonModule,
|
||||
TypeOrmModule.forFeature([WorkspaceEntity]),
|
||||
TypeOrmModule.forFeature([DataSourceEntity]),
|
||||
|
||||
Reference in New Issue
Block a user