Add message channel reset command (#16266)

In this PR, we are adding a new command to reset a message channel.

We are also refactoring a bit cursor reset as we had multiple
implementations at different places in the code base

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
neo773
2025-12-04 18:55:41 +05:30
committed by GitHub
parent f0f648181e
commit 8716cb25e9
25 changed files with 323 additions and 460 deletions
@@ -22,9 +22,6 @@ import { CreateMessageChannelService } from 'src/engine/core-modules/auth/servic
import { GoogleAPIScopesService } from 'src/engine/core-modules/auth/services/google-apis-scopes';
import { GoogleAPIsService } from 'src/engine/core-modules/auth/services/google-apis.service';
import { MicrosoftAPIsService } from 'src/engine/core-modules/auth/services/microsoft-apis.service';
import { ResetCalendarChannelService } from 'src/engine/core-modules/auth/services/reset-calendar-channel.service';
import { ResetMessageChannelService } from 'src/engine/core-modules/auth/services/reset-message-channel.service';
import { ResetMessageFolderService } from 'src/engine/core-modules/auth/services/reset-message-folder.service';
import { ResetPasswordService } from 'src/engine/core-modules/auth/services/reset-password.service';
import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-up.service';
import { UpdateConnectedAccountOnReconnectService } from 'src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service';
@@ -62,7 +59,9 @@ import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service';
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
import { MessagingFolderSyncManagerModule } from 'src/modules/messaging/message-folder-manager/messaging-folder-sync-manager.module';
import { TwoFactorAuthenticationMethodEntity } from '../two-factor-authentication/entities/two-factor-authentication-method.entity';
@@ -142,9 +141,8 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
// So far, it's not possible to have controllers in business modules
// which forces us to have these services in the auth module
// TODO: Move these calendar, message, and connected account services to the business modules once possible
ResetMessageChannelService,
ResetCalendarChannelService,
ResetMessageFolderService,
MessageChannelSyncStatusService,
CalendarChannelSyncStatusService,
CreateMessageChannelService,
CreateCalendarChannelService,
CreateConnectedAccountService,
@@ -8,20 +8,20 @@ import { CreateConnectedAccountService } from 'src/engine/core-modules/auth/serv
import { CreateMessageChannelService } from 'src/engine/core-modules/auth/services/create-message-channel.service';
import { GoogleAPIScopesService } from 'src/engine/core-modules/auth/services/google-apis-scopes';
import { GoogleAPIsService } from 'src/engine/core-modules/auth/services/google-apis.service';
import { ResetCalendarChannelService } from 'src/engine/core-modules/auth/services/reset-calendar-channel.service';
import { ResetMessageChannelService } from 'src/engine/core-modules/auth/services/reset-message-channel.service';
import { UpdateConnectedAccountOnReconnectService } from 'src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-queue-token.util';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service';
import {
CalendarChannelSyncStage,
CalendarChannelVisibility,
} 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 { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
jest.mock('uuid', () => ({
@@ -30,8 +30,8 @@ jest.mock('uuid', () => ({
describe('GoogleAPIsService', () => {
let service: GoogleAPIsService;
let resetCalendarChannelService: ResetCalendarChannelService;
let resetMessageChannelService: ResetMessageChannelService;
let calendarChannelSyncStatusService: CalendarChannelSyncStatusService;
let messagingChannelSyncStatusService: MessageChannelSyncStatusService;
let createMessageChannelService: CreateMessageChannelService;
const mockConnectedAccountRepository = {
@@ -109,9 +109,9 @@ describe('GoogleAPIsService', () => {
useValue: mockTwentyConfigService,
},
{
provide: ResetCalendarChannelService,
provide: CalendarChannelSyncStatusService,
useValue: {
resetCalendarChannels: jest.fn(),
resetAndScheduleCalendarEventListFetch: jest.fn(),
},
},
{
@@ -125,9 +125,9 @@ describe('GoogleAPIsService', () => {
},
},
{
provide: ResetMessageChannelService,
provide: MessageChannelSyncStatusService,
useValue: {
resetMessageChannels: jest.fn(),
resetAndScheduleMessageListFetch: jest.fn(),
},
},
{
@@ -172,12 +172,14 @@ describe('GoogleAPIsService', () => {
}).compile();
service = module.get<GoogleAPIsService>(GoogleAPIsService);
resetCalendarChannelService = module.get<ResetCalendarChannelService>(
ResetCalendarChannelService,
);
resetMessageChannelService = module.get<ResetMessageChannelService>(
ResetMessageChannelService,
);
calendarChannelSyncStatusService =
module.get<CalendarChannelSyncStatusService>(
CalendarChannelSyncStatusService,
);
messagingChannelSyncStatusService =
module.get<MessageChannelSyncStatusService>(
MessageChannelSyncStatusService,
);
createMessageChannelService = module.get<CreateMessageChannelService>(
CreateMessageChannelService,
);
@@ -232,20 +234,12 @@ describe('GoogleAPIsService', () => {
});
expect(
resetCalendarChannelService.resetCalendarChannels,
).toHaveBeenCalledWith({
workspaceId: 'workspace-id',
connectedAccountId: 'existing-account-id',
manager: expect.any(Object),
});
calendarChannelSyncStatusService.resetAndScheduleCalendarEventListFetch,
).toHaveBeenCalledWith([existingConnectedAccount.id], 'workspace-id');
expect(
resetMessageChannelService.resetMessageChannels,
).toHaveBeenCalledWith({
workspaceId: 'workspace-id',
connectedAccountId: 'existing-account-id',
manager: expect.any(Object),
});
messagingChannelSyncStatusService.resetAndScheduleMessageListFetch,
).toHaveBeenCalledWith([existingConnectedAccount.id], 'workspace-id');
expect(
createMessageChannelService.createMessageChannel,
@@ -11,8 +11,6 @@ import { CreateCalendarChannelService } from 'src/engine/core-modules/auth/servi
import { CreateConnectedAccountService } from 'src/engine/core-modules/auth/services/create-connected-account.service';
import { CreateMessageChannelService } from 'src/engine/core-modules/auth/services/create-message-channel.service';
import { GoogleAPIScopesService } from 'src/engine/core-modules/auth/services/google-apis-scopes';
import { ResetCalendarChannelService } from 'src/engine/core-modules/auth/services/reset-calendar-channel.service';
import { ResetMessageChannelService } from 'src/engine/core-modules/auth/services/reset-message-channel.service';
import { UpdateConnectedAccountOnReconnectService } from 'src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service';
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';
@@ -24,6 +22,7 @@ import {
CalendarEventListFetchJob,
type CalendarEventListFetchJobData,
} from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job';
import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service';
import {
CalendarChannelSyncStage,
type CalendarChannelVisibility,
@@ -31,6 +30,7 @@ import {
} 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 { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
import {
MessageChannelSyncStage,
type MessageChannelVisibility,
@@ -52,9 +52,9 @@ export class GoogleAPIsService {
private readonly calendarQueueService: MessageQueueService,
private readonly twentyConfigService: TwentyConfigService,
private readonly accountsToReconnectService: AccountsToReconnectService,
private readonly resetMessageChannelService: ResetMessageChannelService,
private readonly resetCalendarChannelService: ResetCalendarChannelService,
private readonly createMessageChannelService: CreateMessageChannelService,
private readonly messagingChannelSyncStatusService: MessageChannelSyncStatusService,
private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService,
private readonly createCalendarChannelService: CreateCalendarChannelService,
private readonly createConnectedAccountService: CreateConnectedAccountService,
private readonly updateConnectedAccountOnReconnectService: UpdateConnectedAccountOnReconnectService,
@@ -190,17 +190,15 @@ export class GoogleAPIsService {
newOrExistingConnectedAccountId,
);
await this.resetMessageChannelService.resetMessageChannels({
await this.messagingChannelSyncStatusService.resetAndScheduleMessageListFetch(
[newOrExistingConnectedAccountId],
workspaceId,
connectedAccountId: newOrExistingConnectedAccountId,
manager,
});
);
await this.resetCalendarChannelService.resetCalendarChannels({
await this.calendarChannelSyncStatusService.resetAndScheduleCalendarEventListFetch(
[newOrExistingConnectedAccountId],
workspaceId,
connectedAccountId: newOrExistingConnectedAccountId,
manager,
});
);
}
},
);
@@ -7,21 +7,20 @@ import { CreateCalendarChannelService } from 'src/engine/core-modules/auth/servi
import { CreateConnectedAccountService } from 'src/engine/core-modules/auth/services/create-connected-account.service';
import { CreateMessageChannelService } from 'src/engine/core-modules/auth/services/create-message-channel.service';
import { MicrosoftAPIsService } from 'src/engine/core-modules/auth/services/microsoft-apis.service';
import { ResetCalendarChannelService } from 'src/engine/core-modules/auth/services/reset-calendar-channel.service';
import { ResetMessageChannelService } from 'src/engine/core-modules/auth/services/reset-message-channel.service';
import { ResetMessageFolderService } from 'src/engine/core-modules/auth/services/reset-message-folder.service';
import { UpdateConnectedAccountOnReconnectService } from 'src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-queue-token.util';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service';
import {
CalendarChannelSyncStage,
CalendarChannelVisibility,
} 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 { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
jest.mock('uuid', () => ({
@@ -30,8 +29,8 @@ jest.mock('uuid', () => ({
describe('MicrosoftAPIsService', () => {
let service: MicrosoftAPIsService;
let resetCalendarChannelService: ResetCalendarChannelService;
let resetMessageChannelService: ResetMessageChannelService;
let messagingChannelSyncStatusService: MessageChannelSyncStatusService;
let calendarChannelSyncStatusService: CalendarChannelSyncStatusService;
let createMessageChannelService: CreateMessageChannelService;
const mockConnectedAccountRepository = {
@@ -109,21 +108,15 @@ describe('MicrosoftAPIsService', () => {
useValue: mockTwentyConfigService,
},
{
provide: ResetCalendarChannelService,
provide: CalendarChannelSyncStatusService,
useValue: {
resetCalendarChannels: jest.fn(),
resetAndScheduleCalendarEventListFetch: jest.fn(),
},
},
{
provide: ResetMessageChannelService,
provide: MessageChannelSyncStatusService,
useValue: {
resetMessageChannels: jest.fn(),
},
},
{
provide: ResetMessageFolderService,
useValue: {
resetMessageFolders: jest.fn(),
resetAndScheduleMessageListFetch: jest.fn(),
},
},
{
@@ -170,12 +163,14 @@ describe('MicrosoftAPIsService', () => {
}).compile();
service = module.get<MicrosoftAPIsService>(MicrosoftAPIsService);
resetCalendarChannelService = module.get<ResetCalendarChannelService>(
ResetCalendarChannelService,
);
resetMessageChannelService = module.get<ResetMessageChannelService>(
ResetMessageChannelService,
);
calendarChannelSyncStatusService =
module.get<CalendarChannelSyncStatusService>(
CalendarChannelSyncStatusService,
);
messagingChannelSyncStatusService =
module.get<MessageChannelSyncStatusService>(
MessageChannelSyncStatusService,
);
createMessageChannelService = module.get<CreateMessageChannelService>(
CreateMessageChannelService,
);
@@ -235,20 +230,12 @@ describe('MicrosoftAPIsService', () => {
});
expect(
resetCalendarChannelService.resetCalendarChannels,
).toHaveBeenCalledWith({
workspaceId: 'workspace-id',
connectedAccountId: 'existing-account-id',
manager: expect.any(Object),
});
calendarChannelSyncStatusService.resetAndScheduleCalendarEventListFetch,
).toHaveBeenCalledWith([existingConnectedAccount.id], 'workspace-id');
expect(
resetMessageChannelService.resetMessageChannels,
).toHaveBeenCalledWith({
workspaceId: 'workspace-id',
connectedAccountId: 'existing-account-id',
manager: expect.any(Object),
});
messagingChannelSyncStatusService.resetAndScheduleMessageListFetch,
).toHaveBeenCalledWith([existingConnectedAccount.id], 'workspace-id');
expect(
createMessageChannelService.createMessageChannel,
@@ -6,9 +6,6 @@ import { v4 } from 'uuid';
import { CreateCalendarChannelService } from 'src/engine/core-modules/auth/services/create-calendar-channel.service';
import { CreateConnectedAccountService } from 'src/engine/core-modules/auth/services/create-connected-account.service';
import { CreateMessageChannelService } from 'src/engine/core-modules/auth/services/create-message-channel.service';
import { ResetCalendarChannelService } from 'src/engine/core-modules/auth/services/reset-calendar-channel.service';
import { ResetMessageChannelService } from 'src/engine/core-modules/auth/services/reset-message-channel.service';
import { ResetMessageFolderService } from 'src/engine/core-modules/auth/services/reset-message-folder.service';
import { UpdateConnectedAccountOnReconnectService } from 'src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service';
import { getMicrosoftApisOauthScopes } from 'src/engine/core-modules/auth/utils/get-microsoft-apis-oauth-scopes';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
@@ -21,6 +18,7 @@ import {
CalendarEventListFetchJob,
type CalendarEventListFetchJobData,
} from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job';
import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service';
import {
CalendarChannelSyncStage,
type CalendarChannelVisibility,
@@ -28,6 +26,7 @@ import {
} 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 { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
import {
MessageChannelSyncStage,
type MessageChannelVisibility,
@@ -48,9 +47,8 @@ export class MicrosoftAPIsService {
@InjectMessageQueue(MessageQueue.calendarQueue)
private readonly calendarQueueService: MessageQueueService,
private readonly accountsToReconnectService: AccountsToReconnectService,
private readonly resetMessageChannelService: ResetMessageChannelService,
private readonly resetMessageFolderService: ResetMessageFolderService,
private readonly resetCalendarChannelService: ResetCalendarChannelService,
private readonly messagingChannelSyncStatusService: MessageChannelSyncStatusService,
private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService,
private readonly createMessageChannelService: CreateMessageChannelService,
private readonly createCalendarChannelService: CreateCalendarChannelService,
private readonly createConnectedAccountService: CreateConnectedAccountService,
@@ -175,23 +173,20 @@ export class MicrosoftAPIsService {
newOrExistingConnectedAccountId,
);
await this.resetMessageChannelService.resetMessageChannels({
await this.messagingChannelSyncStatusService.resetAndScheduleMessageListFetch(
[newOrExistingConnectedAccountId],
workspaceId,
connectedAccountId: newOrExistingConnectedAccountId,
manager,
});
);
await this.resetMessageFolderService.resetMessageFolders({
await this.calendarChannelSyncStatusService.resetAndScheduleCalendarEventListFetch(
[newOrExistingConnectedAccountId],
workspaceId,
connectedAccountId: newOrExistingConnectedAccountId,
manager,
});
);
await this.resetCalendarChannelService.resetCalendarChannels({
await this.calendarChannelSyncStatusService.resetAndScheduleCalendarEventListFetch(
[newOrExistingConnectedAccountId],
workspaceId,
connectedAccountId: newOrExistingConnectedAccountId,
manager,
});
);
}
},
);
@@ -1,48 +0,0 @@
import { Injectable } from '@nestjs/common';
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import {
CalendarChannelSyncStage,
type CalendarChannelWorkspaceEntity,
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
export type ResetCalendarChannelsInput = {
workspaceId: string;
connectedAccountId: string;
manager: WorkspaceEntityManager;
};
@Injectable()
export class ResetCalendarChannelService {
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
async resetCalendarChannels(
input: ResetCalendarChannelsInput,
): Promise<void> {
const { workspaceId, connectedAccountId, manager } = input;
const calendarChannelRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarChannelWorkspaceEntity>(
workspaceId,
'calendarChannel',
);
await calendarChannelRepository.update(
{
connectedAccountId,
},
{
syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING,
syncStatus: null,
syncCursor: '',
syncStageStartedAt: null,
},
manager,
);
return;
}
}
@@ -1,47 +0,0 @@
import { Injectable } from '@nestjs/common';
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import {
MessageChannelSyncStage,
MessageChannelSyncStatus,
type MessageChannelWorkspaceEntity,
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
export type ResetMessageChannelsInput = {
workspaceId: string;
connectedAccountId: string;
manager: WorkspaceEntityManager;
};
@Injectable()
export class ResetMessageChannelService {
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
async resetMessageChannels(input: ResetMessageChannelsInput): Promise<void> {
const { workspaceId, connectedAccountId, manager } = input;
const messageChannelRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
workspaceId,
'messageChannel',
);
await messageChannelRepository.update(
{
connectedAccountId,
},
{
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
syncStatus: MessageChannelSyncStatus.ONGOING,
syncCursor: '',
syncStageStartedAt: null,
},
manager,
);
return;
}
}
@@ -1,69 +0,0 @@
import { Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
export type ResetMessageFoldersInput = {
workspaceId: string;
connectedAccountId: string;
manager: WorkspaceEntityManager;
};
@Injectable()
export class ResetMessageFolderService {
constructor(
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
) {}
async resetMessageFolders(input: ResetMessageFoldersInput): Promise<void> {
const { workspaceId, connectedAccountId, manager } = input;
const messageChannelRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
workspaceId,
'messageChannel',
);
const messageChannels = await messageChannelRepository.find({
where: { connectedAccountId },
});
const messageChannelIds = messageChannels.map((channel) => channel.id);
if (messageChannelIds.length === 0) {
return;
}
const messageFolderRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageFolderWorkspaceEntity>(
workspaceId,
'messageFolder',
);
const messageFolders = await messageFolderRepository.find({
where: {
messageChannelId: In(messageChannelIds),
},
});
if (messageFolders.length === 0) {
return;
}
await messageFolderRepository.update(
{
messageChannelId: In(messageChannelIds),
},
{
syncCursor: '',
},
manager,
);
return;
}
}