Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export const CONNECTED_ACCOUNT_STANDARD_FIELD_IDS = {
accountOwner: '20202020-3517-4896-afac-b1d0aa362af6',
lastSyncHistoryId: '20202020-115c-4a87-b50f-ac4367a971b9',
authFailedAt: '20202020-d268-4c6b-baff-400d402b430a',
lastCredentialsRefreshedAt: '20202020-aa5e-4e85-903b-fdf90a941941',
messageChannels: '20202020-24f7-4362-8468-042204d1e445',
calendarChannels: '20202020-af4a-47bb-99ec-51911c1d3977',
handleAliases: '20202020-8a3d-46be-814f-6228af16c47b',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,21 @@ import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repos
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity';
import { CalendarEventCleanerModule } from 'src/modules/calendar/calendar-event-cleaner/calendar-event-cleaner.module';
import { CalendarRelaunchFailedCalendarChannelsCommand } from 'src/modules/calendar/calendar-event-import-manager/commands/calendar-relaunch-failed-calendar-channels.command';
import { CalendarEventListFetchCronCommand } from 'src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-event-list-fetch.cron.command';
import { CalendarEventsImportCronCommand } from 'src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-import.cron.command';
import { CalendarOngoingStaleCronCommand } from 'src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-ongoing-stale.cron.command';
import { CalendarRelaunchFailedCalendarChannelsCronCommand } from 'src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-relaunch-failed-calendar-channels.cron.command';
import { CalendarEventListFetchCronJob } from 'src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-event-list-fetch.cron.job';
import { CalendarEventsImportCronJob } from 'src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-events-import.cron.job';
import { CalendarOngoingStaleCronJob } from 'src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-ongoing-stale.cron.job';
import { CalendarRelaunchFailedCalendarChannelsCronJob } from 'src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-relaunch-failed-calendar-channels.cron.job';
import { CalDavDriverModule } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/caldav-driver.module';
import { GoogleCalendarDriverModule } from 'src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/google-calendar-driver.module';
import { MicrosoftCalendarDriverModule } from 'src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/microsoft-calendar-driver.module';
import { CalendarEventListFetchJob } from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job';
import { CalendarEventsImportJob } from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-events-import.job';
import { CalendarOngoingStaleJob } from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-ongoing-stale.job';
import { CalendarRelaunchFailedCalendarChannelJob } from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-relaunch-failed-calendar-channel.job';
import { CalendarAccountAuthenticationService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-account-authentication.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';
Expand Down Expand Up @@ -71,15 +73,18 @@ import { RefreshTokensManagerModule } from 'src/modules/connected-account/refres
CalendarEventsImportJob,
CalendarOngoingStaleCronJob,
CalendarOngoingStaleCronCommand,
CalendarRelaunchFailedCalendarChannelsCommand,
CalendarOngoingStaleJob,
CalendarRelaunchFailedCalendarChannelsCronJob,
CalendarRelaunchFailedCalendarChannelsCronCommand,
CalendarRelaunchFailedCalendarChannelJob,
],
exports: [
CalendarEventsImportService,
CalendarFetchEventsService,
CalendarEventListFetchCronCommand,
CalendarEventsImportCronCommand,
CalendarOngoingStaleCronCommand,
CalendarRelaunchFailedCalendarChannelsCronCommand,
],
})
export class CalendarEventImportManagerModule {}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Command, CommandRunner } 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 {
CALENDAR_RELAUNCH_FAILED_CALENDAR_CHANNELS_CRON_PATTERN,
CalendarRelaunchFailedCalendarChannelsCronJob,
} from 'src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-relaunch-failed-calendar-channels.cron.job';

