6256 refactor messaging module to remove all provider specific code and put it inside the drivers folders (#6721)

Closes #6256 
Closes #6257 
+ Create custom exceptions

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Raphaël Bosi
2024-08-27 18:14:45 +02:00
committed by GitHub
parent eb49cb2d08
commit 81fa3f0c41
62 changed files with 1540 additions and 1360 deletions

View File

@ -14,7 +14,7 @@ import { CalendarEventListFetchCronJob } from 'src/modules/calendar/calendar-eve
import { GoogleCalendarDriverModule } from 'src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/google-calendar-driver.module';
import { CalendarEventListFetchJob } from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job';
import { CalendarChannelSyncStatusService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-channel-sync-status.service';
import { CalendarEventImportErrorHandlerService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-error-handling.service';
import { CalendarEventImportErrorHandlerService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service';
import { CalendarEventsImportService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service';
import { CalendarGetCalendarEventsService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-get-events.service';
import { CalendarSaveEventsService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service';

View File

@ -0,0 +1,16 @@
import { CustomException } from 'src/utils/custom-exception';
export class CalendarEventImportDriverException extends CustomException {
code: CalendarEventImportDriverExceptionCode;
constructor(message: string, code: CalendarEventImportDriverExceptionCode) {
super(message, code);
}
}
export enum CalendarEventImportDriverExceptionCode {
NOT_FOUND = 'NOT_FOUND',
TEMPORARY_ERROR = 'TEMPORARY_ERROR',
INSUFFICIENT_PERMISSIONS = 'INSUFFICIENT_PERMISSIONS',
UNKNOWN = 'UNKNOWN',
UNKNOWN_NETWORK_ERROR = 'UNKNOWN_NETWORK_ERROR',
}

View File

@ -4,7 +4,6 @@ import { GaxiosError } from 'gaxios';
import { calendar_v3 as calendarV3 } from 'googleapis';
import { GoogleCalendarClientProvider } from 'src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/providers/google-calendar.provider';
import { GoogleCalendarError } from 'src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/types/google-calendar-error.type';
import { formatGoogleCalendarEvents } from 'src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/format-google-calendar-event.util';
import { parseGaxiosError } from 'src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/parse-gaxios-error.util';
import { parseGoogleCalendarError } from 'src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/parse-google-calendar-error.util';
@ -92,7 +91,7 @@ export class GoogleCalendarGetEventsService {
throw parseGaxiosError(error);
}
if (error.response?.status !== 410) {
const googleCalendarError: GoogleCalendarError = {
const googleCalendarError = {
code: error.response?.status,
reason:
error.response?.data?.error?.errors?.[0].reason ||

View File

@ -1,5 +0,0 @@
export type GoogleCalendarError = {
code?: number;
reason: string;
message: string;
};

View File

@ -1,11 +1,13 @@
import { GaxiosError } from 'gaxios';
import {
CalendarEventError,
CalendarEventErrorCode,
} from 'src/modules/calendar/calendar-event-import-manager/types/calendar-event-error.type';
CalendarEventImportDriverException,
CalendarEventImportDriverExceptionCode,
} from 'src/modules/calendar/calendar-event-import-manager/drivers/exceptions/calendar-event-import-driver.exception';
export const parseGaxiosError = (error: GaxiosError): CalendarEventError => {
export const parseGaxiosError = (
error: GaxiosError,
): CalendarEventImportDriverException => {
const { code } = error;
switch (code) {
@ -14,15 +16,15 @@ export const parseGaxiosError = (error: GaxiosError): CalendarEventError => {
case 'ECONNABORTED':
case 'ETIMEDOUT':
case 'ERR_NETWORK':
return {
code: CalendarEventErrorCode.TEMPORARY_ERROR,
message: error.message,
};
return new CalendarEventImportDriverException(
error.message,
CalendarEventImportDriverExceptionCode.TEMPORARY_ERROR,
);
default:
return {
code: CalendarEventErrorCode.UNKNOWN,
message: error.message,
};
return new CalendarEventImportDriverException(
error.message,
CalendarEventImportDriverExceptionCode.UNKNOWN_NETWORK_ERROR,
);
}
};

View File

@ -1,86 +1,87 @@
import { GoogleCalendarError } from 'src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/types/google-calendar-error.type';
import {
CalendarEventError,
CalendarEventErrorCode,
} from 'src/modules/calendar/calendar-event-import-manager/types/calendar-event-error.type';
CalendarEventImportDriverException,
CalendarEventImportDriverExceptionCode,
} from 'src/modules/calendar/calendar-event-import-manager/drivers/exceptions/calendar-event-import-driver.exception';
export const parseGoogleCalendarError = (
error: GoogleCalendarError,
): CalendarEventError => {
export const parseGoogleCalendarError = (error: {
code?: number;
reason: string;
message: string;
}): CalendarEventImportDriverException => {
const { code, reason, message } = error;
switch (code) {
case 400:
if (reason === 'invalid_grant') {
return {
code: CalendarEventErrorCode.INSUFFICIENT_PERMISSIONS,
return new CalendarEventImportDriverException(
message,
};
CalendarEventImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
}
if (reason === 'failedPrecondition') {
return {
code: CalendarEventErrorCode.TEMPORARY_ERROR,
return new CalendarEventImportDriverException(
message,
};
CalendarEventImportDriverExceptionCode.TEMPORARY_ERROR,
);
}
return {
code: CalendarEventErrorCode.UNKNOWN,
return new CalendarEventImportDriverException(
message,
};
CalendarEventImportDriverExceptionCode.UNKNOWN,
);
case 404:
return {
code: CalendarEventErrorCode.NOT_FOUND,
return new CalendarEventImportDriverException(
message,
};
CalendarEventImportDriverExceptionCode.NOT_FOUND,
);
case 429:
return {
code: CalendarEventErrorCode.TEMPORARY_ERROR,
return new CalendarEventImportDriverException(
message,
};
CalendarEventImportDriverExceptionCode.TEMPORARY_ERROR,
);
case 403:
if (
reason === 'rateLimitExceeded' ||
reason === 'userRateLimitExceeded'
) {
return {
code: CalendarEventErrorCode.TEMPORARY_ERROR,
return new CalendarEventImportDriverException(
message,
};
CalendarEventImportDriverExceptionCode.TEMPORARY_ERROR,
);
} else {
return {
code: CalendarEventErrorCode.INSUFFICIENT_PERMISSIONS,
return new CalendarEventImportDriverException(
message,
};
CalendarEventImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
}
case 401:
return {
code: CalendarEventErrorCode.INSUFFICIENT_PERMISSIONS,
return new CalendarEventImportDriverException(
message,
};
CalendarEventImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
case 500:
if (reason === 'backendError') {
return {
code: CalendarEventErrorCode.TEMPORARY_ERROR,
return new CalendarEventImportDriverException(
message,
};
CalendarEventImportDriverExceptionCode.TEMPORARY_ERROR,
);
} else {
return {
code: CalendarEventErrorCode.UNKNOWN,
return new CalendarEventImportDriverException(
message,
};
CalendarEventImportDriverExceptionCode.UNKNOWN,
);
}
default:
break;
}
return {
code: CalendarEventErrorCode.UNKNOWN,
return new CalendarEventImportDriverException(
message,
};
CalendarEventImportDriverExceptionCode.UNKNOWN,
);
};

View File

@ -0,0 +1,14 @@
import { CustomException } from 'src/utils/custom-exception';
export class CalendarEventImportException extends CustomException {
code: CalendarEventImportExceptionCode;
constructor(message: string, code: CalendarEventImportExceptionCode) {
super(message, code);
}
}
export enum CalendarEventImportExceptionCode {
PROVIDER_NOT_SUPPORTED = 'PROVIDER_NOT_SUPPORTED',
UNKNOWN = 'UNKNOWN',
CALENDAR_CHANNEL_NOT_FOUND = 'CALENDAR_CHANNEL_NOT_FOUND',
}

View File

@ -4,12 +4,17 @@ import { CacheStorageService } from 'src/engine/integrations/cache-storage/cache
import { InjectCacheStorage } from 'src/engine/integrations/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageNamespace } from 'src/engine/integrations/cache-storage/types/cache-storage-namespace.enum';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import {
CalendarEventImportException,
CalendarEventImportExceptionCode,
} from 'src/modules/calendar/calendar-event-import-manager/exceptions/calendar-event-import.exception';
import {
CalendarChannelSyncStage,
CalendarChannelSyncStatus,
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 { 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';
@Injectable()
@ -161,6 +166,31 @@ export class CalendarChannelSyncStatusService {
syncStage: CalendarChannelSyncStage.FAILED,
});
const connectedAccountRepository =
await this.twentyORMManager.getRepository<ConnectedAccountWorkspaceEntity>(
'connectedAccount',
);
const calendarChannel = await calendarChannelRepository.findOne({
where: { id: calendarChannelId },
});
if (!calendarChannel) {
throw new CalendarEventImportException(
`Calendar channel ${calendarChannelId} not found in workspace ${workspaceId}`,
CalendarEventImportExceptionCode.CALENDAR_CHANNEL_NOT_FOUND,
);
}
const connectedAccountId = calendarChannel.connectedAccountId;
await connectedAccountRepository.update(
{ id: connectedAccountId },
{
authFailedAt: new Date(),
},
);
await this.addToAccountsToReconnect(calendarChannelId, workspaceId);
}

View File

@ -2,8 +2,15 @@ import { Injectable } from '@nestjs/common';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { CALENDAR_THROTTLE_MAX_ATTEMPTS } from 'src/modules/calendar/calendar-event-import-manager/constants/calendar-throttle-max-attempts';
import {
CalendarEventImportDriverException,
CalendarEventImportDriverExceptionCode,
} from 'src/modules/calendar/calendar-event-import-manager/drivers/exceptions/calendar-event-import-driver.exception';
import {
CalendarEventImportException,
CalendarEventImportExceptionCode,
} from 'src/modules/calendar/calendar-event-import-manager/exceptions/calendar-event-import.exception';
import { CalendarChannelSyncStatusService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-channel-sync-status.service';
import { CalendarEventError } from 'src/modules/calendar/calendar-event-import-manager/types/calendar-event-error.type';
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
export enum CalendarEventImportSyncStep {
@ -19,8 +26,8 @@ export class CalendarEventImportErrorHandlerService {
private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService,
) {}
public async handleError(
error: CalendarEventError,
public async handleDriverException(
exception: CalendarEventImportDriverException,
syncStep: CalendarEventImportSyncStep,
calendarChannel: Pick<
CalendarChannelWorkspaceEntity,
@ -28,26 +35,41 @@ export class CalendarEventImportErrorHandlerService {
>,
workspaceId: string,
): Promise<void> {
switch (error.code) {
case 'NOT_FOUND':
await this.handleNotFoundError(syncStep, calendarChannel, workspaceId);
break;
case 'TEMPORARY_ERROR':
await this.handleTemporaryError(syncStep, calendarChannel, workspaceId);
break;
case 'INSUFFICIENT_PERMISSIONS':
await this.handleInsufficientPermissionsError(
switch (exception.code) {
case CalendarEventImportDriverExceptionCode.NOT_FOUND:
await this.handleNotFoundException(
syncStep,
calendarChannel,
workspaceId,
);
break;
case 'UNKNOWN':
await this.handleUnknownError(error, calendarChannel, workspaceId);
case CalendarEventImportDriverExceptionCode.TEMPORARY_ERROR:
await this.handleTemporaryException(
syncStep,
calendarChannel,
workspaceId,
);
break;
case CalendarEventImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS:
await this.handleInsufficientPermissionsException(
calendarChannel,
workspaceId,
);
break;
case CalendarEventImportDriverExceptionCode.UNKNOWN:
case CalendarEventImportDriverExceptionCode.UNKNOWN_NETWORK_ERROR:
await this.handleUnknownException(
exception,
calendarChannel,
workspaceId,
);
break;
default:
throw exception;
}
}
private async handleTemporaryError(
private async handleTemporaryException(
syncStep: CalendarEventImportSyncStep,
calendarChannel: Pick<
CalendarChannelWorkspaceEntity,
@ -103,7 +125,7 @@ export class CalendarEventImportErrorHandlerService {
}
}
private async handleInsufficientPermissionsError(
private async handleInsufficientPermissionsException(
calendarChannel: Pick<CalendarChannelWorkspaceEntity, 'id'>,
workspaceId: string,
): Promise<void> {
@ -113,8 +135,8 @@ export class CalendarEventImportErrorHandlerService {
);
}
private async handleUnknownError(
error: CalendarEventError,
private async handleUnknownException(
exception: CalendarEventImportDriverException,
calendarChannel: Pick<CalendarChannelWorkspaceEntity, 'id'>,
workspaceId: string,
): Promise<void> {
@ -123,12 +145,13 @@ export class CalendarEventImportErrorHandlerService {
workspaceId,
);
throw new Error(
`Unknown error occurred while importing calendar events for calendar channel ${calendarChannel.id} in workspace ${workspaceId}: ${error.message}`,
throw new CalendarEventImportException(
`Unknown error occurred while importing calendar events for calendar channel ${calendarChannel.id} in workspace ${workspaceId}: ${exception.message}`,
CalendarEventImportExceptionCode.UNKNOWN,
);
}
private async handleNotFoundError(
private async handleNotFoundException(
syncStep: CalendarEventImportSyncStep,
calendarChannel: Pick<CalendarChannelWorkspaceEntity, 'id'>,
workspaceId: string,

View File

@ -11,7 +11,7 @@ import { CalendarChannelSyncStatusService } from 'src/modules/calendar/calendar-
import {
CalendarEventImportErrorHandlerService,
CalendarEventImportSyncStep,
} from 'src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-error-handling.service';
} from 'src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service';
import {
CalendarGetCalendarEventsService,
GetCalendarEventsResponse,
@ -64,23 +64,66 @@ export class CalendarEventsImportService {
calendarEvents = getCalendarEventsResponse.calendarEvents;
nextSyncCursor = getCalendarEventsResponse.nextSyncCursor;
} catch (error) {
await this.calendarEventImportErrorHandlerService.handleError(
error,
syncStep,
calendarChannel,
const calendarChannelRepository =
await this.twentyORMManager.getRepository<CalendarChannelWorkspaceEntity>(
'calendarChannel',
);
if (!calendarEvents || calendarEvents?.length === 0) {
await calendarChannelRepository.update(
{
id: calendarChannel.id,
},
{
syncCursor: nextSyncCursor,
},
);
await this.calendarChannelSyncStatusService.schedulePartialCalendarEventListFetch(
calendarChannel.id,
);
}
const blocklist = await this.blocklistRepository.getByWorkspaceMemberId(
connectedAccount.accountOwnerId,
workspaceId,
);
return;
}
const { filteredEvents, cancelledEvents } =
filterEventsAndReturnCancelledEvents(
calendarChannel,
calendarEvents,
blocklist.map((blocklist) => blocklist.handle),
);
const calendarChannelRepository =
await this.twentyORMManager.getRepository<CalendarChannelWorkspaceEntity>(
'calendarChannel',
const cancelledEventExternalIds = cancelledEvents.map(
(event) => event.externalId,
);
await this.calendarSaveEventsService.saveCalendarEventsAndEnqueueContactCreationJob(
filteredEvents,
calendarChannel,
connectedAccount,
workspaceId,
);
const calendarChannelEventAssociationRepository =
await this.twentyORMManager.getRepository<CalendarChannelEventAssociationWorkspaceEntity>(
'calendarChannelEventAssociation',
);
await calendarChannelEventAssociationRepository.delete({
eventExternalId: Any(cancelledEventExternalIds),
calendarChannel: {
id: calendarChannel.id,
},
});
await this.calendarEventCleanerService.cleanWorkspaceCalendarEvents(
workspaceId,
);
if (!calendarEvents || calendarEvents?.length === 0) {
await calendarChannelRepository.update(
{
id: calendarChannel.id,
@ -90,61 +133,16 @@ export class CalendarEventsImportService {
},
);
await this.calendarChannelSyncStatusService.schedulePartialCalendarEventListFetch(
await this.calendarChannelSyncStatusService.markAsCompletedAndSchedulePartialMessageListFetch(
calendarChannel.id,
);
}
const blocklist = await this.blocklistRepository.getByWorkspaceMemberId(
connectedAccount.accountOwnerId,
workspaceId,
);
const { filteredEvents, cancelledEvents } =
filterEventsAndReturnCancelledEvents(
} catch (error) {
await this.calendarEventImportErrorHandlerService.handleDriverException(
error,
syncStep,
calendarChannel,
calendarEvents,
blocklist.map((blocklist) => blocklist.handle),
workspaceId,
);
const cancelledEventExternalIds = cancelledEvents.map(
(event) => event.externalId,
);
await this.calendarSaveEventsService.saveCalendarEventsAndEnqueueContactCreationJob(
filteredEvents,
calendarChannel,
connectedAccount,
workspaceId,
);
const calendarChannelEventAssociationRepository =
await this.twentyORMManager.getRepository<CalendarChannelEventAssociationWorkspaceEntity>(
'calendarChannelEventAssociation',
);
await calendarChannelEventAssociationRepository.delete({
eventExternalId: Any(cancelledEventExternalIds),
calendarChannel: {
id: calendarChannel.id,
},
});
await this.calendarEventCleanerService.cleanWorkspaceCalendarEvents(
workspaceId,
);
await calendarChannelRepository.update(
{
id: calendarChannel.id,
},
{
syncCursor: nextSyncCursor,
},
);
await this.calendarChannelSyncStatusService.markAsCompletedAndSchedulePartialMessageListFetch(
calendarChannel.id,
);
}
}
}

View File

@ -1,6 +1,10 @@
import { Injectable } from '@nestjs/common';
import { GoogleCalendarGetEventsService as GoogleCalendarGetCalendarEventsService } from 'src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/services/google-calendar-get-events.service';
import {
CalendarEventImportException,
CalendarEventImportExceptionCode,
} from 'src/modules/calendar/calendar-event-import-manager/exceptions/calendar-event-import.exception';
import { CalendarEventWithParticipants } from 'src/modules/calendar/common/types/calendar-event';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
@ -29,8 +33,9 @@ export class CalendarGetCalendarEventsService {
syncCursor,
);
default:
throw new Error(
`Provider ${connectedAccount.provider} is not supported.`,
throw new CalendarEventImportException(
`Provider ${connectedAccount.provider} is not supported`,
CalendarEventImportExceptionCode.PROVIDER_NOT_SUPPORTED,
);
}
}

View File

@ -1,11 +0,0 @@
export enum CalendarEventErrorCode {
NOT_FOUND = 'NOT_FOUND',
TEMPORARY_ERROR = 'TEMPORARY_ERROR',
INSUFFICIENT_PERMISSIONS = 'INSUFFICIENT_PERMISSIONS',
UNKNOWN = 'UNKNOWN',
}
export interface CalendarEventError {
message: string;
code: CalendarEventErrorCode;
}