Refactoring the reconnect service (#12089)
following qrqc #3 : refactoring the reconnect service Fixes https://github.com/twentyhq/twenty/issues/12064
This commit is contained in:
@ -12,12 +12,19 @@ import { MicrosoftAPIsAuthController } from 'src/engine/core-modules/auth/contro
|
||||
import { MicrosoftAuthController } from 'src/engine/core-modules/auth/controllers/microsoft-auth.controller';
|
||||
import { SSOAuthController } from 'src/engine/core-modules/auth/controllers/sso-auth.controller';
|
||||
import { ApiKeyService } from 'src/engine/core-modules/auth/services/api-key.service';
|
||||
import { AuthSsoService } from 'src/engine/core-modules/auth/services/auth-sso.service';
|
||||
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 { CreateMessageFolderService } from 'src/engine/core-modules/auth/services/create-message-folder.service';
|
||||
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 { OAuthService } from 'src/engine/core-modules/auth/services/oauth.service';
|
||||
import { AuthSsoService } from 'src/engine/core-modules/auth/services/auth-sso.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';
|
||||
import { SamlAuthStrategy } from 'src/engine/core-modules/auth/strategies/saml.auth.strategy';
|
||||
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
|
||||
import { LoginTokenService } from 'src/engine/core-modules/auth/token/services/login-token.service';
|
||||
@ -114,11 +121,20 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
|
||||
RefreshTokenService,
|
||||
LoginTokenService,
|
||||
ResetPasswordService,
|
||||
// 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,
|
||||
CreateMessageChannelService,
|
||||
CreateCalendarChannelService,
|
||||
CreateMessageFolderService,
|
||||
CreateConnectedAccountService,
|
||||
UpdateConnectedAccountOnReconnectService,
|
||||
TransientTokenService,
|
||||
ApiKeyService,
|
||||
AuthSsoService,
|
||||
// reenable when working on: https://github.com/twentyhq/twenty/issues/9143
|
||||
// OAuthService,
|
||||
],
|
||||
exports: [AccessTokenService, LoginTokenService, RefreshTokenService],
|
||||
})
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import {
|
||||
CalendarChannelVisibility,
|
||||
CalendarChannelWorkspaceEntity,
|
||||
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
|
||||
export type CreateCalendarChannelInput = {
|
||||
workspaceId: string;
|
||||
connectedAccountId: string;
|
||||
handle: string;
|
||||
calendarVisibility?: CalendarChannelVisibility;
|
||||
manager: WorkspaceEntityManager;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class CreateCalendarChannelService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
async createCalendarChannel(
|
||||
input: CreateCalendarChannelInput,
|
||||
): Promise<string> {
|
||||
const {
|
||||
workspaceId,
|
||||
connectedAccountId,
|
||||
handle,
|
||||
calendarVisibility,
|
||||
manager,
|
||||
} = input;
|
||||
|
||||
const calendarChannelRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const newCalendarChannel = await calendarChannelRepository.save(
|
||||
{
|
||||
id: v4(),
|
||||
connectedAccountId,
|
||||
handle,
|
||||
visibility:
|
||||
calendarVisibility || CalendarChannelVisibility.SHARE_EVERYTHING,
|
||||
},
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
|
||||
const calendarChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'calendarChannel', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'calendarChannel',
|
||||
action: DatabaseEventAction.CREATED,
|
||||
events: [
|
||||
{
|
||||
recordId: newCalendarChannel.id,
|
||||
objectMetadata: calendarChannelMetadata,
|
||||
properties: {
|
||||
after: newCalendarChannel,
|
||||
},
|
||||
},
|
||||
],
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return newCalendarChannel.id;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
export type CreateConnectedAccountInput = {
|
||||
workspaceId: string;
|
||||
connectedAccountId: string;
|
||||
handle: string;
|
||||
provider: ConnectedAccountProvider;
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
accountOwnerId: string;
|
||||
scopes: string[];
|
||||
manager: WorkspaceEntityManager;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class CreateConnectedAccountService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
async createConnectedAccount(
|
||||
input: CreateConnectedAccountInput,
|
||||
): Promise<void> {
|
||||
const {
|
||||
workspaceId,
|
||||
connectedAccountId,
|
||||
handle,
|
||||
provider,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
accountOwnerId,
|
||||
scopes,
|
||||
manager,
|
||||
} = input;
|
||||
|
||||
const connectedAccountRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
const newConnectedAccount = await connectedAccountRepository.save(
|
||||
{
|
||||
id: connectedAccountId,
|
||||
handle,
|
||||
provider,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
accountOwnerId,
|
||||
scopes,
|
||||
},
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
|
||||
const connectedAccountMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'connectedAccount', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'connectedAccount',
|
||||
action: DatabaseEventAction.CREATED,
|
||||
events: [
|
||||
{
|
||||
recordId: newConnectedAccount.id,
|
||||
objectMetadata: connectedAccountMetadata,
|
||||
properties: {
|
||||
after: newConnectedAccount,
|
||||
},
|
||||
},
|
||||
],
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import {
|
||||
MessageChannelSyncStatus,
|
||||
MessageChannelType,
|
||||
MessageChannelVisibility,
|
||||
MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
export type CreateMessageChannelInput = {
|
||||
workspaceId: string;
|
||||
connectedAccountId: string;
|
||||
handle: string;
|
||||
messageVisibility?: MessageChannelVisibility;
|
||||
manager: WorkspaceEntityManager;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class CreateMessageChannelService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
async createMessageChannel(
|
||||
input: CreateMessageChannelInput,
|
||||
): Promise<string> {
|
||||
const {
|
||||
workspaceId,
|
||||
connectedAccountId,
|
||||
handle,
|
||||
messageVisibility,
|
||||
manager,
|
||||
} = input;
|
||||
|
||||
const messageChannelRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const newMessageChannel = await messageChannelRepository.save(
|
||||
{
|
||||
id: v4(),
|
||||
connectedAccountId,
|
||||
type: MessageChannelType.EMAIL,
|
||||
handle,
|
||||
visibility:
|
||||
messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
},
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
|
||||
const messageChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'messageChannel', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'messageChannel',
|
||||
action: DatabaseEventAction.CREATED,
|
||||
events: [
|
||||
{
|
||||
recordId: newMessageChannel.id,
|
||||
objectMetadata: messageChannelMetadata,
|
||||
properties: {
|
||||
after: newMessageChannel,
|
||||
},
|
||||
},
|
||||
],
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return newMessageChannel.id;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessageFolderName } from 'src/modules/messaging/message-import-manager/drivers/microsoft/types/folders';
|
||||
|
||||
export type CreateMessageFoldersInput = {
|
||||
workspaceId: string;
|
||||
messageChannelId: string;
|
||||
manager: WorkspaceEntityManager;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class CreateMessageFolderService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async createMessageFolders(input: CreateMessageFoldersInput): Promise<void> {
|
||||
const { workspaceId, messageChannelId, manager } = input;
|
||||
|
||||
const messageFolderRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageFolderWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageFolder',
|
||||
);
|
||||
|
||||
await messageFolderRepository.save(
|
||||
{
|
||||
id: v4(),
|
||||
messageChannelId,
|
||||
name: MessageFolderName.INBOX,
|
||||
syncCursor: '',
|
||||
},
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
|
||||
await messageFolderRepository.save(
|
||||
{
|
||||
id: v4(),
|
||||
messageChannelId,
|
||||
name: MessageFolderName.SENT_ITEMS,
|
||||
syncCursor: '',
|
||||
},
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,251 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
|
||||
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 { 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 { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
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 { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: jest.fn(() => 'mocked-uuid'),
|
||||
}));
|
||||
|
||||
describe('GoogleAPIsService', () => {
|
||||
let service: GoogleAPIsService;
|
||||
let resetCalendarChannelService: ResetCalendarChannelService;
|
||||
let resetMessageChannelService: ResetMessageChannelService;
|
||||
let createMessageChannelService: CreateMessageChannelService;
|
||||
|
||||
const mockConnectedAccountRepository = {
|
||||
findOne: jest.fn(),
|
||||
find: jest.fn(),
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCalendarChannelRepository = {
|
||||
findOne: jest.fn(),
|
||||
find: jest.fn(),
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
const mockMessageChannelRepository = {
|
||||
findOne: jest.fn(),
|
||||
find: jest.fn(),
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
const mockWorkspaceMemberRepository = {
|
||||
findOneOrFail: jest.fn(),
|
||||
};
|
||||
|
||||
const mockWorkspaceDataSource = {
|
||||
transaction: jest.fn((callback) => callback({})),
|
||||
};
|
||||
|
||||
const mockTwentyConfigService = {
|
||||
get: jest.fn(),
|
||||
};
|
||||
|
||||
const mockMessageQueueService = {
|
||||
add: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCalendarQueueService = {
|
||||
add: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
GoogleAPIsService,
|
||||
{
|
||||
provide: TwentyORMGlobalManager,
|
||||
useValue: {
|
||||
getRepositoryForWorkspace: jest
|
||||
.fn()
|
||||
.mockImplementation((workspaceId, entity) => {
|
||||
if (entity === 'connectedAccount')
|
||||
return mockConnectedAccountRepository;
|
||||
if (entity === 'calendarChannel')
|
||||
return mockCalendarChannelRepository;
|
||||
if (entity === 'messageChannel')
|
||||
return mockMessageChannelRepository;
|
||||
if (entity === 'workspaceMember')
|
||||
return mockWorkspaceMemberRepository;
|
||||
|
||||
return {};
|
||||
}),
|
||||
getDataSourceForWorkspace: jest
|
||||
.fn()
|
||||
.mockImplementation(() => mockWorkspaceDataSource),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(ObjectMetadataEntity, 'metadata'),
|
||||
useValue: {
|
||||
findOneOrFail: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: TwentyConfigService,
|
||||
useValue: mockTwentyConfigService,
|
||||
},
|
||||
{
|
||||
provide: ResetCalendarChannelService,
|
||||
useValue: {
|
||||
resetCalendarChannels: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: ResetMessageChannelService,
|
||||
useValue: {
|
||||
resetMessageChannels: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CreateConnectedAccountService,
|
||||
useValue: {
|
||||
createConnectedAccount: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CreateMessageChannelService,
|
||||
useValue: {
|
||||
createMessageChannel: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CreateCalendarChannelService,
|
||||
useValue: {
|
||||
createCalendarChannel: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: UpdateConnectedAccountOnReconnectService,
|
||||
useValue: {
|
||||
updateConnectedAccountOnReconnect: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: AccountsToReconnectService,
|
||||
useValue: {
|
||||
removeAccountToReconnect: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: WorkspaceEventEmitter,
|
||||
useValue: {
|
||||
emitDatabaseBatchEvent: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: getQueueToken(MessageQueue.messagingQueue),
|
||||
useValue: mockMessageQueueService,
|
||||
},
|
||||
{
|
||||
provide: getQueueToken(MessageQueue.calendarQueue),
|
||||
useValue: mockCalendarQueueService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<GoogleAPIsService>(GoogleAPIsService);
|
||||
resetCalendarChannelService = module.get<ResetCalendarChannelService>(
|
||||
ResetCalendarChannelService,
|
||||
);
|
||||
resetMessageChannelService = module.get<ResetMessageChannelService>(
|
||||
ResetMessageChannelService,
|
||||
);
|
||||
createMessageChannelService = module.get<CreateMessageChannelService>(
|
||||
CreateMessageChannelService,
|
||||
);
|
||||
});
|
||||
|
||||
describe('refreshGoogleRefreshToken', () => {
|
||||
it('should reset calendar channels with FAILED_UNKNOWN syncStatus and FAILED syncStage', async () => {
|
||||
mockTwentyConfigService.get.mockImplementation((key) => {
|
||||
if (key === 'CALENDAR_PROVIDER_GOOGLE_ENABLED') return true;
|
||||
if (key === 'MESSAGING_PROVIDER_GMAIL_ENABLED') return true;
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const existingConnectedAccount = {
|
||||
id: 'existing-account-id',
|
||||
handle: 'test@example.com',
|
||||
accountOwnerId: 'workspace-member-id',
|
||||
provider: ConnectedAccountProvider.GOOGLE,
|
||||
} as ConnectedAccountWorkspaceEntity;
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(
|
||||
existingConnectedAccount,
|
||||
);
|
||||
|
||||
mockWorkspaceMemberRepository.findOneOrFail.mockResolvedValue({
|
||||
id: 'workspace-member-id',
|
||||
userId: 'user-id',
|
||||
});
|
||||
|
||||
const failedCalendarChannel = {
|
||||
id: 'calendar-channel-id',
|
||||
connectedAccountId: 'existing-account-id',
|
||||
syncStatus: 'FAILED_UNKNOWN',
|
||||
syncStage: CalendarChannelSyncStage.FAILED,
|
||||
};
|
||||
|
||||
mockCalendarChannelRepository.find.mockResolvedValue([
|
||||
failedCalendarChannel,
|
||||
]);
|
||||
|
||||
mockMessageChannelRepository.find.mockResolvedValue([]);
|
||||
|
||||
await service.refreshGoogleRefreshToken({
|
||||
handle: 'test@example.com',
|
||||
workspaceMemberId: 'workspace-member-id',
|
||||
workspaceId: 'workspace-id',
|
||||
accessToken: 'new-access-token',
|
||||
refreshToken: 'new-refresh-token',
|
||||
calendarVisibility: CalendarChannelVisibility.SHARE_EVERYTHING,
|
||||
messageVisibility: MessageChannelVisibility.SHARE_EVERYTHING,
|
||||
});
|
||||
|
||||
expect(
|
||||
resetCalendarChannelService.resetCalendarChannels,
|
||||
).toHaveBeenCalledWith({
|
||||
workspaceId: 'workspace-id',
|
||||
connectedAccountId: 'existing-account-id',
|
||||
manager: expect.any(Object),
|
||||
});
|
||||
|
||||
expect(
|
||||
resetMessageChannelService.resetMessageChannels,
|
||||
).toHaveBeenCalledWith({
|
||||
workspaceId: 'workspace-id',
|
||||
connectedAccountId: 'existing-account-id',
|
||||
manager: expect.any(Object),
|
||||
});
|
||||
|
||||
expect(
|
||||
createMessageChannelService.createMessageChannel,
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -5,7 +5,12 @@ import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
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 { UpdateConnectedAccountOnReconnectService } from 'src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service';
|
||||
import { getGoogleApisOauthScopes } from 'src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes';
|
||||
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';
|
||||
@ -20,16 +25,12 @@ import {
|
||||
CalendarEventListFetchJobData,
|
||||
} from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job';
|
||||
import {
|
||||
CalendarChannelSyncStage,
|
||||
CalendarChannelVisibility,
|
||||
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 {
|
||||
MessageChannelSyncStage,
|
||||
MessageChannelSyncStatus,
|
||||
MessageChannelType,
|
||||
MessageChannelVisibility,
|
||||
MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
@ -49,6 +50,12 @@ 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 createCalendarChannelService: CreateCalendarChannelService,
|
||||
private readonly createConnectedAccountService: CreateConnectedAccountService,
|
||||
private readonly updateConnectedAccountOnReconnectService: UpdateConnectedAccountOnReconnectService,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@ -110,145 +117,47 @@ export class GoogleAPIsService {
|
||||
await workspaceDataSource.transaction(
|
||||
async (manager: WorkspaceEntityManager) => {
|
||||
if (!existingAccountId) {
|
||||
const newConnectedAccount = await connectedAccountRepository.save(
|
||||
{
|
||||
id: newOrExistingConnectedAccountId,
|
||||
handle,
|
||||
provider: ConnectedAccountProvider.GOOGLE,
|
||||
accessToken: input.accessToken,
|
||||
refreshToken: input.refreshToken,
|
||||
accountOwnerId: workspaceMemberId,
|
||||
scopes,
|
||||
},
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
|
||||
const connectedAccountMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'connectedAccount', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'connectedAccount',
|
||||
action: DatabaseEventAction.CREATED,
|
||||
events: [
|
||||
{
|
||||
recordId: newConnectedAccount.id,
|
||||
objectMetadata: connectedAccountMetadata,
|
||||
properties: {
|
||||
after: newConnectedAccount,
|
||||
},
|
||||
},
|
||||
],
|
||||
await this.createConnectedAccountService.createConnectedAccount({
|
||||
workspaceId,
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
handle,
|
||||
provider: ConnectedAccountProvider.GOOGLE,
|
||||
accessToken: input.accessToken,
|
||||
refreshToken: input.refreshToken,
|
||||
accountOwnerId: workspaceMemberId,
|
||||
scopes,
|
||||
manager,
|
||||
});
|
||||
|
||||
const newMessageChannel = await messageChannelRepository.save(
|
||||
{
|
||||
id: v4(),
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
type: MessageChannelType.EMAIL,
|
||||
handle,
|
||||
visibility:
|
||||
messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
},
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
|
||||
const messageChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'messageChannel', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'messageChannel',
|
||||
action: DatabaseEventAction.CREATED,
|
||||
events: [
|
||||
{
|
||||
recordId: newMessageChannel.id,
|
||||
objectMetadata: messageChannelMetadata,
|
||||
properties: {
|
||||
after: newMessageChannel,
|
||||
},
|
||||
},
|
||||
],
|
||||
await this.createMessageChannelService.createMessageChannel({
|
||||
workspaceId,
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
handle,
|
||||
messageVisibility,
|
||||
manager,
|
||||
});
|
||||
|
||||
if (isCalendarEnabled) {
|
||||
const newCalendarChannel = await calendarChannelRepository.save(
|
||||
{
|
||||
id: v4(),
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
handle,
|
||||
visibility:
|
||||
calendarVisibility ||
|
||||
CalendarChannelVisibility.SHARE_EVERYTHING,
|
||||
},
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
|
||||
const calendarChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'calendarChannel', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'calendarChannel',
|
||||
action: DatabaseEventAction.CREATED,
|
||||
events: [
|
||||
{
|
||||
recordId: newCalendarChannel.id,
|
||||
objectMetadata: calendarChannelMetadata,
|
||||
properties: {
|
||||
after: newCalendarChannel,
|
||||
},
|
||||
},
|
||||
],
|
||||
await this.createCalendarChannelService.createCalendarChannel({
|
||||
workspaceId,
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
handle,
|
||||
calendarVisibility,
|
||||
manager,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const updatedConnectedAccount =
|
||||
await connectedAccountRepository.update(
|
||||
{
|
||||
id: newOrExistingConnectedAccountId,
|
||||
},
|
||||
{
|
||||
accessToken: input.accessToken,
|
||||
refreshToken: input.refreshToken,
|
||||
scopes,
|
||||
},
|
||||
await this.updateConnectedAccountOnReconnectService.updateConnectedAccountOnReconnect(
|
||||
{
|
||||
workspaceId,
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
accessToken: input.accessToken,
|
||||
refreshToken: input.refreshToken,
|
||||
scopes,
|
||||
connectedAccount,
|
||||
manager,
|
||||
);
|
||||
|
||||
const connectedAccountMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'connectedAccount', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'connectedAccount',
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
events: [
|
||||
{
|
||||
recordId: newOrExistingConnectedAccountId,
|
||||
objectMetadata: connectedAccountMetadata,
|
||||
properties: {
|
||||
before: connectedAccount,
|
||||
after: {
|
||||
...connectedAccount,
|
||||
...updatedConnectedAccount.raw[0],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
workspaceId,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const workspaceMemberRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
||||
@ -270,81 +179,16 @@ export class GoogleAPIsService {
|
||||
newOrExistingConnectedAccountId,
|
||||
);
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: { connectedAccountId: newOrExistingConnectedAccountId },
|
||||
});
|
||||
|
||||
const messageChannelUpdates = await messageChannelRepository.update(
|
||||
{
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
},
|
||||
{
|
||||
syncStage:
|
||||
MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
|
||||
syncStatus: null,
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
},
|
||||
manager,
|
||||
);
|
||||
|
||||
const messageChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'messageChannel', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'messageChannel',
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
events: messageChannels.map((messageChannel) => ({
|
||||
recordId: messageChannel.id,
|
||||
objectMetadata: messageChannelMetadata,
|
||||
properties: {
|
||||
before: messageChannel,
|
||||
after: { ...messageChannel, ...messageChannelUpdates.raw[0] },
|
||||
},
|
||||
})),
|
||||
await this.resetMessageChannelService.resetMessageChannels({
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
where: { connectedAccountId: newOrExistingConnectedAccountId },
|
||||
});
|
||||
|
||||
const calendarChannelUpdates = await calendarChannelRepository.update(
|
||||
{
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
},
|
||||
{
|
||||
syncStage:
|
||||
CalendarChannelSyncStage.FULL_CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
syncStatus: null,
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
},
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
manager,
|
||||
);
|
||||
});
|
||||
|
||||
const calendarChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'calendarChannel', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'calendarChannel',
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
events: calendarChannels.map((calendarChannel) => ({
|
||||
recordId: calendarChannel.id,
|
||||
objectMetadata: calendarChannelMetadata,
|
||||
properties: {
|
||||
before: calendarChannel,
|
||||
after: {
|
||||
...calendarChannel,
|
||||
...calendarChannelUpdates.raw[0],
|
||||
},
|
||||
},
|
||||
})),
|
||||
await this.resetCalendarChannelService.resetCalendarChannels({
|
||||
workspaceId,
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
manager,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -0,0 +1,272 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
|
||||
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 { CreateMessageFolderService } from 'src/engine/core-modules/auth/services/create-message-folder.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 { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
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 { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: jest.fn(() => 'mocked-uuid'),
|
||||
}));
|
||||
|
||||
describe('MicrosoftAPIsService', () => {
|
||||
let service: MicrosoftAPIsService;
|
||||
let resetCalendarChannelService: ResetCalendarChannelService;
|
||||
let resetMessageChannelService: ResetMessageChannelService;
|
||||
let createMessageChannelService: CreateMessageChannelService;
|
||||
|
||||
const mockConnectedAccountRepository = {
|
||||
findOne: jest.fn(),
|
||||
find: jest.fn(),
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCalendarChannelRepository = {
|
||||
findOne: jest.fn(),
|
||||
find: jest.fn(),
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
const mockMessageChannelRepository = {
|
||||
findOne: jest.fn(),
|
||||
find: jest.fn(),
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
const mockWorkspaceMemberRepository = {
|
||||
findOneOrFail: jest.fn(),
|
||||
};
|
||||
|
||||
const mockWorkspaceDataSource = {
|
||||
transaction: jest.fn((callback) => callback({})),
|
||||
};
|
||||
|
||||
const mockTwentyConfigService = {
|
||||
get: jest.fn(),
|
||||
};
|
||||
|
||||
const mockMessageQueueService = {
|
||||
add: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCalendarQueueService = {
|
||||
add: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
MicrosoftAPIsService,
|
||||
{
|
||||
provide: TwentyORMGlobalManager,
|
||||
useValue: {
|
||||
getRepositoryForWorkspace: jest
|
||||
.fn()
|
||||
.mockImplementation((workspaceId, entity) => {
|
||||
if (entity === 'connectedAccount')
|
||||
return mockConnectedAccountRepository;
|
||||
if (entity === 'calendarChannel')
|
||||
return mockCalendarChannelRepository;
|
||||
if (entity === 'messageChannel')
|
||||
return mockMessageChannelRepository;
|
||||
if (entity === 'workspaceMember')
|
||||
return mockWorkspaceMemberRepository;
|
||||
|
||||
return {};
|
||||
}),
|
||||
getDataSourceForWorkspace: jest
|
||||
.fn()
|
||||
.mockImplementation(() => mockWorkspaceDataSource),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(ObjectMetadataEntity, 'metadata'),
|
||||
useValue: {
|
||||
findOneOrFail: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: TwentyConfigService,
|
||||
useValue: mockTwentyConfigService,
|
||||
},
|
||||
{
|
||||
provide: ResetCalendarChannelService,
|
||||
useValue: {
|
||||
resetCalendarChannels: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: ResetMessageChannelService,
|
||||
useValue: {
|
||||
resetMessageChannels: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: ResetMessageFolderService,
|
||||
useValue: {
|
||||
resetMessageFolders: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CreateConnectedAccountService,
|
||||
useValue: {
|
||||
createConnectedAccount: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CreateMessageChannelService,
|
||||
useValue: {
|
||||
createMessageChannel: jest
|
||||
.fn()
|
||||
.mockResolvedValue('message-channel-id'),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CreateMessageFolderService,
|
||||
useValue: {
|
||||
createMessageFolders: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: CreateCalendarChannelService,
|
||||
useValue: {
|
||||
createCalendarChannel: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: UpdateConnectedAccountOnReconnectService,
|
||||
useValue: {
|
||||
updateConnectedAccountOnReconnect: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: AccountsToReconnectService,
|
||||
useValue: {
|
||||
removeAccountToReconnect: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: WorkspaceEventEmitter,
|
||||
useValue: {
|
||||
emitDatabaseBatchEvent: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: getQueueToken(MessageQueue.messagingQueue),
|
||||
useValue: mockMessageQueueService,
|
||||
},
|
||||
{
|
||||
provide: getQueueToken(MessageQueue.calendarQueue),
|
||||
useValue: mockCalendarQueueService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<MicrosoftAPIsService>(MicrosoftAPIsService);
|
||||
resetCalendarChannelService = module.get<ResetCalendarChannelService>(
|
||||
ResetCalendarChannelService,
|
||||
);
|
||||
resetMessageChannelService = module.get<ResetMessageChannelService>(
|
||||
ResetMessageChannelService,
|
||||
);
|
||||
createMessageChannelService = module.get<CreateMessageChannelService>(
|
||||
CreateMessageChannelService,
|
||||
);
|
||||
});
|
||||
|
||||
describe('refreshMicrosoftRefreshToken', () => {
|
||||
it('should reset calendar channels and message channels', async () => {
|
||||
mockTwentyConfigService.get.mockImplementation((key) => {
|
||||
if (key === 'CALENDAR_PROVIDER_MICROSOFT_ENABLED') return true;
|
||||
if (key === 'MESSAGING_PROVIDER_MICROSOFT_ENABLED') return true;
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const existingConnectedAccount = {
|
||||
id: 'existing-account-id',
|
||||
handle: 'test@example.com',
|
||||
accountOwnerId: 'workspace-member-id',
|
||||
provider: ConnectedAccountProvider.MICROSOFT,
|
||||
} as ConnectedAccountWorkspaceEntity;
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(
|
||||
existingConnectedAccount,
|
||||
);
|
||||
|
||||
mockWorkspaceMemberRepository.findOneOrFail.mockResolvedValue({
|
||||
id: 'workspace-member-id',
|
||||
userId: 'user-id',
|
||||
});
|
||||
|
||||
const failedCalendarChannel = {
|
||||
id: 'calendar-channel-id',
|
||||
connectedAccountId: 'existing-account-id',
|
||||
syncStatus: 'FAILED_UNKNOWN',
|
||||
syncStage: CalendarChannelSyncStage.FAILED,
|
||||
};
|
||||
|
||||
mockCalendarChannelRepository.find.mockResolvedValue([
|
||||
failedCalendarChannel,
|
||||
]);
|
||||
|
||||
mockMessageChannelRepository.find.mockResolvedValue([
|
||||
{
|
||||
id: 'message-channel-id',
|
||||
connectedAccountId: 'existing-account-id',
|
||||
},
|
||||
]);
|
||||
|
||||
await service.refreshMicrosoftRefreshToken({
|
||||
handle: 'test@example.com',
|
||||
workspaceMemberId: 'workspace-member-id',
|
||||
workspaceId: 'workspace-id',
|
||||
accessToken: 'new-access-token',
|
||||
refreshToken: 'new-refresh-token',
|
||||
calendarVisibility: CalendarChannelVisibility.SHARE_EVERYTHING,
|
||||
messageVisibility: MessageChannelVisibility.SHARE_EVERYTHING,
|
||||
});
|
||||
|
||||
expect(
|
||||
resetCalendarChannelService.resetCalendarChannels,
|
||||
).toHaveBeenCalledWith({
|
||||
workspaceId: 'workspace-id',
|
||||
connectedAccountId: 'existing-account-id',
|
||||
manager: expect.any(Object),
|
||||
});
|
||||
|
||||
expect(
|
||||
resetMessageChannelService.resetMessageChannels,
|
||||
).toHaveBeenCalledWith({
|
||||
workspaceId: 'workspace-id',
|
||||
connectedAccountId: 'existing-account-id',
|
||||
manager: expect.any(Object),
|
||||
});
|
||||
|
||||
expect(
|
||||
createMessageChannelService.createMessageChannel,
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -5,7 +5,14 @@ import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
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 { CreateMessageFolderService } from 'src/engine/core-modules/auth/services/create-message-folder.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';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
@ -20,21 +27,15 @@ import {
|
||||
CalendarEventListFetchJobData,
|
||||
} from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job';
|
||||
import {
|
||||
CalendarChannelSyncStage,
|
||||
CalendarChannelVisibility,
|
||||
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 {
|
||||
MessageChannelSyncStage,
|
||||
MessageChannelSyncStatus,
|
||||
MessageChannelType,
|
||||
MessageChannelVisibility,
|
||||
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 { MessageFolderName } from 'src/modules/messaging/message-import-manager/drivers/microsoft/types/folders';
|
||||
import {
|
||||
MessagingMessageListFetchJob,
|
||||
MessagingMessageListFetchJobData,
|
||||
@ -50,6 +51,14 @@ 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 createMessageChannelService: CreateMessageChannelService,
|
||||
private readonly createCalendarChannelService: CreateCalendarChannelService,
|
||||
private readonly createMessageFolderService: CreateMessageFolderService,
|
||||
private readonly createConnectedAccountService: CreateConnectedAccountService,
|
||||
private readonly updateConnectedAccountOnReconnectService: UpdateConnectedAccountOnReconnectService,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@ -98,12 +107,6 @@ export class MicrosoftAPIsService {
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageFolderRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageFolderWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageFolder',
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||
workspaceId,
|
||||
@ -114,169 +117,56 @@ export class MicrosoftAPIsService {
|
||||
await workspaceDataSource.transaction(
|
||||
async (manager: WorkspaceEntityManager) => {
|
||||
if (!existingAccountId) {
|
||||
const newConnectedAccount = await connectedAccountRepository.save(
|
||||
{
|
||||
id: newOrExistingConnectedAccountId,
|
||||
handle,
|
||||
provider: ConnectedAccountProvider.MICROSOFT,
|
||||
accessToken: input.accessToken,
|
||||
refreshToken: input.refreshToken,
|
||||
accountOwnerId: workspaceMemberId,
|
||||
scopes,
|
||||
},
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
|
||||
const connectedAccountMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'connectedAccount', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'connectedAccount',
|
||||
action: DatabaseEventAction.CREATED,
|
||||
events: [
|
||||
{
|
||||
recordId: newConnectedAccount.id,
|
||||
objectMetadata: connectedAccountMetadata,
|
||||
properties: {
|
||||
after: newConnectedAccount,
|
||||
},
|
||||
},
|
||||
],
|
||||
await this.createConnectedAccountService.createConnectedAccount({
|
||||
workspaceId,
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
handle,
|
||||
provider: ConnectedAccountProvider.MICROSOFT,
|
||||
accessToken: input.accessToken,
|
||||
refreshToken: input.refreshToken,
|
||||
accountOwnerId: workspaceMemberId,
|
||||
scopes,
|
||||
manager,
|
||||
});
|
||||
|
||||
const newMessageChannel = await messageChannelRepository.save(
|
||||
{
|
||||
id: v4(),
|
||||
const newMessageChannelId =
|
||||
await this.createMessageChannelService.createMessageChannel({
|
||||
workspaceId,
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
type: MessageChannelType.EMAIL,
|
||||
handle,
|
||||
visibility:
|
||||
messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
},
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
|
||||
await messageFolderRepository.save(
|
||||
{
|
||||
id: v4(),
|
||||
messageChannelId: newMessageChannel.id,
|
||||
name: MessageFolderName.INBOX,
|
||||
syncCursor: '',
|
||||
},
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
|
||||
await messageFolderRepository.save(
|
||||
{
|
||||
id: v4(),
|
||||
messageChannelId: newMessageChannel.id,
|
||||
name: MessageFolderName.SENT_ITEMS,
|
||||
syncCursor: '',
|
||||
},
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
|
||||
const messageChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'messageChannel', workspaceId },
|
||||
messageVisibility,
|
||||
manager,
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'messageChannel',
|
||||
action: DatabaseEventAction.CREATED,
|
||||
events: [
|
||||
{
|
||||
recordId: newMessageChannel.id,
|
||||
objectMetadata: messageChannelMetadata,
|
||||
properties: {
|
||||
after: newMessageChannel,
|
||||
},
|
||||
},
|
||||
],
|
||||
await this.createMessageFolderService.createMessageFolders({
|
||||
workspaceId,
|
||||
messageChannelId: newMessageChannelId,
|
||||
manager,
|
||||
});
|
||||
|
||||
if (
|
||||
this.twentyConfigService.get('CALENDAR_PROVIDER_MICROSOFT_ENABLED')
|
||||
) {
|
||||
const newCalendarChannel = await calendarChannelRepository.save(
|
||||
{
|
||||
id: v4(),
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
handle,
|
||||
visibility:
|
||||
calendarVisibility ||
|
||||
CalendarChannelVisibility.SHARE_EVERYTHING,
|
||||
},
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
|
||||
const calendarChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'calendarChannel', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'calendarChannel',
|
||||
action: DatabaseEventAction.CREATED,
|
||||
events: [
|
||||
{
|
||||
recordId: newCalendarChannel.id,
|
||||
objectMetadata: calendarChannelMetadata,
|
||||
properties: {
|
||||
after: newCalendarChannel,
|
||||
},
|
||||
},
|
||||
],
|
||||
await this.createCalendarChannelService.createCalendarChannel({
|
||||
workspaceId,
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
handle,
|
||||
calendarVisibility,
|
||||
manager,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const updatedConnectedAccount =
|
||||
await connectedAccountRepository.update(
|
||||
{
|
||||
id: newOrExistingConnectedAccountId,
|
||||
},
|
||||
{
|
||||
accessToken: input.accessToken,
|
||||
refreshToken: input.refreshToken,
|
||||
scopes,
|
||||
},
|
||||
await this.updateConnectedAccountOnReconnectService.updateConnectedAccountOnReconnect(
|
||||
{
|
||||
workspaceId,
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
accessToken: input.accessToken,
|
||||
refreshToken: input.refreshToken,
|
||||
scopes,
|
||||
connectedAccount,
|
||||
manager,
|
||||
);
|
||||
|
||||
const connectedAccountMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'connectedAccount', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'connectedAccount',
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
events: [
|
||||
{
|
||||
recordId: newOrExistingConnectedAccountId,
|
||||
objectMetadata: connectedAccountMetadata,
|
||||
properties: {
|
||||
before: connectedAccount,
|
||||
after: {
|
||||
...connectedAccount,
|
||||
...updatedConnectedAccount.raw[0],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
workspaceId,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const workspaceMemberRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
||||
@ -298,82 +188,22 @@ export class MicrosoftAPIsService {
|
||||
newOrExistingConnectedAccountId,
|
||||
);
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: { connectedAccountId: newOrExistingConnectedAccountId },
|
||||
});
|
||||
|
||||
const messageChannelUpdates = await messageChannelRepository.update(
|
||||
{
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
},
|
||||
{
|
||||
syncStage:
|
||||
MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
},
|
||||
manager,
|
||||
);
|
||||
|
||||
const messageChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'messageChannel', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'messageChannel',
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
events: messageChannels.map((messageChannel) => ({
|
||||
recordId: messageChannel.id,
|
||||
objectMetadata: messageChannelMetadata,
|
||||
properties: {
|
||||
before: messageChannel,
|
||||
after: { ...messageChannel, ...messageChannelUpdates.raw[0] },
|
||||
},
|
||||
})),
|
||||
await this.resetMessageChannelService.resetMessageChannels({
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
// now for the calendar channels
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
where: { connectedAccountId: newOrExistingConnectedAccountId },
|
||||
});
|
||||
|
||||
const calendarChannelUpdates = await calendarChannelRepository.update(
|
||||
{
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
},
|
||||
{
|
||||
syncStage:
|
||||
CalendarChannelSyncStage.FULL_CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
syncStatus: null,
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
},
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
manager,
|
||||
);
|
||||
});
|
||||
|
||||
const calendarChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'calendarChannel', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'calendarChannel',
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
events: calendarChannels.map((calendarChannel) => ({
|
||||
recordId: calendarChannel.id,
|
||||
objectMetadata: calendarChannelMetadata,
|
||||
properties: {
|
||||
before: calendarChannel,
|
||||
after: {
|
||||
...calendarChannel,
|
||||
...calendarChannelUpdates.raw[0],
|
||||
},
|
||||
},
|
||||
})),
|
||||
await this.resetMessageFolderService.resetMessageFolders({
|
||||
workspaceId,
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
manager,
|
||||
});
|
||||
|
||||
await this.resetCalendarChannelService.resetCalendarChannels({
|
||||
workspaceId,
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
manager,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import {
|
||||
CalendarChannelSyncStage,
|
||||
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,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
async resetCalendarChannels(
|
||||
input: ResetCalendarChannelsInput,
|
||||
): Promise<void> {
|
||||
const { workspaceId, connectedAccountId, manager } = input;
|
||||
|
||||
const calendarChannelRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
where: { connectedAccountId },
|
||||
});
|
||||
|
||||
const calendarChannelUpdates = await calendarChannelRepository.update(
|
||||
{
|
||||
connectedAccountId,
|
||||
},
|
||||
{
|
||||
syncStage:
|
||||
CalendarChannelSyncStage.FULL_CALENDAR_EVENT_LIST_FETCH_PENDING,
|
||||
syncStatus: null,
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
},
|
||||
manager,
|
||||
);
|
||||
|
||||
const calendarChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'calendarChannel', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'calendarChannel',
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
events: calendarChannels.map((calendarChannel) => ({
|
||||
recordId: calendarChannel.id,
|
||||
objectMetadata: calendarChannelMetadata,
|
||||
properties: {
|
||||
before: calendarChannel,
|
||||
after: {
|
||||
...calendarChannel,
|
||||
...calendarChannelUpdates.raw[0],
|
||||
},
|
||||
},
|
||||
})),
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
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,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
async resetMessageChannels(input: ResetMessageChannelsInput): Promise<void> {
|
||||
const { workspaceId, connectedAccountId, manager } = input;
|
||||
|
||||
const messageChannelRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: { connectedAccountId },
|
||||
});
|
||||
|
||||
const messageChannelUpdates = await messageChannelRepository.update(
|
||||
{
|
||||
connectedAccountId,
|
||||
},
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
|
||||
syncStatus: null,
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
},
|
||||
manager,
|
||||
);
|
||||
|
||||
const messageChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'messageChannel', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'messageChannel',
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
events: messageChannels.map((messageChannel) => ({
|
||||
recordId: messageChannel.id,
|
||||
objectMetadata: messageChannelMetadata,
|
||||
properties: {
|
||||
before: messageChannel,
|
||||
after: { ...messageChannel, ...messageChannelUpdates.raw[0] },
|
||||
},
|
||||
})),
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { In } from 'typeorm';
|
||||
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { 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';
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
export type UpdateConnectedAccountOnReconnectInput = {
|
||||
workspaceId: string;
|
||||
connectedAccountId: string;
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
scopes: string[];
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity;
|
||||
manager: WorkspaceEntityManager;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class UpdateConnectedAccountOnReconnectService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
async updateConnectedAccountOnReconnect(
|
||||
input: UpdateConnectedAccountOnReconnectInput,
|
||||
): Promise<void> {
|
||||
const {
|
||||
workspaceId,
|
||||
connectedAccountId,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
scopes,
|
||||
connectedAccount,
|
||||
manager,
|
||||
} = input;
|
||||
|
||||
const connectedAccountRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
const updatedConnectedAccount = await connectedAccountRepository.update(
|
||||
{
|
||||
id: connectedAccountId,
|
||||
},
|
||||
{
|
||||
accessToken,
|
||||
refreshToken,
|
||||
scopes,
|
||||
authFailedAt: null,
|
||||
},
|
||||
manager,
|
||||
);
|
||||
|
||||
const connectedAccountMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'connectedAccount', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'connectedAccount',
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
events: [
|
||||
{
|
||||
recordId: connectedAccountId,
|
||||
objectMetadata: connectedAccountMetadata,
|
||||
properties: {
|
||||
before: connectedAccount,
|
||||
after: {
|
||||
...connectedAccount,
|
||||
...updatedConnectedAccount.raw[0],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user