@Command({
name: 'cron:calendar:relaunch-failed-calendar-channels',
description:
'Starts a cron job to relaunch failed calendar channels every 30 minutes',
})
export class CalendarRelaunchFailedCalendarChannelsCronCommand extends CommandRunner {
constructor(
@InjectMessageQueue(MessageQueue.cronQueue)
private readonly messageQueueService: MessageQueueService,
) {
super();
}

async run(): Promise<void> {
await this.messageQueueService.addCron<undefined>({
jobName: CalendarRelaunchFailedCalendarChannelsCronJob.name,
data: undefined,
options: {
repeat: {
pattern: CALENDAR_RELAUNCH_FAILED_CALENDAR_CHANNELS_CRON_PATTERN,
},
},
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';

import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { DataSource, Repository } from 'typeorm';

import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
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 { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
import {
CalendarRelaunchFailedCalendarChannelJob,
type CalendarRelaunchFailedCalendarChannelJobData,
} from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-relaunch-failed-calendar-channel.job';
import { CalendarChannelSyncStage } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';

export const CALENDAR_RELAUNCH_FAILED_CALENDAR_CHANNELS_CRON_PATTERN =
'*/30 * * * *';

@Processor(MessageQueue.cronQueue)
export class CalendarRelaunchFailedCalendarChannelsCronJob {
constructor(
@InjectRepository(WorkspaceEntity)
private readonly workspaceRepository: Repository<WorkspaceEntity>,
@InjectMessageQueue(MessageQueue.calendarQueue)
private readonly messageQueueService: MessageQueueService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
private readonly exceptionHandlerService: ExceptionHandlerService,
) {}

@Process(CalendarRelaunchFailedCalendarChannelsCronJob.name)
@SentryCronMonitor(
CalendarRelaunchFailedCalendarChannelsCronJob.name,
CALENDAR_RELAUNCH_FAILED_CALENDAR_CHANNELS_CRON_PATTERN,
)
async handle(): Promise<void> {
const activeWorkspaces = await this.workspaceRepository.find({
where: {
activationStatus: WorkspaceActivationStatus.ACTIVE,
},
});

for (const activeWorkspace of activeWorkspaces) {
try {
const schemaName = getWorkspaceSchemaName(activeWorkspace.id);

const failedCalendarChannels = await this.coreDataSource.query(
`SELECT * FROM ${schemaName}."calendarChannel" WHERE "syncStage" = '${CalendarChannelSyncStage.FAILED}'`,
);

for (const calendarChannel of failedCalendarChannels) {
await this.messageQueueService.add<CalendarRelaunchFailedCalendarChannelJobData>(
CalendarRelaunchFailedCalendarChannelJob.name,
{
workspaceId: activeWorkspace.id,
calendarChannelId: calendarChannel.id,
},
);
}
} catch (error) {
this.exceptionHandlerService.captureExceptions([error], {
workspace: {
id: activeWorkspace.id,
},
});
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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 { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
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';

export type CalendarRelaunchFailedCalendarChannelJobData = {
workspaceId: string;
calendarChannelId: string;
};

@Processor({
queueName: MessageQueue.calendarQueue,
scope: Scope.REQUEST,
})
export class CalendarRelaunchFailedCalendarChannelJob {
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly accountsToReconnectService: AccountsToReconnectService,
) {}

@Process(CalendarRelaunchFailedCalendarChannelJob.name)
async handle(data: CalendarRelaunchFailedCalendarChannelJobData) {
const { workspaceId, calendarChannelId } = data;

const calendarChannelRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarChannelWorkspaceEntity>(
workspaceId,
'calendarChannel',
{ shouldBypassPermissionChecks: true },
);

const calendarChannel = await calendarChannelRepository.findOne({
where: {
id: calendarChannelId,
},
relations: {
connectedAccount: {
accountOwner: true,
},
},
});

if (
!calendarChannel ||
calendarChannel.syncStage !== CalendarChannelSyncStage.FAILED
) {
return;
}

await calendarChannelRepository.update(calendarChannelId, {
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
syncStatus: CalendarChannelSyncStatus.ACTIVE,
});

await this.accountsToReconnectService.removeAccountToReconnect(
calendarChannel.connectedAccount.accountOwner.userId,
calendarChannel.connectedAccountId,
workspaceId,
);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';

import { isDefined } from 'class-validator';
import { GaxiosError } from 'gaxios';
import { google } from 'googleapis';

import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
Expand All @@ -23,19 +23,25 @@ export class GoogleAPIRefreshAccessTokenService {
oAuth2Client.setCredentials({
refresh_token: refreshToken,
});

const { token } = await oAuth2Client.getAccessToken();

if (!isDefined(token)) {
throw new ConnectedAccountRefreshAccessTokenException(
'Failed to refresh google access token',
ConnectedAccountRefreshAccessTokenExceptionCode.REFRESH_ACCESS_TOKEN_FAILED,
);
try {
const { token } = await oAuth2Client.getAccessToken();

return {
accessToken: token as string,
refreshToken,
};
} catch (error) {
if (
error instanceof GaxiosError &&
error.response?.data?.error === 'invalid_grant'
) {
throw new ConnectedAccountRefreshAccessTokenException(
'Error refreshing Google tokens: Invalid refresh token',
ConnectedAccountRefreshAccessTokenExceptionCode.INVALID_REFRESH_TOKEN,
);
}

throw error;
}

return {
accessToken: token as string,
refreshToken,
};
}
}
Loading