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 { MicrosoftAuthController } from 'src/engine/core-modules/auth/controllers/microsoft-auth.controller';
|
||||||
import { SSOAuthController } from 'src/engine/core-modules/auth/controllers/sso-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 { 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 { GoogleAPIsService } from 'src/engine/core-modules/auth/services/google-apis.service';
|
||||||
import { MicrosoftAPIsService } from 'src/engine/core-modules/auth/services/microsoft-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 { ResetCalendarChannelService } from 'src/engine/core-modules/auth/services/reset-calendar-channel.service';
|
||||||
import { AuthSsoService } from 'src/engine/core-modules/auth/services/auth-sso.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 { 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 { 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 { 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 { 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';
|
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,
|
RefreshTokenService,
|
||||||
LoginTokenService,
|
LoginTokenService,
|
||||||
ResetPasswordService,
|
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,
|
TransientTokenService,
|
||||||
ApiKeyService,
|
ApiKeyService,
|
||||||
AuthSsoService,
|
AuthSsoService,
|
||||||
// reenable when working on: https://github.com/twentyhq/twenty/issues/9143
|
|
||||||
// OAuthService,
|
|
||||||
],
|
],
|
||||||
exports: [AccessTokenService, LoginTokenService, RefreshTokenService],
|
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 { Repository } from 'typeorm';
|
||||||
import { v4 } from 'uuid';
|
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 { 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 { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||||
@ -20,16 +25,12 @@ import {
|
|||||||
CalendarEventListFetchJobData,
|
CalendarEventListFetchJobData,
|
||||||
} from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job';
|
} from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job';
|
||||||
import {
|
import {
|
||||||
CalendarChannelSyncStage,
|
|
||||||
CalendarChannelVisibility,
|
CalendarChannelVisibility,
|
||||||
CalendarChannelWorkspaceEntity,
|
CalendarChannelWorkspaceEntity,
|
||||||
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||||
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
|
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 { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||||
import {
|
import {
|
||||||
MessageChannelSyncStage,
|
|
||||||
MessageChannelSyncStatus,
|
|
||||||
MessageChannelType,
|
|
||||||
MessageChannelVisibility,
|
MessageChannelVisibility,
|
||||||
MessageChannelWorkspaceEntity,
|
MessageChannelWorkspaceEntity,
|
||||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||||
@ -49,6 +50,12 @@ export class GoogleAPIsService {
|
|||||||
private readonly calendarQueueService: MessageQueueService,
|
private readonly calendarQueueService: MessageQueueService,
|
||||||
private readonly twentyConfigService: TwentyConfigService,
|
private readonly twentyConfigService: TwentyConfigService,
|
||||||
private readonly accountsToReconnectService: AccountsToReconnectService,
|
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,
|
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
@ -110,145 +117,47 @@ export class GoogleAPIsService {
|
|||||||
await workspaceDataSource.transaction(
|
await workspaceDataSource.transaction(
|
||||||
async (manager: WorkspaceEntityManager) => {
|
async (manager: WorkspaceEntityManager) => {
|
||||||
if (!existingAccountId) {
|
if (!existingAccountId) {
|
||||||
const newConnectedAccount = await connectedAccountRepository.save(
|
await this.createConnectedAccountService.createConnectedAccount({
|
||||||
{
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
connectedAccountId: newOrExistingConnectedAccountId,
|
||||||
|
handle,
|
||||||
|
provider: ConnectedAccountProvider.GOOGLE,
|
||||||
|
accessToken: input.accessToken,
|
||||||
|
refreshToken: input.refreshToken,
|
||||||
|
accountOwnerId: workspaceMemberId,
|
||||||
|
scopes,
|
||||||
|
manager,
|
||||||
});
|
});
|
||||||
|
|
||||||
const newMessageChannel = await messageChannelRepository.save(
|
await this.createMessageChannelService.createMessageChannel({
|
||||||
{
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
connectedAccountId: newOrExistingConnectedAccountId,
|
||||||
|
handle,
|
||||||
|
messageVisibility,
|
||||||
|
manager,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isCalendarEnabled) {
|
if (isCalendarEnabled) {
|
||||||
const newCalendarChannel = await calendarChannelRepository.save(
|
await this.createCalendarChannelService.createCalendarChannel({
|
||||||
{
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
connectedAccountId: newOrExistingConnectedAccountId,
|
||||||
|
handle,
|
||||||
|
calendarVisibility,
|
||||||
|
manager,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const updatedConnectedAccount =
|
await this.updateConnectedAccountOnReconnectService.updateConnectedAccountOnReconnect(
|
||||||
await connectedAccountRepository.update(
|
{
|
||||||
{
|
workspaceId,
|
||||||
id: newOrExistingConnectedAccountId,
|
connectedAccountId: newOrExistingConnectedAccountId,
|
||||||
},
|
accessToken: input.accessToken,
|
||||||
{
|
refreshToken: input.refreshToken,
|
||||||
accessToken: input.accessToken,
|
scopes,
|
||||||
refreshToken: input.refreshToken,
|
connectedAccount,
|
||||||
scopes,
|
|
||||||
},
|
|
||||||
manager,
|
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 =
|
const workspaceMemberRepository =
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
||||||
@ -270,81 +179,16 @@ export class GoogleAPIsService {
|
|||||||
newOrExistingConnectedAccountId,
|
newOrExistingConnectedAccountId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const messageChannels = await messageChannelRepository.find({
|
await this.resetMessageChannelService.resetMessageChannels({
|
||||||
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] },
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
connectedAccountId: newOrExistingConnectedAccountId,
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
manager,
|
manager,
|
||||||
);
|
});
|
||||||
|
|
||||||
const calendarChannelMetadata =
|
await this.resetCalendarChannelService.resetCalendarChannels({
|
||||||
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,
|
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 { Repository } from 'typeorm';
|
||||||
import { v4 } from 'uuid';
|
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 { 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 { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||||
@ -20,21 +27,15 @@ import {
|
|||||||
CalendarEventListFetchJobData,
|
CalendarEventListFetchJobData,
|
||||||
} from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job';
|
} from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job';
|
||||||
import {
|
import {
|
||||||
CalendarChannelSyncStage,
|
|
||||||
CalendarChannelVisibility,
|
CalendarChannelVisibility,
|
||||||
CalendarChannelWorkspaceEntity,
|
CalendarChannelWorkspaceEntity,
|
||||||
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||||
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
|
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 { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||||
import {
|
import {
|
||||||
MessageChannelSyncStage,
|
|
||||||
MessageChannelSyncStatus,
|
|
||||||
MessageChannelType,
|
|
||||||
MessageChannelVisibility,
|
MessageChannelVisibility,
|
||||||
MessageChannelWorkspaceEntity,
|
MessageChannelWorkspaceEntity,
|
||||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
} 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 {
|
import {
|
||||||
MessagingMessageListFetchJob,
|
MessagingMessageListFetchJob,
|
||||||
MessagingMessageListFetchJobData,
|
MessagingMessageListFetchJobData,
|
||||||
@ -50,6 +51,14 @@ export class MicrosoftAPIsService {
|
|||||||
@InjectMessageQueue(MessageQueue.calendarQueue)
|
@InjectMessageQueue(MessageQueue.calendarQueue)
|
||||||
private readonly calendarQueueService: MessageQueueService,
|
private readonly calendarQueueService: MessageQueueService,
|
||||||
private readonly accountsToReconnectService: AccountsToReconnectService,
|
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,
|
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
@ -98,12 +107,6 @@ export class MicrosoftAPIsService {
|
|||||||
'messageChannel',
|
'messageChannel',
|
||||||
);
|
);
|
||||||
|
|
||||||
const messageFolderRepository =
|
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageFolderWorkspaceEntity>(
|
|
||||||
workspaceId,
|
|
||||||
'messageFolder',
|
|
||||||
);
|
|
||||||
|
|
||||||
const workspaceDataSource =
|
const workspaceDataSource =
|
||||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -114,169 +117,56 @@ export class MicrosoftAPIsService {
|
|||||||
await workspaceDataSource.transaction(
|
await workspaceDataSource.transaction(
|
||||||
async (manager: WorkspaceEntityManager) => {
|
async (manager: WorkspaceEntityManager) => {
|
||||||
if (!existingAccountId) {
|
if (!existingAccountId) {
|
||||||
const newConnectedAccount = await connectedAccountRepository.save(
|
await this.createConnectedAccountService.createConnectedAccount({
|
||||||
{
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
connectedAccountId: newOrExistingConnectedAccountId,
|
||||||
|
handle,
|
||||||
|
provider: ConnectedAccountProvider.MICROSOFT,
|
||||||
|
accessToken: input.accessToken,
|
||||||
|
refreshToken: input.refreshToken,
|
||||||
|
accountOwnerId: workspaceMemberId,
|
||||||
|
scopes,
|
||||||
|
manager,
|
||||||
});
|
});
|
||||||
|
|
||||||
const newMessageChannel = await messageChannelRepository.save(
|
const newMessageChannelId =
|
||||||
{
|
await this.createMessageChannelService.createMessageChannel({
|
||||||
id: v4(),
|
workspaceId,
|
||||||
connectedAccountId: newOrExistingConnectedAccountId,
|
connectedAccountId: newOrExistingConnectedAccountId,
|
||||||
type: MessageChannelType.EMAIL,
|
|
||||||
handle,
|
handle,
|
||||||
visibility:
|
messageVisibility,
|
||||||
messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING,
|
manager,
|
||||||
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 },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
await this.createMessageFolderService.createMessageFolders({
|
||||||
objectMetadataNameSingular: 'messageChannel',
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: newMessageChannel.id,
|
|
||||||
objectMetadata: messageChannelMetadata,
|
|
||||||
properties: {
|
|
||||||
after: newMessageChannel,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
messageChannelId: newMessageChannelId,
|
||||||
|
manager,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.twentyConfigService.get('CALENDAR_PROVIDER_MICROSOFT_ENABLED')
|
this.twentyConfigService.get('CALENDAR_PROVIDER_MICROSOFT_ENABLED')
|
||||||
) {
|
) {
|
||||||
const newCalendarChannel = await calendarChannelRepository.save(
|
await this.createCalendarChannelService.createCalendarChannel({
|
||||||
{
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
connectedAccountId: newOrExistingConnectedAccountId,
|
||||||
|
handle,
|
||||||
|
calendarVisibility,
|
||||||
|
manager,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const updatedConnectedAccount =
|
await this.updateConnectedAccountOnReconnectService.updateConnectedAccountOnReconnect(
|
||||||
await connectedAccountRepository.update(
|
{
|
||||||
{
|
workspaceId,
|
||||||
id: newOrExistingConnectedAccountId,
|
connectedAccountId: newOrExistingConnectedAccountId,
|
||||||
},
|
accessToken: input.accessToken,
|
||||||
{
|
refreshToken: input.refreshToken,
|
||||||
accessToken: input.accessToken,
|
scopes,
|
||||||
refreshToken: input.refreshToken,
|
connectedAccount,
|
||||||
scopes,
|
|
||||||
},
|
|
||||||
manager,
|
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 =
|
const workspaceMemberRepository =
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
||||||
@ -298,82 +188,22 @@ export class MicrosoftAPIsService {
|
|||||||
newOrExistingConnectedAccountId,
|
newOrExistingConnectedAccountId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const messageChannels = await messageChannelRepository.find({
|
await this.resetMessageChannelService.resetMessageChannels({
|
||||||
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] },
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
connectedAccountId: newOrExistingConnectedAccountId,
|
||||||
|
|
||||||
// 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,
|
|
||||||
},
|
|
||||||
manager,
|
manager,
|
||||||
);
|
});
|
||||||
|
|
||||||
const calendarChannelMetadata =
|
await this.resetMessageFolderService.resetMessageFolders({
|
||||||
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,
|
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