Bettertyping (#10725)

To get better at catching errors on providers
(It will ease also my coming PR to send microsoft messages using
workflow)
This commit is contained in:
Guillim
2025-03-07 14:11:56 +01:00
committed by GitHub
parent 4be75fb7da
commit 4bdcf77028
12 changed files with 48 additions and 28 deletions

View File

@ -0,0 +1 @@
export const MICROSOFT_SEND_SCOPE = 'Mail.Send';

View File

@ -1,11 +1,12 @@
import { CalendarChannel } from '@/accounts/types/CalendarChannel'; import { CalendarChannel } from '@/accounts/types/CalendarChannel';
import { ConnectedAccountProvider } from 'twenty-shared';
import { MessageChannel } from './MessageChannel'; import { MessageChannel } from './MessageChannel';
export type ConnectedAccount = { export type ConnectedAccount = {
id: string; id: string;
handle: string; handle: string;
provider: string; provider: ConnectedAccountProvider;
accessToken: string; accessToken: string;
refreshToken: string; refreshToken: string;
accountOwnerId: string; accountOwnerId: string;

View File

@ -6,6 +6,7 @@ import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAut
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro'; import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { ConnectedAccountProvider } from 'twenty-shared';
import { import {
Button, Button,
Card, Card,
@ -60,7 +61,7 @@ export const SettingsAccountsListEmptyStateCard = ({
Icon={IconGoogle} Icon={IconGoogle}
title={t`Connect with Google`} title={t`Connect with Google`}
variant="secondary" variant="secondary"
onClick={() => triggerApisOAuth('google')} onClick={() => triggerApisOAuth(ConnectedAccountProvider.GOOGLE)}
/> />
)} )}
@ -69,7 +70,7 @@ export const SettingsAccountsListEmptyStateCard = ({
Icon={IconMicrosoft} Icon={IconMicrosoft}
title={t`Connect with Microsoft`} title={t`Connect with Microsoft`}
variant="secondary" variant="secondary"
onClick={() => triggerApisOAuth('microsoft')} onClick={() => triggerApisOAuth(ConnectedAccountProvider.MICROSOFT)}
/> />
)} )}
</StyledBody> </StyledBody>

View File

@ -1,19 +1,20 @@
import { AppPath } from '@/types/AppPath'; import { AppPath } from '@/types/AppPath';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
import { ConnectedAccountProvider } from 'twenty-shared';
import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { import {
CalendarChannelVisibility, CalendarChannelVisibility,
MessageChannelVisibility, MessageChannelVisibility,
useGenerateTransientTokenMutation, useGenerateTransientTokenMutation,
} from '~/generated/graphql'; } from '~/generated/graphql';
import { useRedirect } from '@/domain-manager/hooks/useRedirect';
const getProviderUrl = (provider: string) => { const getProviderUrl = (provider: ConnectedAccountProvider) => {
switch (provider) { switch (provider) {
case 'google': case ConnectedAccountProvider.GOOGLE:
return 'google-apis'; return 'google-apis';
case 'microsoft': case ConnectedAccountProvider.MICROSOFT:
return 'microsoft-apis'; return 'microsoft-apis';
default: default:
throw new Error(`Provider ${provider} is not supported`); throw new Error(`Provider ${provider} is not supported`);
@ -26,7 +27,7 @@ export const useTriggerApisOAuth = () => {
const triggerApisOAuth = useCallback( const triggerApisOAuth = useCallback(
async ( async (
provider: string, provider: ConnectedAccountProvider,
{ {
redirectLocation, redirectLocation,
messageVisibility, messageVisibility,

View File

@ -7,7 +7,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro'; import { useLingui } from '@lingui/react/macro';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { capitalize } from 'twenty-shared'; import { capitalize, ConnectedAccountProvider } from 'twenty-shared';
import { import {
Card, Card,
IconGoogle, IconGoogle,
@ -136,7 +136,9 @@ export const SettingsSecurityAuthProvidersOptionsList = () => {
checked={currentWorkspace.isGoogleAuthEnabled} checked={currentWorkspace.isGoogleAuthEnabled}
advancedMode advancedMode
divider divider
onChange={() => toggleAuthMethod('google')} onChange={() =>
toggleAuthMethod(ConnectedAccountProvider.GOOGLE)
}
/> />
)} )}
{authProviders.microsoft === true && ( {authProviders.microsoft === true && (
@ -147,7 +149,9 @@ export const SettingsSecurityAuthProvidersOptionsList = () => {
checked={currentWorkspace.isMicrosoftAuthEnabled} checked={currentWorkspace.isMicrosoftAuthEnabled}
advancedMode advancedMode
divider divider
onChange={() => toggleAuthMethod('microsoft')} onChange={() =>
toggleAuthMethod(ConnectedAccountProvider.MICROSOFT)
}
/> />
)} )}
{authProviders.password === true && ( {authProviders.password === true && (

View File

@ -14,7 +14,7 @@ import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components
import { useTheme } from '@emotion/react'; import { useTheme } from '@emotion/react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared'; import { ConnectedAccountProvider, isDefined } from 'twenty-shared';
import { IconPlus, useIcons } from 'twenty-ui'; import { IconPlus, useIcons } from 'twenty-ui';
import { JsonValue } from 'type-fest'; import { JsonValue } from 'type-fest';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
@ -71,7 +71,7 @@ export const WorkflowEditActionFormSendEmail = ({
!isDefined(scopes) || !isDefined(scopes) ||
!isDefined(scopes.find((scope) => scope === GMAIL_SEND_SCOPE)) !isDefined(scopes.find((scope) => scope === GMAIL_SEND_SCOPE))
) { ) {
await triggerApisOAuth('google', { await triggerApisOAuth(ConnectedAccountProvider.GOOGLE, {
redirectLocation: redirectUrl, redirectLocation: redirectUrl,
loginHint: connectedAccount.handle, loginHint: connectedAccount.handle,
}); });
@ -200,7 +200,7 @@ export const WorkflowEditActionFormSendEmail = ({
options={connectedAccountOptions} options={connectedAccountOptions}
callToActionButton={{ callToActionButton={{
onClick: () => onClick: () =>
triggerApisOAuth('google', { triggerApisOAuth(ConnectedAccountProvider.GOOGLE, {
redirectLocation: redirectUrl, redirectLocation: redirectUrl,
}), }),
Icon: IconPlus, Icon: IconPlus,

View File

@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ConnectedAccountProvider } from 'twenty-shared';
import { GoogleCalendarGetEventsService } from 'src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/services/google-calendar-get-events.service'; import { GoogleCalendarGetEventsService } from 'src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/services/google-calendar-get-events.service';
import { MicrosoftCalendarGetEventsService } from 'src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/services/microsoft-calendar-get-events.service'; import { MicrosoftCalendarGetEventsService } from 'src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/services/microsoft-calendar-get-events.service';
import { import {
@ -31,12 +33,12 @@ export class CalendarGetCalendarEventsService {
syncCursor?: string, syncCursor?: string,
): Promise<GetCalendarEventsResponse> { ): Promise<GetCalendarEventsResponse> {
switch (connectedAccount.provider) { switch (connectedAccount.provider) {
case 'google': case ConnectedAccountProvider.GOOGLE:
return this.googleCalendarGetEventsService.getCalendarEvents( return this.googleCalendarGetEventsService.getCalendarEvents(
connectedAccount, connectedAccount,
syncCursor, syncCursor,
); );
case 'microsoft': case ConnectedAccountProvider.MICROSOFT:
return this.microsoftCalendarGetEventsService.getCalendarEvents( return this.microsoftCalendarGetEventsService.getCalendarEvents(
connectedAccount, connectedAccount,
syncCursor, syncCursor,

View File

@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { assertUnreachable, ConnectedAccountProvider } from 'twenty-shared';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/google-email-alias-manager.service'; import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/google-email-alias-manager.service';
import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/microsoft-email-alias-manager.service'; import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/microsoft-email-alias-manager.service';
@ -19,20 +21,21 @@ export class EmailAliasManagerService {
let handleAliases: string[]; let handleAliases: string[];
switch (connectedAccount.provider) { switch (connectedAccount.provider) {
case 'microsoft': case ConnectedAccountProvider.MICROSOFT:
handleAliases = handleAliases =
await this.microsoftEmailAliasManagerService.getHandleAliases( await this.microsoftEmailAliasManagerService.getHandleAliases(
connectedAccount, connectedAccount,
); );
break; break;
case 'google': case ConnectedAccountProvider.GOOGLE:
handleAliases = handleAliases =
await this.googleEmailAliasManagerService.getHandleAliases( await this.googleEmailAliasManagerService.getHandleAliases(
connectedAccount, connectedAccount,
); );
break; break;
default: default:
throw new Error( assertUnreachable(
connectedAccount.provider,
`Email alias manager for provider ${connectedAccount.provider} is not implemented`, `Email alias manager for provider ${connectedAccount.provider} is not implemented`,
); );
} }

View File

@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { OAuth2Client } from 'google-auth-library'; import { OAuth2Client } from 'google-auth-library';
import { ConnectedAccountProvider } from 'twenty-shared';
import { GoogleOAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/drivers/google/google-oauth2-client-manager.service'; import { GoogleOAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/drivers/google/google-oauth2-client-manager.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';
@ -20,7 +21,7 @@ export class OAuth2ClientManagerService {
const { refreshToken } = connectedAccount; const { refreshToken } = connectedAccount;
switch (connectedAccount.provider) { switch (connectedAccount.provider) {
case 'google': case ConnectedAccountProvider.GOOGLE:
return this.googleOAuth2ClientManagerService.getOAuth2Client( return this.googleOAuth2ClientManagerService.getOAuth2Client(
refreshToken, refreshToken,
); );

View File

@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ConnectedAccountProvider } from 'twenty-shared';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
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 { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
@ -24,9 +26,9 @@ export class MessagingCursorService {
); );
switch (connectedAccount.provider) { switch (connectedAccount.provider) {
case 'google': case ConnectedAccountProvider.GOOGLE:
return messageChannel.syncCursor; return messageChannel.syncCursor;
case 'microsoft': { case ConnectedAccountProvider.MICROSOFT: {
const folder = await folderRepository.findOne({ const folder = await folderRepository.findOne({
where: { where: {
id: folderId, id: folderId,

View File

@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ConnectedAccountProvider } from 'twenty-shared';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; 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'; import { MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
@ -46,7 +48,7 @@ export class MessagingGetMessageListService {
messageChannel: MessageChannelWorkspaceEntity, messageChannel: MessageChannelWorkspaceEntity,
): Promise<GetFullMessageListForFoldersResponse[]> { ): Promise<GetFullMessageListForFoldersResponse[]> {
switch (messageChannel.connectedAccount.provider) { switch (messageChannel.connectedAccount.provider) {
case 'google': case ConnectedAccountProvider.GOOGLE:
return [ return [
{ {
...(await this.gmailGetMessageListService.getFullMessageList( ...(await this.gmailGetMessageListService.getFullMessageList(
@ -55,7 +57,7 @@ export class MessagingGetMessageListService {
folderId: undefined, folderId: undefined,
}, },
]; ];
case 'microsoft': { case ConnectedAccountProvider.MICROSOFT: {
const folderRepository = const folderRepository =
await this.twentyORMManager.getRepository<MessageFolderWorkspaceEntity>( await this.twentyORMManager.getRepository<MessageFolderWorkspaceEntity>(
'messageFolder', 'messageFolder',
@ -84,7 +86,7 @@ export class MessagingGetMessageListService {
messageChannel: MessageChannelWorkspaceEntity, messageChannel: MessageChannelWorkspaceEntity,
): Promise<GetPartialMessageListForFoldersResponse[]> { ): Promise<GetPartialMessageListForFoldersResponse[]> {
switch (messageChannel.connectedAccount.provider) { switch (messageChannel.connectedAccount.provider) {
case 'google': case ConnectedAccountProvider.GOOGLE:
return [ return [
{ {
...(await this.gmailGetMessageListService.getPartialMessageList( ...(await this.gmailGetMessageListService.getPartialMessageList(
@ -94,7 +96,7 @@ export class MessagingGetMessageListService {
folderId: undefined, folderId: undefined,
}, },
]; ];
case 'microsoft': case ConnectedAccountProvider.MICROSOFT:
return this.microsoftGetMessageListService.getPartialMessageListForFolders( return this.microsoftGetMessageListService.getPartialMessageListForFolders(
messageChannel.connectedAccount, messageChannel.connectedAccount,
messageChannel, messageChannel,

View File

@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ConnectedAccountProvider } from 'twenty-shared';
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 { GmailGetMessagesService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-messages.service'; import { GmailGetMessagesService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-messages.service';
import { MicrosoftGetMessagesService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service'; import { MicrosoftGetMessagesService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service';
@ -32,13 +34,13 @@ export class MessagingGetMessagesService {
workspaceId: string, workspaceId: string,
): Promise<GetMessagesResponse> { ): Promise<GetMessagesResponse> {
switch (connectedAccount.provider) { switch (connectedAccount.provider) {
case 'google': case ConnectedAccountProvider.GOOGLE:
return this.gmailGetMessagesService.getMessages( return this.gmailGetMessagesService.getMessages(
messageIds, messageIds,
connectedAccount, connectedAccount,
workspaceId, workspaceId,
); );
case 'microsoft': case ConnectedAccountProvider.MICROSOFT:
return this.microsoftGetMessagesService.getMessages( return this.microsoftGetMessagesService.getMessages(
messageIds, messageIds,
connectedAccount, connectedAccount,