diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index d1bcde8917..33d8ef9c79 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -1733,7 +1733,6 @@ enum FeatureFlagKey { IS_USAGE_ANALYTICS_ENABLED IS_RICH_TEXT_V1_MIGRATED IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED - IS_CONNECTED_ACCOUNT_MIGRATED IS_RECORD_TABLE_WIDGET_ENABLED IS_DATASOURCE_MIGRATED } @@ -2542,7 +2541,7 @@ type ConnectedImapSmtpCaldavAccount { id: UUID! handle: String! provider: String! - accountOwnerId: UUID! + userWorkspaceId: UUID! connectionParameters: ImapSmtpCaldavConnectionParameters } @@ -3013,7 +3012,7 @@ type MessageFolder { name: String isSentFolder: Boolean! isSynced: Boolean! - parentFolderId: UUID + parentFolderId: String externalId: String pendingSyncAction: MessageFolderPendingSyncAction! messageChannelId: UUID! diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index dcc9001b10..aafae8ee3f 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -1424,7 +1424,7 @@ export interface PublicFeatureFlag { __typename: 'PublicFeatureFlag' } -export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED' +export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_MARKETPLACE_SETTING_TAB_VISIBLE' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_RECORD_TABLE_WIDGET_ENABLED' | 'IS_DATASOURCE_MIGRATED' export interface ClientConfigMaintenanceMode { startAt: Scalars['DateTime'] @@ -2250,7 +2250,7 @@ export interface ConnectedImapSmtpCaldavAccount { id: Scalars['UUID'] handle: Scalars['String'] provider: Scalars['String'] - accountOwnerId: Scalars['UUID'] + userWorkspaceId: Scalars['UUID'] connectionParameters?: ImapSmtpCaldavConnectionParameters __typename: 'ConnectedImapSmtpCaldavAccount' } @@ -2628,7 +2628,7 @@ export interface MessageFolder { name?: Scalars['String'] isSentFolder: Scalars['Boolean'] isSynced: Scalars['Boolean'] - parentFolderId?: Scalars['UUID'] + parentFolderId?: Scalars['String'] externalId?: Scalars['String'] pendingSyncAction: MessageFolderPendingSyncAction messageChannelId: Scalars['UUID'] @@ -5431,7 +5431,7 @@ export interface ConnectedImapSmtpCaldavAccountGenqlSelection{ id?: boolean | number handle?: boolean | number provider?: boolean | number - accountOwnerId?: boolean | number + userWorkspaceId?: boolean | number connectionParameters?: ImapSmtpCaldavConnectionParametersGenqlSelection __typename?: boolean | number __scalar?: boolean | number @@ -9101,7 +9101,6 @@ export const enumFeatureFlagKey = { IS_USAGE_ANALYTICS_ENABLED: 'IS_USAGE_ANALYTICS_ENABLED' as const, IS_RICH_TEXT_V1_MIGRATED: 'IS_RICH_TEXT_V1_MIGRATED' as const, IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' as const, - IS_CONNECTED_ACCOUNT_MIGRATED: 'IS_CONNECTED_ACCOUNT_MIGRATED' as const, IS_RECORD_TABLE_WIDGET_ENABLED: 'IS_RECORD_TABLE_WIDGET_ENABLED' as const, IS_DATASOURCE_MIGRATED: 'IS_DATASOURCE_MIGRATED' as const } diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index 119fd248a9..ef56ef3d7f 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -5076,7 +5076,7 @@ export default { "provider": [ 1 ], - "accountOwnerId": [ + "userWorkspaceId": [ 3 ], "connectionParameters": [ @@ -5903,7 +5903,7 @@ export default { 6 ], "parentFolderId": [ - 3 + 1 ], "externalId": [ 1 diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index f0209c0072..9a5fe979e8 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1033,11 +1033,11 @@ export type ConnectedAccountDto = { export type ConnectedImapSmtpCaldavAccount = { __typename?: 'ConnectedImapSmtpCaldavAccount'; - accountOwnerId: Scalars['UUID']; connectionParameters?: Maybe; handle: Scalars['String']; id: Scalars['UUID']; provider: Scalars['String']; + userWorkspaceId: Scalars['UUID']; }; export type ConnectionParameters = { @@ -1722,7 +1722,6 @@ export type FeatureFlag = { export enum FeatureFlagKey { IS_AI_ENABLED = 'IS_AI_ENABLED', IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED', - IS_CONNECTED_ACCOUNT_MIGRATED = 'IS_CONNECTED_ACCOUNT_MIGRATED', IS_DATASOURCE_MIGRATED = 'IS_DATASOURCE_MIGRATED', IS_DRAFT_EMAIL_ENABLED = 'IS_DRAFT_EMAIL_ENABLED', IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED', @@ -2407,7 +2406,7 @@ export type MessageFolder = { isSynced: Scalars['Boolean']; messageChannelId: Scalars['UUID']; name?: Maybe; - parentFolderId?: Maybe; + parentFolderId?: Maybe; pendingSyncAction: MessageFolderPendingSyncAction; updatedAt: Scalars['DateTime']; }; @@ -7023,7 +7022,7 @@ export type GetConnectedImapSmtpCaldavAccountQueryVariables = Exact<{ }>; -export type GetConnectedImapSmtpCaldavAccountQuery = { __typename?: 'Query', getConnectedImapSmtpCaldavAccount: { __typename?: 'ConnectedImapSmtpCaldavAccount', id: string, handle: string, provider: string, accountOwnerId: string, connectionParameters?: { __typename?: 'ImapSmtpCaldavConnectionParameters', IMAP?: { __typename?: 'ConnectionParametersOutput', host: string, port: number, secure?: boolean | null, username?: string | null, password: string } | null, SMTP?: { __typename?: 'ConnectionParametersOutput', host: string, username?: string | null, port: number, secure?: boolean | null, password: string } | null, CALDAV?: { __typename?: 'ConnectionParametersOutput', host: string, username?: string | null, password: string } | null } | null } }; +export type GetConnectedImapSmtpCaldavAccountQuery = { __typename?: 'Query', getConnectedImapSmtpCaldavAccount: { __typename?: 'ConnectedImapSmtpCaldavAccount', id: string, handle: string, provider: string, userWorkspaceId: string, connectionParameters?: { __typename?: 'ImapSmtpCaldavConnectionParameters', IMAP?: { __typename?: 'ConnectionParametersOutput', host: string, port: number, secure?: boolean | null, username?: string | null, password: string } | null, SMTP?: { __typename?: 'ConnectionParametersOutput', host: string, username?: string | null, port: number, secure?: boolean | null, password: string } | null, CALDAV?: { __typename?: 'ConnectionParametersOutput', host: string, username?: string | null, password: string } | null } | null } }; export type MyCalendarChannelsQueryVariables = Exact<{ connectedAccountId?: InputMaybe; @@ -8345,7 +8344,7 @@ export const UpdateCalendarChannelDocument = {"kind":"Document","definitions":[{ export const UpdateMessageChannelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateMessageChannel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateMessageChannelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMessageChannel"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"contactAutoCreationPolicy"}},{"kind":"Field","name":{"kind":"Name","value":"excludeNonProfessionalEmails"}},{"kind":"Field","name":{"kind":"Name","value":"excludeGroupEmails"}},{"kind":"Field","name":{"kind":"Name","value":"messageFolderImportPolicy"}}]}}]}}]} as unknown as DocumentNode; export const UpdateMessageFolderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateMessageFolder"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateMessageFolderInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMessageFolder"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isSynced"}}]}}]}}]} as unknown as DocumentNode; export const UpdateMessageFoldersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateMessageFolders"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateMessageFoldersInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMessageFolders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"isSynced"}}]}}]}}]} as unknown as DocumentNode; -export const GetConnectedImapSmtpCaldavAccountDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetConnectedImapSmtpCaldavAccount"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getConnectedImapSmtpCaldavAccount"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"provider"}},{"kind":"Field","name":{"kind":"Name","value":"accountOwnerId"}},{"kind":"Field","name":{"kind":"Name","value":"connectionParameters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"IMAP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"password"}}]}},{"kind":"Field","name":{"kind":"Name","value":"SMTP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"password"}}]}},{"kind":"Field","name":{"kind":"Name","value":"CALDAV"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"password"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const GetConnectedImapSmtpCaldavAccountDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetConnectedImapSmtpCaldavAccount"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getConnectedImapSmtpCaldavAccount"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"provider"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"connectionParameters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"IMAP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"password"}}]}},{"kind":"Field","name":{"kind":"Name","value":"SMTP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"port"}},{"kind":"Field","name":{"kind":"Name","value":"secure"}},{"kind":"Field","name":{"kind":"Name","value":"password"}}]}},{"kind":"Field","name":{"kind":"Name","value":"CALDAV"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"host"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"password"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const MyCalendarChannelsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MyCalendarChannels"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myCalendarChannels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"connectedAccountId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"syncStage"}},{"kind":"Field","name":{"kind":"Name","value":"syncStageStartedAt"}},{"kind":"Field","name":{"kind":"Name","value":"isContactAutoCreationEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"contactAutoCreationPolicy"}},{"kind":"Field","name":{"kind":"Name","value":"isSyncEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"connectedAccountId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; export const MyConnectedAccountsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MyConnectedAccounts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myConnectedAccounts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"provider"}},{"kind":"Field","name":{"kind":"Name","value":"authFailedAt"}},{"kind":"Field","name":{"kind":"Name","value":"scopes"}},{"kind":"Field","name":{"kind":"Name","value":"handleAliases"}},{"kind":"Field","name":{"kind":"Name","value":"lastSignedInAt"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; export const MyMessageChannelsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MyMessageChannels"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"myMessageChannels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"connectedAccountId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"handle"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"isContactAutoCreationEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"contactAutoCreationPolicy"}},{"kind":"Field","name":{"kind":"Name","value":"messageFolderImportPolicy"}},{"kind":"Field","name":{"kind":"Name","value":"excludeNonProfessionalEmails"}},{"kind":"Field","name":{"kind":"Name","value":"excludeGroupEmails"}},{"kind":"Field","name":{"kind":"Name","value":"isSyncEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"syncStatus"}},{"kind":"Field","name":{"kind":"Name","value":"syncStage"}},{"kind":"Field","name":{"kind":"Name","value":"syncStageStartedAt"}},{"kind":"Field","name":{"kind":"Name","value":"connectedAccountId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelDetails.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelDetails.tsx index be0e4d1095..155d05721a 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelDetails.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsCalendarChannelDetails.tsx @@ -1,8 +1,5 @@ import { type CalendarChannel } from '@/accounts/types/CalendarChannel'; -import { CoreObjectNameSingular, FeatureFlagKey } from 'twenty-shared/types'; -import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { UPDATE_CALENDAR_CHANNEL } from '@/settings/accounts/graphql/mutations/updateCalendarChannel'; -import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; import { useMutation } from '@apollo/client/react'; import { SettingsAccountsEventVisibilitySettingsCard } from '@/settings/accounts/components/SettingsAccountsCalendarVisibilitySettingsCard'; import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle'; @@ -30,25 +27,12 @@ type SettingsAccountsCalendarChannelDetailsProps = { export const SettingsAccountsCalendarChannelDetails = ({ calendarChannel, }: SettingsAccountsCalendarChannelDetailsProps) => { - const featureFlagsMap = useFeatureFlagsMap(); - const isMigrated = - featureFlagsMap[FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED] ?? false; - - const { updateOneRecord } = useUpdateOneRecord(); const [updateMetadataChannel] = useMutation(UPDATE_CALENDAR_CHANNEL); const updateChannel = (update: Record) => { - if (isMigrated) { - updateMetadataChannel({ - variables: { input: { id: calendarChannel.id, update } }, - }); - } else { - updateOneRecord({ - objectNameSingular: CoreObjectNameSingular.CalendarChannel, - idToUpdate: calendarChannel.id, - updateOneRecordInput: update, - }); - } + updateMetadataChannel({ + variables: { input: { id: calendarChannel.id, update } }, + }); }; const handleVisibilityChange = (value: CalendarChannelVisibility) => { diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelDetails.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelDetails.tsx index 2cfafb624b..df818de18f 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelDetails.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsMessageChannelDetails.tsx @@ -5,10 +5,7 @@ import { type MessageChannelContactAutoCreationPolicy, type MessageFolderImportPolicy, } from '@/accounts/types/MessageChannel'; -import { CoreObjectNameSingular, FeatureFlagKey } from 'twenty-shared/types'; -import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { UPDATE_MESSAGE_CHANNEL } from '@/settings/accounts/graphql/mutations/updateMessageChannel'; -import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; import { useMutation } from '@apollo/client/react'; import { SettingsAccountsMessageAutoCreationCard } from '@/settings/accounts/components/SettingsAccountsMessageAutoCreationCard'; import { SettingsAccountsMessageFolderCard } from '@/settings/accounts/components/SettingsAccountsMessageFolderCard'; @@ -43,25 +40,12 @@ const StyledDetailsContainer = styled.div` export const SettingsAccountsMessageChannelDetails = ({ messageChannel, }: SettingsAccountsMessageChannelDetailsProps) => { - const featureFlagsMap = useFeatureFlagsMap(); - const isMigrated = - featureFlagsMap[FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED] ?? false; - - const { updateOneRecord } = useUpdateOneRecord(); const [updateMetadataChannel] = useMutation(UPDATE_MESSAGE_CHANNEL); const updateChannel = (update: Record) => { - if (isMigrated) { - updateMetadataChannel({ - variables: { input: { id: messageChannel.id, update } }, - }); - } else { - updateOneRecord({ - objectNameSingular: CoreObjectNameSingular.MessageChannel, - idToUpdate: messageChannel.id, - updateOneRecordInput: update, - }); - } + updateMetadataChannel({ + variables: { input: { id: messageChannel.id, update } }, + }); }; const handleVisibilityChange = (value: MessageChannelVisibility) => { diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsRowDropdownMenu.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsRowDropdownMenu.tsx index a4f6c7cb18..9811a64781 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsRowDropdownMenu.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsRowDropdownMenu.tsx @@ -2,14 +2,7 @@ import { useApolloClient, useMutation } from '@apollo/client/react'; import { type ConnectedAccount } from '@/accounts/types/ConnectedAccount'; import { CalendarChannelSyncStage } from '@/accounts/types/CalendarChannel'; import { MessageChannelSyncStage } from '@/accounts/types/MessageChannel'; -import { - CoreObjectNameSingular, - ConnectedAccountProvider, - FeatureFlagKey, - SettingsPath, -} from 'twenty-shared/types'; - -import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord'; +import { ConnectedAccountProvider, SettingsPath } from 'twenty-shared/types'; import { useTriggerProviderReconnect } from '@/settings/accounts/hooks/useTriggerProviderReconnect'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; @@ -31,7 +24,6 @@ import { import { LightIconButton } from 'twenty-ui/input'; import { MenuItem } from 'twenty-ui/navigation'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; -import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; import { DELETE_CONNECTED_ACCOUNT } from '../graphql/mutations/deleteConnectedAccount'; type SettingsAccountsRowDropdownMenuProps = { @@ -51,14 +43,7 @@ export const SettingsAccountsRowDropdownMenu = ({ const navigate = useNavigateSettings(); const { closeDropdown } = useCloseDropdown(); - const featureFlagsMap = useFeatureFlagsMap(); - const isMigrated = - featureFlagsMap[FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED] ?? false; - const apolloClient = useApolloClient(); - const { destroyOneRecord } = useDestroyOneRecord({ - objectNameSingular: CoreObjectNameSingular.ConnectedAccount, - }); const [deleteConnectedAccountMutation] = useMutation( DELETE_CONNECTED_ACCOUNT, ); @@ -75,14 +60,10 @@ export const SettingsAccountsRowDropdownMenu = ({ ); const deleteAccount = async () => { - if (isMigrated) { - await deleteConnectedAccountMutation({ - variables: { id: account.id }, - }); - await apolloClient.refetchQueries({ include: 'active' }); - } else { - await destroyOneRecord(account.id); - } + await deleteConnectedAccountMutation({ + variables: { id: account.id }, + }); + await apolloClient.refetchQueries({ include: 'active' }); }; return ( diff --git a/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getConnectedImapSmtpCaldavAccount.ts b/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getConnectedImapSmtpCaldavAccount.ts index 57434185ae..afce73519a 100644 --- a/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getConnectedImapSmtpCaldavAccount.ts +++ b/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getConnectedImapSmtpCaldavAccount.ts @@ -6,7 +6,7 @@ export const GET_CONNECTED_IMAP_SMTP_CALDAV_ACCOUNT = gql` id handle provider - accountOwnerId + userWorkspaceId connectionParameters { IMAP { host diff --git a/packages/twenty-front/src/modules/settings/accounts/hooks/useMyCalendarChannels.ts b/packages/twenty-front/src/modules/settings/accounts/hooks/useMyCalendarChannels.ts index 52608fd56c..707ea57719 100644 --- a/packages/twenty-front/src/modules/settings/accounts/hooks/useMyCalendarChannels.ts +++ b/packages/twenty-front/src/modules/settings/accounts/hooks/useMyCalendarChannels.ts @@ -1,16 +1,7 @@ -import { type ConnectedAccount } from '@/accounts/types/ConnectedAccount'; -import { - type CalendarChannel, - CalendarChannelSyncStage, -} from '@/accounts/types/CalendarChannel'; -import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { type CalendarChannel } from '@/accounts/types/CalendarChannel'; import { GET_MY_CALENDAR_CHANNELS } from '@/settings/accounts/graphql/queries/getMyCalendarChannels'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; import { useApolloClient, useQuery } from '@apollo/client/react'; import { useMemo } from 'react'; -import { CoreObjectNameSingular, FeatureFlagKey } from 'twenty-shared/types'; type MetadataCalendarChannel = { id: string; @@ -28,51 +19,15 @@ type MetadataCalendarChannel = { }; export const useMyCalendarChannels = () => { - const featureFlagsMap = useFeatureFlagsMap(); - const isMigrated = - featureFlagsMap[FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED] ?? false; - - const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState); const apolloClient = useApolloClient(); - const { records: workspaceAccounts } = useFindManyRecords({ - objectNameSingular: CoreObjectNameSingular.ConnectedAccount, - filter: { - accountOwnerId: { - eq: currentWorkspaceMember?.id, - }, - }, - skip: isMigrated, - }); - - const { records: workspaceChannels, loading: workspaceLoading } = - useFindManyRecords< - CalendarChannel & { connectedAccount: ConnectedAccount } - >({ - objectNameSingular: CoreObjectNameSingular.CalendarChannel, - filter: { - connectedAccountId: { - in: workspaceAccounts.map((account) => account.id), - }, - syncStage: { - neq: CalendarChannelSyncStage.PENDING_CONFIGURATION, - }, - }, - skip: isMigrated || !workspaceAccounts.length, - }); - const { data: metadataData, loading: metadataLoading } = useQuery<{ myCalendarChannels: MetadataCalendarChannel[]; }>(GET_MY_CALENDAR_CHANNELS, { client: apolloClient, - skip: !isMigrated, }); const channels = useMemo(() => { - if (!isMigrated) { - return workspaceChannels; - } - if (!metadataData?.myCalendarChannels) { return []; } @@ -102,10 +57,10 @@ export const useMyCalendarChannels = () => { __typename: 'CalendarChannel', }) as CalendarChannel, ); - }, [isMigrated, workspaceChannels, metadataData]); + }, [metadataData]); return { channels, - loading: isMigrated ? metadataLoading : workspaceLoading, + loading: metadataLoading, }; }; diff --git a/packages/twenty-front/src/modules/settings/accounts/hooks/useMyConnectedAccounts.ts b/packages/twenty-front/src/modules/settings/accounts/hooks/useMyConnectedAccounts.ts index 1b0f1a9b66..7fd0763270 100644 --- a/packages/twenty-front/src/modules/settings/accounts/hooks/useMyConnectedAccounts.ts +++ b/packages/twenty-front/src/modules/settings/accounts/hooks/useMyConnectedAccounts.ts @@ -1,15 +1,9 @@ import { type ConnectedAccount } from '@/accounts/types/ConnectedAccount'; -import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; -import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject'; import { GET_MY_CONNECTED_ACCOUNTS } from '@/settings/accounts/graphql/queries/getMyConnectedAccounts'; import { useMyCalendarChannels } from '@/settings/accounts/hooks/useMyCalendarChannels'; import { useMyMessageChannels } from '@/settings/accounts/hooks/useMyMessageChannels'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; import { useApolloClient, useQuery } from '@apollo/client/react'; import { useMemo } from 'react'; -import { CoreObjectNameSingular, FeatureFlagKey } from 'twenty-shared/types'; type MetadataConnectedAccount = { id: string; @@ -25,37 +19,12 @@ type MetadataConnectedAccount = { }; export const useMyConnectedAccounts = () => { - const featureFlagsMap = useFeatureFlagsMap(); - const isMigrated = - featureFlagsMap[FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED] ?? false; - - const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState); - const apolloClient = useApolloClient(); - const { recordGqlFields } = useGenerateDepthRecordGqlFieldsFromObject({ - objectNameSingular: CoreObjectNameSingular.ConnectedAccount, - depth: 1, - shouldOnlyLoadRelationIdentifiers: false, - }); - - const { records: workspaceAccounts, loading: workspaceLoading } = - useFindManyRecords({ - objectNameSingular: CoreObjectNameSingular.ConnectedAccount, - filter: { - accountOwnerId: { - eq: currentWorkspaceMember?.id, - }, - }, - recordGqlFields, - skip: isMigrated, - }); - const { data: metadataData, loading: metadataLoading } = useQuery<{ myConnectedAccounts: MetadataConnectedAccount[]; }>(GET_MY_CONNECTED_ACCOUNTS, { client: apolloClient, - skip: !isMigrated, }); const { channels: messageChannels, loading: messageChannelsLoading } = @@ -64,10 +33,6 @@ export const useMyConnectedAccounts = () => { useMyCalendarChannels(); const accounts = useMemo(() => { - if (!isMigrated) { - return workspaceAccounts; - } - if (!metadataData?.myConnectedAccounts) { return []; } @@ -105,18 +70,11 @@ export const useMyConnectedAccounts = () => { account.messageChannels.length > 0 || account.calendarChannels.length > 0, ); - }, [ - isMigrated, - workspaceAccounts, - metadataData, - messageChannels, - calendarChannels, - ]); + }, [metadataData, messageChannels, calendarChannels]); return { accounts, - loading: isMigrated - ? metadataLoading || messageChannelsLoading || calendarChannelsLoading - : workspaceLoading, + loading: + metadataLoading || messageChannelsLoading || calendarChannelsLoading, }; }; diff --git a/packages/twenty-front/src/modules/settings/accounts/hooks/useMyMessageChannels.ts b/packages/twenty-front/src/modules/settings/accounts/hooks/useMyMessageChannels.ts index 164955165f..5250836797 100644 --- a/packages/twenty-front/src/modules/settings/accounts/hooks/useMyMessageChannels.ts +++ b/packages/twenty-front/src/modules/settings/accounts/hooks/useMyMessageChannels.ts @@ -1,17 +1,7 @@ -import { type ConnectedAccount } from '@/accounts/types/ConnectedAccount'; -import { - type MessageChannel, - MessageChannelSyncStage, -} from '@/accounts/types/MessageChannel'; -import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; -import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; -import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject'; +import { type MessageChannel } from '@/accounts/types/MessageChannel'; import { GET_MY_MESSAGE_CHANNELS } from '@/settings/accounts/graphql/queries/getMyMessageChannels'; -import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; import { useApolloClient, useQuery } from '@apollo/client/react'; import { useMemo } from 'react'; -import { CoreObjectNameSingular, FeatureFlagKey } from 'twenty-shared/types'; type MetadataMessageChannel = { id: string; @@ -33,58 +23,15 @@ type MetadataMessageChannel = { }; export const useMyMessageChannels = () => { - const featureFlagsMap = useFeatureFlagsMap(); - const isMigrated = - featureFlagsMap[FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED] ?? false; - - const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState); const apolloClient = useApolloClient(); - const { records: workspaceAccounts } = useFindManyRecords({ - objectNameSingular: CoreObjectNameSingular.ConnectedAccount, - filter: { - accountOwnerId: { - eq: currentWorkspaceMember?.id, - }, - }, - skip: isMigrated, - }); - - const { recordGqlFields } = useGenerateDepthRecordGqlFieldsFromObject({ - objectNameSingular: CoreObjectNameSingular.MessageChannel, - depth: 1, - }); - - const { records: workspaceChannels, loading: workspaceLoading } = - useFindManyRecords( - { - objectNameSingular: CoreObjectNameSingular.MessageChannel, - filter: { - connectedAccountId: { - in: workspaceAccounts.map((account) => account.id), - }, - isSyncEnabled: { eq: true }, - syncStage: { - neq: MessageChannelSyncStage.PENDING_CONFIGURATION, - }, - }, - recordGqlFields, - skip: isMigrated || !workspaceAccounts.length, - }, - ); - const { data: metadataData, loading: metadataLoading } = useQuery<{ myMessageChannels: MetadataMessageChannel[]; }>(GET_MY_MESSAGE_CHANNELS, { client: apolloClient, - skip: !isMigrated, }); const channels = useMemo(() => { - if (!isMigrated) { - return workspaceChannels; - } - if (!metadataData?.myMessageChannels) { return []; } @@ -118,10 +65,10 @@ export const useMyMessageChannels = () => { __typename: 'MessageChannel', }) as MessageChannel, ); - }, [isMigrated, workspaceChannels, metadataData]); + }, [metadataData]); return { channels, - loading: isMigrated ? metadataLoading : workspaceLoading, + loading: metadataLoading, }; }; diff --git a/packages/twenty-front/src/modules/settings/accounts/hooks/useMyMessageFolders.ts b/packages/twenty-front/src/modules/settings/accounts/hooks/useMyMessageFolders.ts index 120ba42815..e179c7769f 100644 --- a/packages/twenty-front/src/modules/settings/accounts/hooks/useMyMessageFolders.ts +++ b/packages/twenty-front/src/modules/settings/accounts/hooks/useMyMessageFolders.ts @@ -1,12 +1,7 @@ -import { type MessageChannel } from '@/accounts/types/MessageChannel'; import { type MessageFolder } from '@/accounts/types/MessageFolder'; -import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord'; -import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject'; import { GET_MY_MESSAGE_FOLDERS } from '@/settings/accounts/graphql/queries/getMyMessageFolders'; -import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; import { useApolloClient, useQuery } from '@apollo/client/react'; import { useMemo } from 'react'; -import { CoreObjectNameSingular, FeatureFlagKey } from 'twenty-shared/types'; type MetadataMessageFolder = { id: string; @@ -21,39 +16,16 @@ type MetadataMessageFolder = { }; export const useMyMessageFolders = (messageChannelId?: string) => { - const featureFlagsMap = useFeatureFlagsMap(); - const isMigrated = - featureFlagsMap[FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED] ?? false; - const apolloClient = useApolloClient(); - const { recordGqlFields } = useGenerateDepthRecordGqlFieldsFromObject({ - objectNameSingular: CoreObjectNameSingular.MessageChannel, - depth: 1, - shouldOnlyLoadRelationIdentifiers: false, - }); - - const { record: messageChannel, loading: workspaceLoading } = - useFindOneRecord({ - objectNameSingular: CoreObjectNameSingular.MessageChannel, - objectRecordId: messageChannelId, - recordGqlFields, - skip: isMigrated || !messageChannelId, - }); - const { data: metadataData, loading: metadataLoading } = useQuery<{ myMessageFolders: MetadataMessageFolder[]; }>(GET_MY_MESSAGE_FOLDERS, { client: apolloClient, variables: messageChannelId ? { messageChannelId } : undefined, - skip: !isMigrated, }); const messageFolders = useMemo(() => { - if (!isMigrated) { - return messageChannel?.messageFolders ?? []; - } - if (!metadataData?.myMessageFolders) { return []; } @@ -71,10 +43,10 @@ export const useMyMessageFolders = (messageChannelId?: string) => { __typename: 'MessageFolder' as const, }), ); - }, [isMigrated, messageChannel, metadataData]); + }, [metadataData]); return { messageFolders, - loading: isMigrated ? metadataLoading : workspaceLoading, + loading: metadataLoading, }; }; diff --git a/packages/twenty-front/src/modules/settings/accounts/hooks/useUpdateMessageFoldersSyncStatus.ts b/packages/twenty-front/src/modules/settings/accounts/hooks/useUpdateMessageFoldersSyncStatus.ts index bf831651c0..ab4652c658 100644 --- a/packages/twenty-front/src/modules/settings/accounts/hooks/useUpdateMessageFoldersSyncStatus.ts +++ b/packages/twenty-front/src/modules/settings/accounts/hooks/useUpdateMessageFoldersSyncStatus.ts @@ -1,10 +1,7 @@ import { useCallback } from 'react'; -import { useUpdateManyRecords } from '@/object-record/hooks/useUpdateManyRecords'; import { UPDATE_MESSAGE_FOLDERS } from '@/settings/accounts/graphql/mutations/updateMessageFolders'; -import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; import { useApolloClient } from '@apollo/client/react'; -import { CoreObjectNameSingular, FeatureFlagKey } from 'twenty-shared/types'; type UpdateMessageFoldersSyncStatusArgs = { messageFolderIds: string[]; @@ -12,56 +9,35 @@ type UpdateMessageFoldersSyncStatusArgs = { }; export const useUpdateMessageFoldersSyncStatus = () => { - const featureFlagsMap = useFeatureFlagsMap(); - const isMigrated = - featureFlagsMap[FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED] ?? false; - const apolloClient = useApolloClient(); - const { updateManyRecords } = useUpdateManyRecords({ - objectNameSingular: CoreObjectNameSingular.MessageFolder, - recordGqlFields: { - id: true, - isSynced: true, - }, - }); - const updateMessageFoldersSyncStatus = useCallback( async ({ messageFolderIds, isSynced, }: UpdateMessageFoldersSyncStatusArgs) => { - if (isMigrated) { - if (messageFolderIds.length === 0) { - return; - } - - await apolloClient.mutate({ - mutation: UPDATE_MESSAGE_FOLDERS, - variables: { - input: { - ids: messageFolderIds, - update: { isSynced }, - }, - }, - optimisticResponse: { - updateMessageFolders: messageFolderIds.map((id) => ({ - __typename: 'MessageFolder', - id, - isSynced, - })), - }, - }); - + if (messageFolderIds.length === 0) { return; } - await updateManyRecords({ - recordIdsToUpdate: messageFolderIds, - updateOneRecordInput: { isSynced }, + await apolloClient.mutate({ + mutation: UPDATE_MESSAGE_FOLDERS, + variables: { + input: { + ids: messageFolderIds, + update: { isSynced }, + }, + }, + optimisticResponse: { + updateMessageFolders: messageFolderIds.map((id) => ({ + __typename: 'MessageFolder', + id, + isSynced, + })), + }, }); }, - [isMigrated, apolloClient, updateManyRecords], + [apolloClient], ); return { updateMessageFoldersSyncStatus }; diff --git a/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsConfiguration.tsx b/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsConfiguration.tsx index 6b1b540971..bbca68df73 100644 --- a/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsConfiguration.tsx +++ b/packages/twenty-front/src/pages/settings/accounts/SettingsAccountsConfiguration.tsx @@ -5,17 +5,10 @@ import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomStat import { type CalendarChannel } from '@/accounts/types/CalendarChannel'; import { type MessageChannel } from '@/accounts/types/MessageChannel'; -import { - CoreObjectNameSingular, - FeatureFlagKey, - SettingsPath, -} from 'twenty-shared/types'; -import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject'; -import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { SettingsPath } from 'twenty-shared/types'; import { GET_MY_CALENDAR_CHANNELS } from '@/settings/accounts/graphql/queries/getMyCalendarChannels'; import { GET_MY_MESSAGE_CHANNELS } from '@/settings/accounts/graphql/queries/getMyMessageChannels'; import { settingsAccountsSelectedMessageChannelState } from '@/settings/accounts/states/settingsAccountsSelectedMessageChannelState'; -import { useFeatureFlagsMap } from '@/workspace/hooks/useFeatureFlagsMap'; import { useMutation, useQuery } from '@apollo/client/react'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { getSettingsPath, isDefined } from 'twenty-shared/utils'; @@ -48,65 +41,24 @@ export const SettingsAccountsConfiguration = () => { SettingsAccountsConfigurationStep.Email, ); - const featureFlagsMap = useFeatureFlagsMap(); - const isMigrated = - featureFlagsMap[FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED] ?? false; - - const { recordGqlFields } = useGenerateDepthRecordGqlFieldsFromObject({ - objectNameSingular: CoreObjectNameSingular.MessageChannel, - depth: 1, - shouldOnlyLoadRelationIdentifiers: false, - }); - - const { records: workspaceMessageChannels } = - useFindManyRecords({ - objectNameSingular: CoreObjectNameSingular.MessageChannel, - filter: { - connectedAccountId: { - eq: connectedAccountId, - }, - }, - recordGqlFields, - onCompleted: (data) => { - if (isDefined(data[0])) { - setSettingsAccountsSelectedMessageChannel(data[0]); - } - }, - skip: !connectedAccountId || isMigrated, - }); - - const { records: workspaceCalendarChannels } = - useFindManyRecords({ - objectNameSingular: CoreObjectNameSingular.CalendarChannel, - filter: { - connectedAccountId: { - eq: connectedAccountId, - }, - }, - skip: !connectedAccountId || isMigrated, - }); - const { data: metadataMessageChannelData } = useQuery<{ myMessageChannels: MessageChannel[]; }>(GET_MY_MESSAGE_CHANNELS, { variables: { connectedAccountId }, - skip: !isMigrated || !connectedAccountId, + skip: !connectedAccountId, }); const { data: metadataCalendarChannelData } = useQuery<{ myCalendarChannels: CalendarChannel[]; }>(GET_MY_CALENDAR_CHANNELS, { variables: { connectedAccountId }, - skip: !isMigrated || !connectedAccountId, + skip: !connectedAccountId, }); - const messageChannels = isMigrated - ? (metadataMessageChannelData?.myMessageChannels ?? []) - : workspaceMessageChannels; + const messageChannels = metadataMessageChannelData?.myMessageChannels ?? []; - const calendarChannels = isMigrated - ? (metadataCalendarChannelData?.myCalendarChannels ?? []) - : workspaceCalendarChannels; + const calendarChannels = + metadataCalendarChannelData?.myCalendarChannels ?? []; const messageChannel = messageChannels[0]; const calendarChannel = calendarChannels[0]; @@ -143,11 +95,9 @@ export const SettingsAccountsConfiguration = () => { if (showEmailStep) { return ( <> - {isMigrated && ( - - )} + { const isDryRun = options.dryRun ?? false; - const isAlreadyMigrated = await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - workspaceId, - ); - - if (isAlreadyMigrated) { - this.logger.log( - `IS_CONNECTED_ACCOUNT_MIGRATED already enabled for workspace ${workspaceId}, skipping`, - ); - - return; - } - const connectedAccountWorkspaceRepository = await this.twentyORMGlobalManager.getRepository( workspaceId, @@ -71,13 +55,13 @@ export class MigrateMessagingInfrastructureToMetadataCommand extends ActiveOrSus ); const messageChannelWorkspaceRepository = - await this.twentyORMGlobalManager.getRepository( + await this.twentyORMGlobalManager.getRepository( workspaceId, 'messageChannel', ); const calendarChannelWorkspaceRepository = - await this.twentyORMGlobalManager.getRepository( + await this.twentyORMGlobalManager.getRepository( workspaceId, 'calendarChannel', ); @@ -203,9 +187,7 @@ export class MigrateMessagingInfrastructureToMetadataCommand extends ActiveOrSus }); if (coreConnectedAccounts.length > 0) { - await this.connectedAccountRepository.save( - coreConnectedAccounts as unknown as ConnectedAccountEntity[], - ); + await this.connectedAccountRepository.save(coreConnectedAccounts); this.logger.log( `Migrated ${coreConnectedAccounts.length} connected accounts for workspace ${workspaceId}`, ); @@ -352,15 +334,6 @@ export class MigrateMessagingInfrastructureToMetadataCommand extends ActiveOrSus `Migrated ${coreMessageFolders.length} message folders for workspace ${workspaceId}`, ); } - - await this.featureFlagService.enableFeatureFlags( - [FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED], - workspaceId, - ); - - this.logger.log( - `Enabled IS_CONNECTED_ACCOUNT_MIGRATED for workspace ${workspaceId}`, - ); } private async buildWorkspaceMemberIdToUserWorkspaceIdMap( diff --git a/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts b/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts index bd45d3f2e5..eb56b9a901 100644 --- a/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/metadata.module-factory.ts @@ -25,7 +25,7 @@ export const metadataModuleFactory = async ( cacheStorageService: CacheStorageService, metricsService: MetricsService, i18nService: I18nService, - featureFlagService: FeatureFlagService, + _featureFlagService: FeatureFlagService, ): Promise => { const config: YogaDriverConfig = { autoSchemaFile: true, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module.ts index da1c91c7eb..2181719659 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module.ts @@ -7,7 +7,6 @@ import { WorkspaceQueryHookExplorer } from 'src/engine/api/graphql/workspace-que import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service'; import { BlocklistQueryHookModule } from 'src/modules/blocklist/query-hooks/blocklist-query-hook.module'; import { CalendarQueryHookModule } from 'src/modules/calendar/common/query-hooks/calendar-query-hook.module'; -import { ConnectedAccountQueryHookModule } from 'src/modules/connected-account/query-hooks/connected-account-query-hook.module'; import { DashboardQueryHookModule } from 'src/modules/dashboard/query-hooks/dashboard-query-hook.module'; import { MessagingQueryHookModule } from 'src/modules/messaging/common/query-hooks/messaging-query-hook.module'; import { NoteQueryHookModule } from 'src/modules/note/query-hooks/note-query-hook.module'; @@ -18,7 +17,6 @@ import { WorkspaceMemberQueryHookModule } from 'src/modules/workspace-member/que imports: [ MessagingQueryHookModule, CalendarQueryHookModule, - ConnectedAccountQueryHookModule, DashboardQueryHookModule, BlocklistQueryHookModule, WorkspaceMemberQueryHookModule, diff --git a/packages/twenty-server/src/engine/core-modules/admin-panel/__tests__/connected-account.health.spec.ts b/packages/twenty-server/src/engine/core-modules/admin-panel/__tests__/connected-account.health.spec.ts index 69f94c104c..16110afb48 100644 --- a/packages/twenty-server/src/engine/core-modules/admin-panel/__tests__/connected-account.health.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/admin-panel/__tests__/connected-account.health.spec.ts @@ -1,13 +1,15 @@ import { HealthIndicatorService } from '@nestjs/terminus'; import { Test, type TestingModule } from '@nestjs/testing'; +import { + CalendarChannelSyncStatus, + MessageChannelSyncStatus, +} from 'twenty-shared/types'; import { HEALTH_ERROR_MESSAGES } from 'src/engine/core-modules/admin-panel/constants/health-error-messages.constants'; import { HEALTH_INDICATORS_TIMEOUT } from 'src/engine/core-modules/admin-panel/constants/health-indicators-timeout.conts'; import { METRICS_FAILURE_RATE_THRESHOLD } from 'src/engine/core-modules/admin-panel/constants/metrics-failure-rate-threshold.const'; import { ConnectedAccountHealth } from 'src/engine/core-modules/admin-panel/indicators/connected-account.health'; import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; -import { CalendarChannelSyncStatus } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; -import { MessageChannelSyncStatus } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; describe('ConnectedAccountHealth', () => { let service: ConnectedAccountHealth; diff --git a/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts b/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts index bc3a0091f0..bb1d743f9a 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts @@ -58,10 +58,9 @@ import { UserModule } from 'src/engine/core-modules/user/user.module'; import { WorkspaceInvitationModule } from 'src/engine/core-modules/workspace-invitation/workspace-invitation.module'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { CoreEntityCacheModule } from 'src/engine/core-entity-cache/core-entity-cache.module'; -import { CalendarChannelDataAccessModule } from 'src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; -import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module'; -import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; @@ -94,15 +93,14 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy'; TwoFactorAuthenticationMethodEntity, ObjectMetadataEntity, ConnectedAccountEntity, + MessageChannelEntity, + CalendarChannelEntity, ]), UserWorkspaceModule, OnboardingModule, ConnectedAccountModule, MessagingCommonModule, MessagingFolderSyncManagerModule, - CalendarChannelDataAccessModule, - ConnectedAccountDataAccessModule, - MessageChannelDataAccessModule, WorkspaceSSOModule, FeatureFlagModule, WorkspaceInvitationModule, diff --git a/packages/twenty-server/src/engine/core-modules/auth/controllers/google-apis-auth.controller.ts b/packages/twenty-server/src/engine/core-modules/auth/controllers/google-apis-auth.controller.ts index 3beb53190b..a5b1a7a6df 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/controllers/google-apis-auth.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/controllers/google-apis-auth.controller.ts @@ -101,8 +101,9 @@ export class GoogleAPIsAuthController { const connectedAccountId = await this.googleAPIsService.refreshGoogleRefreshToken({ handle, - workspaceMemberId: workspaceMemberId, - workspaceId: workspaceId, + userId, + workspaceMemberId, + workspaceId, accessToken, refreshToken, calendarVisibility, diff --git a/packages/twenty-server/src/engine/core-modules/auth/controllers/microsoft-apis-auth.controller.ts b/packages/twenty-server/src/engine/core-modules/auth/controllers/microsoft-apis-auth.controller.ts index 002d99865e..55d024d33e 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/controllers/microsoft-apis-auth.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/controllers/microsoft-apis-auth.controller.ts @@ -108,7 +108,8 @@ export class MicrosoftAPIsAuthController { const connectedAccountId = await this.microsoftAPIsService.refreshMicrosoftRefreshToken({ handle, - workspaceMemberId: workspaceMemberId, + userId, + workspaceMemberId, workspaceId: workspaceId, accessToken, refreshToken, diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts index 7d0dccb727..faf32ee250 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/auth.service.ts @@ -9,11 +9,7 @@ import { addMilliseconds } from 'date-fns'; import ms from 'ms'; import { PasswordUpdateNotifyEmail } from 'twenty-emails'; import { PermissionFlagType } from 'twenty-shared/constants'; -import { - AppPath, - ConnectedAccountProvider, - FeatureFlagKey, -} from 'twenty-shared/types'; +import { AppPath, ConnectedAccountProvider } from 'twenty-shared/types'; import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils'; import { IsNull, Repository } from 'typeorm'; @@ -1081,19 +1077,6 @@ export class AuthService { oidcTokenClaims?: Record; connectedAccountProvider?: ConnectedAccountProvider; }): Promise { - const isConnectedAccountMigrated = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - input.workspaceId, - ); - - // const willBeEnabledByDefault = DEFAULT_FEATURE_FLAGS.includes(FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED); - const willBeEnabledByDefault = false; - - if (!isConnectedAccountMigrated && !willBeEnabledByDefault) { - return; - } - const provider = input.connectedAccountProvider ?? this.mapAuthProviderToConnectedAccountProvider(input.authProvider); diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/create-calendar-channel.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/create-calendar-channel.service.ts index 3341ac6c9a..c10a44c842 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/create-calendar-channel.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/create-calendar-channel.service.ts @@ -1,31 +1,30 @@ import { Injectable } from '@nestjs/common'; import { v4 } from 'uuid'; +import { EntityManager } from 'typeorm'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; -import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; -import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { CalendarChannelSyncStage, CalendarChannelSyncStatus, CalendarChannelVisibility, -} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; +} from 'twenty-shared/types'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; +import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; export type CreateCalendarChannelInput = { workspaceId: string; connectedAccountId: string; handle: string; calendarVisibility?: CalendarChannelVisibility; - manager: WorkspaceEntityManager; skipMessageChannelConfiguration?: boolean; + transactionManager: EntityManager; }; @Injectable() export class CreateCalendarChannelService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, ) {} async createCalendarChannel( @@ -36,8 +35,8 @@ export class CreateCalendarChannelService { connectedAccountId, handle, calendarVisibility, - manager, skipMessageChannelConfiguration, + transactionManager, } = input; const authContext = buildSystemAuthContext(workspaceId); @@ -46,23 +45,20 @@ export class CreateCalendarChannelService { async () => { const newCalendarChannelId = v4(); - await this.calendarChannelDataAccessService.save( + await transactionManager.getRepository(CalendarChannelEntity).save({ + id: newCalendarChannelId, + connectedAccountId, + handle, + visibility: + calendarVisibility || CalendarChannelVisibility.SHARE_EVERYTHING, + syncStatus: skipMessageChannelConfiguration + ? CalendarChannelSyncStatus.ONGOING + : CalendarChannelSyncStatus.NOT_SYNCED, + syncStage: skipMessageChannelConfiguration + ? CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING + : CalendarChannelSyncStage.PENDING_CONFIGURATION, workspaceId, - { - id: newCalendarChannelId, - connectedAccountId, - handle, - visibility: - calendarVisibility || CalendarChannelVisibility.SHARE_EVERYTHING, - syncStatus: skipMessageChannelConfiguration - ? CalendarChannelSyncStatus.ONGOING - : CalendarChannelSyncStatus.NOT_SYNCED, - syncStage: skipMessageChannelConfiguration - ? CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING - : CalendarChannelSyncStage.PENDING_CONFIGURATION, - }, - manager, - ); + } as CalendarChannelEntity); return newCalendarChannelId; }, diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/create-connected-account.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/create-connected-account.service.ts index 58eda495c5..3a262b1fd8 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/create-connected-account.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/create-connected-account.service.ts @@ -1,11 +1,14 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { type ConnectedAccountProvider } from 'twenty-shared/types'; +import { EntityManager, Repository } from 'typeorm'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; -import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; +import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; export type CreateConnectedAccountInput = { workspaceId: string; @@ -16,14 +19,15 @@ export type CreateConnectedAccountInput = { refreshToken: string; accountOwnerId: string; scopes: string[]; - manager: WorkspaceEntityManager; + transactionManager: EntityManager; }; @Injectable() export class CreateConnectedAccountService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, ) {} async createConnectedAccount( @@ -38,25 +42,51 @@ export class CreateConnectedAccountService { refreshToken, accountOwnerId, scopes, - manager, } = input; const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.connectedAccountDataAccessService.save( - workspaceId, - { + const workspaceMemberRepo = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workspaceMember', + ); + + const member = await workspaceMemberRepo.findOne({ + where: { id: accountOwnerId }, + }); + + if (!member) { + throw new Error( + `Workspace member not found for accountOwnerId ${accountOwnerId}`, + ); + } + + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { userId: member.userId, workspaceId }, + }); + + if (!userWorkspace) { + throw new Error( + `User workspace not found for user ${member.userId} in workspace ${workspaceId}`, + ); + } + + const userWorkspaceId = userWorkspace.id; + + await input.transactionManager + .getRepository(ConnectedAccountEntity) + .save({ id: connectedAccountId, handle, provider, accessToken, refreshToken, - accountOwnerId, + userWorkspaceId, scopes, - }, - manager, - ); + workspaceId, + } as ConnectedAccountEntity); }, authContext); } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/create-message-channel.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/create-message-channel.service.ts index 437a597273..7eae3b41f4 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/create-message-channel.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/create-message-channel.service.ts @@ -1,37 +1,32 @@ import { Injectable } from '@nestjs/common'; -import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { EntityManager } from 'typeorm'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; -import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; -import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { MessageChannelPendingGroupEmailsAction, MessageChannelSyncStage, MessageChannelSyncStatus, MessageChannelType, MessageChannelVisibility, - type MessageChannelWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service'; +} from 'twenty-shared/types'; +import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; +import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; export type CreateMessageChannelInput = { workspaceId: string; connectedAccountId: string; handle: string; messageVisibility?: MessageChannelVisibility; - manager: WorkspaceEntityManager; skipMessageChannelConfiguration?: boolean; + transactionManager: EntityManager; }; @Injectable() export class CreateMessageChannelService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, - private readonly syncMessageFoldersService: SyncMessageFoldersService, ) {} async createMessageChannel( @@ -42,50 +37,32 @@ export class CreateMessageChannelService { connectedAccountId, handle, messageVisibility, - manager, skipMessageChannelConfiguration, + transactionManager, } = input; const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( async () => { + const messageChannelRepo = + transactionManager.getRepository(MessageChannelEntity); const newMessageChannelId = v4(); - await this.messageChannelDataAccessService.save( - workspaceId, - { - id: newMessageChannelId, - connectedAccountId, - type: MessageChannelType.EMAIL, - handle, - visibility: - messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING, - syncStatus: skipMessageChannelConfiguration - ? MessageChannelSyncStatus.ONGOING - : MessageChannelSyncStatus.NOT_SYNCED, - syncStage: skipMessageChannelConfiguration - ? MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING - : MessageChannelSyncStage.PENDING_CONFIGURATION, - pendingGroupEmailsAction: - MessageChannelPendingGroupEmailsAction.NONE, - }, - manager, - ); - - const createdMessageChannel = - await this.messageChannelDataAccessService.findOne(workspaceId, { - where: { id: newMessageChannelId }, - relations: ['connectedAccount', 'messageFolders'], - }); - - if (!isDefined(createdMessageChannel)) { - throw new Error('Message channel not found'); - } - - await this.syncMessageFoldersService.syncMessageFolders({ - messageChannel: - createdMessageChannel as unknown as MessageChannelWorkspaceEntity, + await messageChannelRepo.save({ + id: newMessageChannelId, + connectedAccountId, + type: MessageChannelType.EMAIL, + handle, + visibility: + messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING, + syncStatus: skipMessageChannelConfiguration + ? MessageChannelSyncStatus.ONGOING + : MessageChannelSyncStatus.NOT_SYNCED, + syncStage: skipMessageChannelConfiguration + ? MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING + : MessageChannelSyncStage.PENDING_CONFIGURATION, + pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction.NONE, workspaceId, }); diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.spec.ts index cdec314fd6..c5d2c58b3c 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.spec.ts @@ -1,7 +1,12 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { ConnectedAccountProvider } from 'twenty-shared/types'; +import { + CalendarChannelSyncStage, + CalendarChannelVisibility, + ConnectedAccountProvider, + MessageChannelVisibility, +} 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'; @@ -11,23 +16,19 @@ import { GoogleApisServiceAvailabilityService } from 'src/engine/core-modules/au import { GoogleAPIsService } from 'src/engine/core-modules/auth/services/google-apis.service'; import { UpdateConnectedAccountOnReconnectService } from 'src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.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 { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; -import { - CalendarChannelSyncStage, - CalendarChannelVisibility, -} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; -import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service'; jest.mock('uuid', () => ({ v4: jest.fn(() => 'mocked-uuid'), @@ -39,24 +40,36 @@ describe('GoogleAPIsService', () => { let messagingChannelSyncStatusService: MessageChannelSyncStatusService; let createMessageChannelService: CreateMessageChannelService; - const mockConnectedAccountDataAccessService = { + const mockConnectedAccountRepository = { findOne: jest.fn(), }; - const mockMessageChannelDataAccessService = { + const mockTransactionManager = { + getRepository: jest.fn().mockReturnValue({ save: jest.fn() }), + }; + + const mockMessageChannelRepository = { + find: jest.fn(), + findOne: jest.fn().mockResolvedValue(null), + manager: { + transaction: jest.fn((callback) => callback(mockTransactionManager)), + }, + }; + + const mockCalendarChannelRepository = { find: jest.fn(), }; - const mockCalendarChannelDataAccessService = { - find: jest.fn(), + const mockUserWorkspaceRepository = { + findOne: jest.fn().mockResolvedValue({ id: 'user-workspace-id' }), }; const mockWorkspaceMemberRepository = { findOneOrFail: jest.fn(), - }; - - const mockWorkspaceDataSource = { - transaction: jest.fn((callback) => callback({})), + findOne: jest.fn().mockResolvedValue({ + id: 'workspace-member-id', + userId: 'user-id', + }), }; const mockTwentyConfigService = { @@ -86,9 +99,6 @@ describe('GoogleAPIsService', () => { return {}; }), - getGlobalWorkspaceDataSource: jest - .fn() - .mockResolvedValue(mockWorkspaceDataSource), executeInWorkspaceContext: jest .fn() .mockImplementation((fn: () => any, _authContext?: any) => fn()), @@ -180,16 +190,26 @@ describe('GoogleAPIsService', () => { }, }, { - provide: ConnectedAccountDataAccessService, - useValue: mockConnectedAccountDataAccessService, + provide: SyncMessageFoldersService, + useValue: { + syncMessageFolders: jest.fn().mockResolvedValue([]), + }, }, { - provide: MessageChannelDataAccessService, - useValue: mockMessageChannelDataAccessService, + provide: getRepositoryToken(ConnectedAccountEntity), + useValue: mockConnectedAccountRepository, }, { - provide: CalendarChannelDataAccessService, - useValue: mockCalendarChannelDataAccessService, + provide: getRepositoryToken(UserWorkspaceEntity), + useValue: mockUserWorkspaceRepository, + }, + { + provide: getRepositoryToken(MessageChannelEntity), + useValue: mockMessageChannelRepository, + }, + { + provide: getRepositoryToken(CalendarChannelEntity), + useValue: mockCalendarChannelRepository, }, ], }).compile(); @@ -220,15 +240,15 @@ describe('GoogleAPIsService', () => { const existingConnectedAccount = { id: 'existing-account-id', handle: 'test@example.com', - accountOwnerId: 'workspace-member-id', + userWorkspaceId: 'user-workspace-id', provider: ConnectedAccountProvider.GOOGLE, - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; - mockConnectedAccountDataAccessService.findOne.mockResolvedValue( + mockConnectedAccountRepository.findOne.mockResolvedValue( existingConnectedAccount, ); - mockWorkspaceMemberRepository.findOneOrFail.mockResolvedValue({ + mockWorkspaceMemberRepository.findOne.mockResolvedValue({ id: 'workspace-member-id', userId: 'user-id', }); @@ -240,14 +260,15 @@ describe('GoogleAPIsService', () => { syncStage: CalendarChannelSyncStage.FAILED, }; - mockCalendarChannelDataAccessService.find.mockResolvedValue([ + mockCalendarChannelRepository.find.mockResolvedValue([ failedCalendarChannel, ]); - mockMessageChannelDataAccessService.find.mockResolvedValue([]); + mockMessageChannelRepository.find.mockResolvedValue([]); await service.refreshGoogleRefreshToken({ handle: 'test@example.com', + userId: 'user-id', workspaceMemberId: 'workspace-member-id', workspaceId: 'workspace-id', accessToken: 'new-access-token', diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts index c71b6059f9..ed858dac0a 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/google-apis.service.ts @@ -1,7 +1,17 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { ConnectedAccountProvider, FeatureFlagKey } from 'twenty-shared/types'; +import { + CalendarChannelSyncStage, + type CalendarChannelVisibility, + ConnectedAccountProvider, + FeatureFlagKey, + MessageChannelSyncStage, + MessageChannelSyncStatus, + type MessageChannelVisibility, +} from 'twenty-shared/types'; import { v4 } from 'uuid'; +import { EntityManager, Repository } from 'typeorm'; import { AuthException, @@ -14,14 +24,15 @@ import { GoogleAPIScopesService } from 'src/engine/core-modules/auth/services/go import { GoogleApisServiceAvailabilityService } from 'src/engine/core-modules/auth/services/google-apis-service-availability.service'; import { UpdateConnectedAccountOnReconnectService } from 'src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; +import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; -import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { @@ -29,23 +40,14 @@ import { type CalendarEventListFetchJobData, } from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; -import { - CalendarChannelSyncStage, - type CalendarChannelVisibility, -} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; -import { - MessageChannelSyncStage, - MessageChannelSyncStatus, - type MessageChannelVisibility, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessagingMessageListFetchJob, type MessagingMessageListFetchJobData, } from 'src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job'; -import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; +import { isDefined } from 'twenty-shared/utils'; @Injectable() export class GoogleAPIsService { @@ -66,13 +68,20 @@ export class GoogleAPIsService { private readonly googleAPIScopesService: GoogleAPIScopesService, private readonly googleApisServiceAvailabilityService: GoogleApisServiceAvailabilityService, private readonly featureFlagService: FeatureFlagService, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, + private readonly syncMessageFoldersService: SyncMessageFoldersService, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, ) {} async refreshGoogleRefreshToken(input: { handle: string; + userId: string; workspaceMemberId: string; workspaceId: string; accessToken: string; @@ -84,6 +93,7 @@ export class GoogleAPIsService { const { handle, workspaceId, + userId, workspaceMemberId, calendarVisibility, messageVisibility, @@ -132,19 +142,48 @@ export class GoogleAPIsService { return this.globalWorkspaceOrmManager.executeInWorkspaceContext( async () => { - const connectedAccount = - await this.connectedAccountDataAccessService.findOne(workspaceId, { - where: { handle, accountOwnerId: workspaceMemberId }, - }); + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { userId, workspaceId }, + }); + + if (!isDefined(userWorkspace)) { + throw new AuthException( + `User workspace not found for user ${userId} in workspace ${workspaceId}`, + AuthExceptionCode.INVALID_INPUT, + ); + } + + const userWorkspaceId = userWorkspace.id; + + const connectedAccount = await this.connectedAccountRepository.findOne({ + where: { + handle, + userWorkspaceId, + workspaceId, + }, + }); const existingAccountId = connectedAccount?.id; const newOrExistingConnectedAccountId = existingAccountId ?? v4(); - const workspaceDataSource = - await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource(); + const existingMessageChannels = + await this.messageChannelRepository.find({ + where: { + connectedAccountId: newOrExistingConnectedAccountId, + workspaceId, + }, + }); - await workspaceDataSource.transaction( - async (manager: WorkspaceEntityManager) => { + const existingCalendarChannels = + await this.calendarChannelRepository.find({ + where: { + connectedAccountId: newOrExistingConnectedAccountId, + workspaceId, + }, + }); + + await this.messageChannelRepository.manager.transaction( + async (transactionManager: EntityManager) => { await this.createConnectedAccountService.createConnectedAccount({ workspaceId, connectedAccountId: newOrExistingConnectedAccountId, @@ -154,7 +193,7 @@ export class GoogleAPIsService { refreshToken: input.refreshToken, accountOwnerId: workspaceMemberId, scopes, - manager, + transactionManager, }); if (existingAccountId) { @@ -165,24 +204,10 @@ export class GoogleAPIsService { accessToken: input.accessToken, refreshToken: input.refreshToken, scopes, - manager, + transactionManager, }, ); - const workspaceMemberRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workspaceMember', - { shouldBypassPermissionChecks: true }, - ); - - const workspaceMember = - await workspaceMemberRepository.findOneOrFail({ - where: { id: workspaceMemberId }, - }); - - const userId = workspaceMember.userId; - await this.accountsToReconnectService.removeAccountToReconnect( userId, workspaceId, @@ -190,11 +215,6 @@ export class GoogleAPIsService { ); } - const existingMessageChannels = - await this.messageChannelDataAccessService.find(workspaceId, { - connectedAccountId: newOrExistingConnectedAccountId, - }); - if ( isMessagingEnabled && isMessagingAvailable && @@ -205,18 +225,11 @@ export class GoogleAPIsService { connectedAccountId: newOrExistingConnectedAccountId, handle, messageVisibility, - manager, skipMessageChannelConfiguration, + transactionManager, }); } - const existingCalendarChannels = - await this.calendarChannelDataAccessService.find(workspaceId, { - where: { - connectedAccountId: newOrExistingConnectedAccountId, - }, - }); - if ( isCalendarEnabled && isCalendarAvailable && @@ -227,18 +240,43 @@ export class GoogleAPIsService { connectedAccountId: newOrExistingConnectedAccountId, handle, calendarVisibility, - manager, skipMessageChannelConfiguration, + transactionManager, }); } }, ); - if (isMessagingEnabled) { - const messageChannels = - await this.messageChannelDataAccessService.find(workspaceId, { - connectedAccountId: newOrExistingConnectedAccountId, + if ( + isMessagingEnabled && + isMessagingAvailable && + existingMessageChannels.length === 0 + ) { + const newMessageChannel = await this.messageChannelRepository.findOne( + { + where: { + connectedAccountId: newOrExistingConnectedAccountId, + workspaceId, + }, + relations: ['connectedAccount', 'messageFolders'], + }, + ); + + if (isDefined(newMessageChannel)) { + await this.syncMessageFoldersService.syncMessageFolders({ + messageChannel: newMessageChannel, + workspaceId, }); + } + } + + if (isMessagingEnabled) { + const messageChannels = await this.messageChannelRepository.find({ + where: { + connectedAccountId: newOrExistingConnectedAccountId, + workspaceId, + }, + }); if (!isMessagingAvailable && messageChannels.length > 0) { await this.messagingChannelSyncStatusService.markAsFailed( @@ -268,12 +306,12 @@ export class GoogleAPIsService { } if (isCalendarEnabled) { - const calendarChannels = - await this.calendarChannelDataAccessService.find(workspaceId, { - where: { - connectedAccountId: newOrExistingConnectedAccountId, - }, - }); + const calendarChannels = await this.calendarChannelRepository.find({ + where: { + connectedAccountId: newOrExistingConnectedAccountId, + workspaceId, + }, + }); if (!isCalendarAvailable && calendarChannels.length > 0) { await this.calendarChannelSyncStatusService.markAsFailedInsufficientPermissionsAndFlushCalendarEventsToImport( diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.spec.ts index 86eddd77ae..896993f994 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.spec.ts @@ -1,30 +1,31 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { ConnectedAccountProvider } from 'twenty-shared/types'; +import { + CalendarChannelSyncStage, + CalendarChannelVisibility, + ConnectedAccountProvider, + MessageChannelVisibility, +} 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 { MicrosoftAPIsService } from 'src/engine/core-modules/auth/services/microsoft-apis.service'; import { UpdateConnectedAccountOnReconnectService } from 'src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.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 { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; -import { - CalendarChannelSyncStage, - CalendarChannelVisibility, -} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; -import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service'; jest.mock('uuid', () => ({ v4: jest.fn(() => 'mocked-uuid'), @@ -36,24 +37,36 @@ describe('MicrosoftAPIsService', () => { let calendarChannelSyncStatusService: CalendarChannelSyncStatusService; let createMessageChannelService: CreateMessageChannelService; - const mockConnectedAccountDataAccessService = { + const mockConnectedAccountRepository = { findOne: jest.fn(), }; - const mockMessageChannelDataAccessService = { + const mockTransactionManager = { + getRepository: jest.fn().mockReturnValue({ save: jest.fn() }), + }; + + const mockMessageChannelRepository = { + find: jest.fn(), + findOne: jest.fn().mockResolvedValue(null), + manager: { + transaction: jest.fn((callback) => callback(mockTransactionManager)), + }, + }; + + const mockCalendarChannelRepository = { find: jest.fn(), }; - const mockCalendarChannelDataAccessService = { - find: jest.fn(), + const mockUserWorkspaceRepository = { + findOne: jest.fn().mockResolvedValue({ id: 'user-workspace-id' }), }; const mockWorkspaceMemberRepository = { findOneOrFail: jest.fn(), - }; - - const mockWorkspaceDataSource = { - transaction: jest.fn((callback) => callback({})), + findOne: jest.fn().mockResolvedValue({ + id: 'workspace-member-id', + userId: 'user-id', + }), }; const mockTwentyConfigService = { @@ -83,9 +96,6 @@ describe('MicrosoftAPIsService', () => { return {}; }), - getGlobalWorkspaceDataSource: jest - .fn() - .mockResolvedValue(mockWorkspaceDataSource), executeInWorkspaceContext: jest .fn() .mockImplementation((fn: () => any, _authContext?: any) => fn()), @@ -154,16 +164,26 @@ describe('MicrosoftAPIsService', () => { useValue: mockCalendarQueueService, }, { - provide: ConnectedAccountDataAccessService, - useValue: mockConnectedAccountDataAccessService, + provide: getRepositoryToken(ConnectedAccountEntity), + useValue: mockConnectedAccountRepository, }, { - provide: MessageChannelDataAccessService, - useValue: mockMessageChannelDataAccessService, + provide: getRepositoryToken(UserWorkspaceEntity), + useValue: mockUserWorkspaceRepository, }, { - provide: CalendarChannelDataAccessService, - useValue: mockCalendarChannelDataAccessService, + provide: getRepositoryToken(MessageChannelEntity), + useValue: mockMessageChannelRepository, + }, + { + provide: getRepositoryToken(CalendarChannelEntity), + useValue: mockCalendarChannelRepository, + }, + { + provide: SyncMessageFoldersService, + useValue: { + syncMessageFolders: jest.fn().mockResolvedValue([]), + }, }, ], }).compile(); @@ -194,15 +214,15 @@ describe('MicrosoftAPIsService', () => { const existingConnectedAccount = { id: 'existing-account-id', handle: 'test@example.com', - accountOwnerId: 'workspace-member-id', + userWorkspaceId: 'user-workspace-id', provider: ConnectedAccountProvider.MICROSOFT, - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; - mockConnectedAccountDataAccessService.findOne.mockResolvedValue( + mockConnectedAccountRepository.findOne.mockResolvedValue( existingConnectedAccount, ); - mockWorkspaceMemberRepository.findOneOrFail.mockResolvedValue({ + mockWorkspaceMemberRepository.findOne.mockResolvedValue({ id: 'workspace-member-id', userId: 'user-id', }); @@ -214,11 +234,11 @@ describe('MicrosoftAPIsService', () => { syncStage: CalendarChannelSyncStage.FAILED, }; - mockCalendarChannelDataAccessService.find.mockResolvedValue([ + mockCalendarChannelRepository.find.mockResolvedValue([ failedCalendarChannel, ]); - mockMessageChannelDataAccessService.find.mockResolvedValue([ + mockMessageChannelRepository.find.mockResolvedValue([ { id: 'message-channel-id', connectedAccountId: 'existing-account-id', @@ -227,6 +247,7 @@ describe('MicrosoftAPIsService', () => { await service.refreshMicrosoftRefreshToken({ handle: 'test@example.com', + userId: 'user-id', workspaceMemberId: 'workspace-member-id', workspaceId: 'workspace-id', accessToken: 'new-access-token', diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.ts index 9edfe43e74..c3dd9297ed 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/microsoft-apis.service.ts @@ -1,21 +1,34 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { ConnectedAccountProvider } from 'twenty-shared/types'; +import { + CalendarChannelSyncStage, + type CalendarChannelVisibility, + ConnectedAccountProvider, + MessageChannelSyncStage, + type MessageChannelVisibility, +} from 'twenty-shared/types'; import { v4 } from 'uuid'; +import { EntityManager, Repository } from 'typeorm'; +import { + AuthException, + AuthExceptionCode, +} from 'src/engine/core-modules/auth/auth.exception'; 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 { 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 { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; -import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { @@ -23,22 +36,14 @@ import { type CalendarEventListFetchJobData, } from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; -import { - CalendarChannelSyncStage, - type CalendarChannelVisibility, -} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; -import { - MessageChannelSyncStage, - type MessageChannelVisibility, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessagingMessageListFetchJob, type MessagingMessageListFetchJobData, } from 'src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job'; -import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; +import { isDefined } from 'twenty-shared/utils'; @Injectable() export class MicrosoftAPIsService { @@ -56,13 +61,20 @@ export class MicrosoftAPIsService { private readonly createConnectedAccountService: CreateConnectedAccountService, private readonly updateConnectedAccountOnReconnectService: UpdateConnectedAccountOnReconnectService, private readonly twentyConfigService: TwentyConfigService, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, + private readonly syncMessageFoldersService: SyncMessageFoldersService, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, ) {} async refreshMicrosoftRefreshToken(input: { handle: string; + userId: string; workspaceMemberId: string; workspaceId: string; accessToken: string; @@ -74,6 +86,7 @@ export class MicrosoftAPIsService { const { handle, workspaceId, + userId, workspaceMemberId, calendarVisibility, messageVisibility, @@ -86,19 +99,48 @@ export class MicrosoftAPIsService { return this.globalWorkspaceOrmManager.executeInWorkspaceContext( async () => { - const connectedAccount = - await this.connectedAccountDataAccessService.findOne(workspaceId, { - where: { handle, accountOwnerId: workspaceMemberId }, - }); + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { userId, workspaceId }, + }); + + if (!isDefined(userWorkspace)) { + throw new AuthException( + `User workspace not found for user ${userId} in workspace ${workspaceId}`, + AuthExceptionCode.INVALID_INPUT, + ); + } + + const userWorkspaceId = userWorkspace.id; + + const connectedAccount = await this.connectedAccountRepository.findOne({ + where: { + handle, + userWorkspaceId: userWorkspaceId, + workspaceId, + }, + }); const existingAccountId = connectedAccount?.id; const newOrExistingConnectedAccountId = existingAccountId ?? v4(); - const workspaceDataSource = - await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource(); + const existingMessageChannels = + await this.messageChannelRepository.find({ + where: { + connectedAccountId: newOrExistingConnectedAccountId, + workspaceId, + }, + }); - await workspaceDataSource.transaction( - async (manager: WorkspaceEntityManager) => { + const existingCalendarChannels = + await this.calendarChannelRepository.find({ + where: { + connectedAccountId: newOrExistingConnectedAccountId, + workspaceId, + }, + }); + + await this.messageChannelRepository.manager.transaction( + async (transactionManager: EntityManager) => { await this.createConnectedAccountService.createConnectedAccount({ workspaceId, connectedAccountId: newOrExistingConnectedAccountId, @@ -108,7 +150,7 @@ export class MicrosoftAPIsService { refreshToken: input.refreshToken, accountOwnerId: workspaceMemberId, scopes, - manager, + transactionManager, }); if (existingAccountId) { @@ -119,24 +161,10 @@ export class MicrosoftAPIsService { accessToken: input.accessToken, refreshToken: input.refreshToken, scopes, - manager, + transactionManager, }, ); - const workspaceMemberRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workspaceMember', - { shouldBypassPermissionChecks: true }, - ); - - const workspaceMember = - await workspaceMemberRepository.findOneOrFail({ - where: { id: workspaceMemberId }, - }); - - const userId = workspaceMember.userId; - await this.accountsToReconnectService.removeAccountToReconnect( userId, workspaceId, @@ -154,11 +182,6 @@ export class MicrosoftAPIsService { ); } - const existingMessageChannels = - await this.messageChannelDataAccessService.find(workspaceId, { - connectedAccountId: newOrExistingConnectedAccountId, - }); - if ( this.twentyConfigService.get( 'MESSAGING_PROVIDER_MICROSOFT_ENABLED', @@ -170,18 +193,11 @@ export class MicrosoftAPIsService { connectedAccountId: newOrExistingConnectedAccountId, handle, messageVisibility, - manager, skipMessageChannelConfiguration, + transactionManager, }); } - const existingCalendarChannels = - await this.calendarChannelDataAccessService.find(workspaceId, { - where: { - connectedAccountId: newOrExistingConnectedAccountId, - }, - }); - if ( this.twentyConfigService.get( 'CALENDAR_PROVIDER_MICROSOFT_ENABLED', @@ -193,20 +209,46 @@ export class MicrosoftAPIsService { connectedAccountId: newOrExistingConnectedAccountId, handle, calendarVisibility, - manager, skipMessageChannelConfiguration, + transactionManager, }); } }, ); + if ( + this.twentyConfigService.get( + 'MESSAGING_PROVIDER_MICROSOFT_ENABLED', + ) && + existingMessageChannels.length === 0 + ) { + const newMessageChannel = await this.messageChannelRepository.findOne( + { + where: { + connectedAccountId: newOrExistingConnectedAccountId, + workspaceId, + }, + relations: ['connectedAccount', 'messageFolders'], + }, + ); + + if (isDefined(newMessageChannel)) { + await this.syncMessageFoldersService.syncMessageFolders({ + messageChannel: newMessageChannel, + workspaceId, + }); + } + } + if ( this.twentyConfigService.get('MESSAGING_PROVIDER_MICROSOFT_ENABLED') ) { - const messageChannels = - await this.messageChannelDataAccessService.find(workspaceId, { + const messageChannels = await this.messageChannelRepository.find({ + where: { connectedAccountId: newOrExistingConnectedAccountId, - }); + workspaceId, + }, + }); for (const messageChannel of messageChannels) { if ( @@ -227,12 +269,12 @@ export class MicrosoftAPIsService { if ( this.twentyConfigService.get('CALENDAR_PROVIDER_MICROSOFT_ENABLED') ) { - const calendarChannels = - await this.calendarChannelDataAccessService.find(workspaceId, { - where: { - connectedAccountId: newOrExistingConnectedAccountId, - }, - }); + const calendarChannels = await this.calendarChannelRepository.find({ + where: { + connectedAccountId: newOrExistingConnectedAccountId, + workspaceId, + }, + }); for (const calendarChannel of calendarChannels) { if ( diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service.ts index 4847906c22..f184a36696 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service.ts @@ -1,7 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; -import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; +import { EntityManager } from 'typeorm'; + +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; @@ -11,14 +12,13 @@ export type UpdateConnectedAccountOnReconnectInput = { accessToken: string; refreshToken: string; scopes: string[]; - manager: WorkspaceEntityManager; + transactionManager: EntityManager; }; @Injectable() export class UpdateConnectedAccountOnReconnectService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, ) {} async updateConnectedAccountOnReconnect( @@ -35,18 +35,20 @@ export class UpdateConnectedAccountOnReconnectService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.connectedAccountDataAccessService.update( - workspaceId, - { - id: connectedAccountId, - }, - { - accessToken, - refreshToken, - scopes, - authFailedAt: null, - }, - ); + await input.transactionManager + .getRepository(ConnectedAccountEntity) + .update( + { + id: connectedAccountId, + workspaceId, + }, + { + accessToken, + refreshToken, + scopes, + authFailedAt: null, + }, + ); }, authContext); } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/types/apis-oauth-request.type.ts b/packages/twenty-server/src/engine/core-modules/auth/types/apis-oauth-request.type.ts index 7f384a3a59..6e56b0b21b 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/types/apis-oauth-request.type.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/types/apis-oauth-request.type.ts @@ -1,7 +1,9 @@ import { type Request } from 'express'; -import { type CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; -import { type MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { + type CalendarChannelVisibility, + type MessageChannelVisibility, +} from 'twenty-shared/types'; export type APIsOAuthRequest = Omit< Request, diff --git a/packages/twenty-server/src/engine/core-modules/auth/types/apis-oauth-state.type.ts b/packages/twenty-server/src/engine/core-modules/auth/types/apis-oauth-state.type.ts index fba16ea129..d8e4e16707 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/types/apis-oauth-state.type.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/types/apis-oauth-state.type.ts @@ -1,5 +1,7 @@ -import { type CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; -import { type MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { + CalendarChannelVisibility, + MessageChannelVisibility, +} from 'twenty-shared/types'; export type APIsOAuthState = { transientToken?: string; diff --git a/packages/twenty-server/src/engine/core-modules/auth/utils/__tests__/google-apis-set-request-extra-params.util.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/utils/__tests__/google-apis-set-request-extra-params.util.spec.ts index 24580991d4..aaa9cc4e9f 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/utils/__tests__/google-apis-set-request-extra-params.util.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/utils/__tests__/google-apis-set-request-extra-params.util.spec.ts @@ -1,7 +1,9 @@ +import { + CalendarChannelVisibility, + MessageChannelVisibility, +} from 'twenty-shared/types'; import { type APIsOAuthRequest } from 'src/engine/core-modules/auth/types/apis-oauth-request.type'; import { setRequestExtraParams } from 'src/engine/core-modules/auth/utils/google-apis-set-request-extra-params.util'; -import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; -import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; describe('googleApisSetRequestExtraParams', () => { it('should set request extra params', () => { diff --git a/packages/twenty-server/src/engine/core-modules/calendar/dtos/timeline-calendar-event.dto.ts b/packages/twenty-server/src/engine/core-modules/calendar/dtos/timeline-calendar-event.dto.ts index a0b1efe7d4..d4e5f65405 100644 --- a/packages/twenty-server/src/engine/core-modules/calendar/dtos/timeline-calendar-event.dto.ts +++ b/packages/twenty-server/src/engine/core-modules/calendar/dtos/timeline-calendar-event.dto.ts @@ -1,8 +1,8 @@ import { Field, ObjectType } from '@nestjs/graphql'; +import { CalendarChannelVisibility } from 'twenty-shared/types'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { TimelineCalendarEventParticipantDTO } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-event-participant.dto'; -import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; @ObjectType('LinkMetadata') class LinkMetadataDTO { diff --git a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.module.ts b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.module.ts index 4bb3334b3e..6017807226 100644 --- a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.module.ts +++ b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.module.ts @@ -1,11 +1,22 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { TimelineCalendarEventResolver } from 'src/engine/core-modules/calendar/timeline-calendar-event.resolver'; import { TimelineCalendarEventService } from 'src/engine/core-modules/calendar/timeline-calendar-event.service'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { UserModule } from 'src/engine/core-modules/user/user.module'; @Module({ - imports: [UserModule], + imports: [ + UserModule, + TypeOrmModule.forFeature([ + CalendarChannelEntity, + ConnectedAccountEntity, + UserWorkspaceEntity, + ]), + ], exports: [], providers: [TimelineCalendarEventResolver, TimelineCalendarEventService], }) diff --git a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.spec.ts b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.spec.ts index e0a02404c1..125882755d 100644 --- a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.spec.ts @@ -1,10 +1,14 @@ import { Test, type TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; +import { CalendarChannelVisibility } from 'twenty-shared/types'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; -import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { type CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; import { TimelineCalendarEventService } from './timeline-calendar-event.service'; @@ -19,6 +23,10 @@ type MockWorkspaceRepository = Partial< describe('TimelineCalendarEventService', () => { let service: TimelineCalendarEventService; let mockCalendarEventRepository: MockWorkspaceRepository; + let mockCalendarChannelCoreRepository: { find: jest.Mock }; + let mockConnectedAccountRepository: { find: jest.Mock }; + let mockUserWorkspaceRepository: { findOne: jest.Mock }; + let mockWorkspaceMemberRepository: { findOne: jest.Mock }; const mockCalendarEvent: Partial = { id: '1', @@ -36,8 +44,32 @@ describe('TimelineCalendarEventService', () => { findAndCount: jest.fn(), }; + mockConnectedAccountRepository = { + find: jest.fn().mockResolvedValue([]), + }; + + mockCalendarChannelCoreRepository = { + find: jest.fn().mockResolvedValue([]), + }; + + mockUserWorkspaceRepository = { + findOne: jest.fn().mockResolvedValue(null), + }; + + mockWorkspaceMemberRepository = { + findOne: jest.fn().mockResolvedValue(null), + }; + const mockGlobalWorkspaceOrmManager = { - getRepository: jest.fn().mockResolvedValue(mockCalendarEventRepository), + getRepository: jest + .fn() + .mockImplementation((_workspaceId, entityName) => { + if (entityName === 'workspaceMember') { + return Promise.resolve(mockWorkspaceMemberRepository); + } + + return Promise.resolve(mockCalendarEventRepository); + }), executeInWorkspaceContext: jest .fn() .mockImplementation((fn: () => any, _authContext?: any) => fn()), @@ -50,6 +82,18 @@ describe('TimelineCalendarEventService', () => { provide: GlobalWorkspaceOrmManager, useValue: mockGlobalWorkspaceOrmManager, }, + { + provide: getRepositoryToken(CalendarChannelEntity), + useValue: mockCalendarChannelCoreRepository, + }, + { + provide: getRepositoryToken(ConnectedAccountEntity), + useValue: mockConnectedAccountRepository, + }, + { + provide: getRepositoryToken(UserWorkspaceEntity), + useValue: mockUserWorkspaceRepository, + }, ], }).compile(); @@ -70,19 +114,21 @@ describe('TimelineCalendarEventService', () => { { ...mockCalendarEvent, calendarChannelEventAssociations: [ - { - calendarChannel: { - visibility: CalendarChannelVisibility.SHARE_EVERYTHING, - connectedAccount: { - accountOwnerId: 'other-workspace-member-id', - }, - }, - }, + { calendarChannelId: 'channel-1' }, ], }, ], 1, ]); + mockCalendarChannelCoreRepository.find.mockResolvedValue([ + { + id: 'channel-1', + visibility: CalendarChannelVisibility.SHARE_EVERYTHING, + connectedAccountId: 'connected-account-1', + }, + ]); + // Ownership doesn't matter for SHARE_EVERYTHING + mockWorkspaceMemberRepository.findOne.mockResolvedValue(null); const result = await service.getCalendarEventsFromPersonIds({ currentWorkspaceMemberId, @@ -110,19 +156,27 @@ describe('TimelineCalendarEventService', () => { { ...mockCalendarEvent, calendarChannelEventAssociations: [ - { - calendarChannel: { - visibility: CalendarChannelVisibility.METADATA, - connectedAccount: { - accountOwnerId: 'other-workspace-member-id', - }, - }, - }, + { calendarChannelId: 'channel-1' }, ], }, ], 1, ]); + mockCalendarChannelCoreRepository.find.mockResolvedValue([ + { + id: 'channel-1', + visibility: CalendarChannelVisibility.METADATA, + connectedAccountId: 'connected-account-1', + }, + ]); + // Current user resolves but doesn't own the account + mockWorkspaceMemberRepository.findOne.mockResolvedValue({ + userId: 'current-user-id', + }); + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'current-uw-id', + }); + mockConnectedAccountRepository.find.mockResolvedValue([]); const result = await service.getCalendarEventsFromPersonIds({ currentWorkspaceMemberId, @@ -152,19 +206,29 @@ describe('TimelineCalendarEventService', () => { { ...mockCalendarEvent, calendarChannelEventAssociations: [ - { - calendarChannel: { - visibility: CalendarChannelVisibility.METADATA, - connectedAccount: { - accountOwnerId: 'current-workspace-member-id', - }, - }, - }, + { calendarChannelId: 'channel-1' }, ], }, ], 1, ]); + mockCalendarChannelCoreRepository.find.mockResolvedValue([ + { + id: 'channel-1', + visibility: CalendarChannelVisibility.METADATA, + connectedAccountId: 'connected-account-1', + }, + ]); + // Current user resolves and owns the account + mockWorkspaceMemberRepository.findOne.mockResolvedValue({ + userId: 'current-user-id', + }); + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'current-uw-id', + }); + mockConnectedAccountRepository.find.mockResolvedValue([ + { id: 'connected-account-1' }, + ]); const result = await service.getCalendarEventsFromPersonIds({ currentWorkspaceMemberId, diff --git a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts index f2e9ea1d11..f982c041e9 100644 --- a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts +++ b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts @@ -1,22 +1,33 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import omit from 'lodash.omit'; import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; -import { Any } from 'typeorm'; +import { Any, In, type Repository } from 'typeorm'; +import { CalendarChannelVisibility } from 'twenty-shared/types'; import { TIMELINE_CALENDAR_EVENTS_DEFAULT_PAGE_SIZE } from 'src/engine/core-modules/calendar/constants/calendar.constants'; import { type TimelineCalendarEventsWithTotalDTO } from 'src/engine/core-modules/calendar/dtos/timeline-calendar-events-with-total.dto'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; -import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { type CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; import { type OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity'; import { type PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; +import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @Injectable() export class TimelineCalendarEventService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, ) {} async getCalendarEventsFromPersonIds({ @@ -79,16 +90,83 @@ export class TimelineCalendarEventService { person: true, workspaceMember: true, }, - calendarChannelEventAssociations: { - calendarChannel: { - connectedAccount: { - accountOwner: true, - }, - }, - }, + calendarChannelEventAssociations: true, }, }); + const allCalendarChannelIds = [ + ...new Set( + events.flatMap((event) => + event.calendarChannelEventAssociations.map( + (association) => association.calendarChannelId, + ), + ), + ), + ]; + + const calendarChannels = + allCalendarChannelIds.length > 0 + ? await this.calendarChannelRepository.find({ + where: { id: In(allCalendarChannelIds), workspaceId }, + }) + : []; + + // Resolve current user's userWorkspaceId (workspaceMember → userId → userWorkspace) + const workspaceMemberRepo = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workspaceMember', + { shouldBypassPermissionChecks: true }, + ); + + const currentMember = await workspaceMemberRepo.findOne({ + where: { id: currentWorkspaceMemberId }, + select: { userId: true }, + }); + + const currentUserWorkspaceId = currentMember + ? (( + await this.userWorkspaceRepository.findOne({ + where: { userId: currentMember.userId, workspaceId }, + select: { id: true }, + }) + )?.id ?? null) + : null; + + // Find which connected accounts the current user owns (1 query) + const connectedAccountIds = [ + ...new Set( + calendarChannels.map((channel) => channel.connectedAccountId), + ), + ]; + + const ownedAccountIds = + connectedAccountIds.length > 0 && currentUserWorkspaceId + ? new Set( + ( + await this.connectedAccountRepository.find({ + where: { + id: In(connectedAccountIds), + userWorkspaceId: currentUserWorkspaceId, + }, + select: { id: true }, + }) + ).map((a) => a.id), + ) + : new Set(); + + const calendarChannelMap = new Map( + calendarChannels.map((channel) => [ + channel.id, + { + visibility: channel.visibility, + isOwnedByCurrentUser: ownedAccountIds.has( + channel.connectedAccountId, + ), + }, + ]), + ); + const orderedEvents = events.sort( (a, b) => ids.indexOf(a.id) - ids.indexOf(b.id), ); @@ -123,20 +201,22 @@ export class TimelineCalendarEventService { }), ); - const isCalendarEventImportedByCurrentWorkspaceMember = - event.calendarChannelEventAssociations.some( - (association) => - association.calendarChannel.connectedAccount.accountOwnerId === - currentWorkspaceMemberId, - ); + const hasFullAccess = event.calendarChannelEventAssociations.some( + (association) => { + const channel = calendarChannelMap.get( + association.calendarChannelId, + ); - const visibility = - event.calendarChannelEventAssociations.some( - (association) => - association.calendarChannel.visibility === 'SHARE_EVERYTHING', - ) || isCalendarEventImportedByCurrentWorkspaceMember - ? CalendarChannelVisibility.SHARE_EVERYTHING - : CalendarChannelVisibility.METADATA; + return ( + channel?.visibility === 'SHARE_EVERYTHING' || + channel?.isOwnedByCurrentUser + ); + }, + ); + + const visibility = hasFullAccess + ? CalendarChannelVisibility.SHARE_EVERYTHING + : CalendarChannelVisibility.METADATA; return { ...omit(event, [ diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connected-account.dto.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connected-account.dto.ts index c50f3335eb..d42f283de2 100644 --- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connected-account.dto.ts +++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connected-account.dto.ts @@ -17,7 +17,7 @@ export class ConnectedImapSmtpCaldavAccountDTO { provider: ConnectedAccountProvider; @Field(() => UUIDScalarType) - accountOwnerId: string; + userWorkspaceId: string; @Field(() => ImapSmtpCaldavConnectionParametersDTO, { nullable: true }) connectionParameters: ImapSmtpCaldavConnectionParametersDTO | null; diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.module.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.module.ts index b6ad67a763..d0701f1d82 100644 --- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.module.ts +++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.module.ts @@ -1,9 +1,10 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { ImapSmtpCaldavValidatorModule } from 'src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection-validator.module'; import { MessageQueueModule } from 'src/engine/core-modules/message-queue/message-queue.module'; -import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module'; import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module'; @@ -22,7 +23,7 @@ import { ImapSmtpCaldavService } from './services/imap-smtp-caldav-connection.se IMAPAPIsModule, MessagingImportManagerModule, MessageQueueModule, - ConnectedAccountDataAccessModule, + TypeOrmModule.forFeature([ConnectedAccountEntity]), TwentyORMModule, FeatureFlagModule, ImapSmtpCaldavValidatorModule, diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts index f23700c7dd..4415021dab 100644 --- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts @@ -57,7 +57,7 @@ export class ImapSmtpCaldavResolver { handle: connectedAccount.handle, provider: connectedAccount.provider, connectionParameters: connectedAccount.connectionParameters, - accountOwnerId: connectedAccount.accountOwnerId, + userWorkspaceId: connectedAccount.userWorkspaceId, }; } diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts index 6e26f3a019..b05f4838b0 100644 --- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts +++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/services/imap-smtp-caldav-connection.service.ts @@ -1,12 +1,14 @@ import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { msg } from '@lingui/core/macro'; import { ImapFlow } from 'imapflow'; import { createTransport } from 'nodemailer'; import { ConnectedAccountProvider } from 'twenty-shared/types'; +import { Repository } from 'typeorm'; import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { type AccountType, type ConnectionParameters, @@ -14,7 +16,6 @@ import { import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { CalDAVClient } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/lib/caldav.client'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; @Injectable() export class ImapSmtpCaldavService { @@ -22,7 +23,8 @@ export class ImapSmtpCaldavService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, ) {} async testImapConnection( @@ -185,20 +187,20 @@ export class ImapSmtpCaldavService { async getImapSmtpCaldav( workspaceId: string, connectionId: string, - ): Promise { + ): Promise { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( async () => { - const connectedAccount = - await this.connectedAccountDataAccessService.findOne(workspaceId, { - where: { - id: connectionId, - provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV, - }, - }); + const connectedAccount = await this.connectedAccountRepository.findOne({ + where: { + id: connectionId, + provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV, + workspaceId, + }, + }); - return connectedAccount as ConnectedAccountWorkspaceEntity | null; + return connectedAccount; }, authContext, ); diff --git a/packages/twenty-server/src/engine/core-modules/messaging/dtos/timeline-thread.dto.ts b/packages/twenty-server/src/engine/core-modules/messaging/dtos/timeline-thread.dto.ts index 72dafca778..04b9abbbd8 100644 --- a/packages/twenty-server/src/engine/core-modules/messaging/dtos/timeline-thread.dto.ts +++ b/packages/twenty-server/src/engine/core-modules/messaging/dtos/timeline-thread.dto.ts @@ -1,8 +1,8 @@ import { Field, ObjectType } from '@nestjs/graphql'; +import { MessageChannelVisibility } from 'twenty-shared/types'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { TimelineThreadParticipantDTO } from 'src/engine/core-modules/messaging/dtos/timeline-thread-participant.dto'; -import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; @ObjectType('TimelineThread') export class TimelineThreadDTO { diff --git a/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts b/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts index b2b4cfda59..f3535db298 100644 --- a/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts +++ b/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts @@ -1,19 +1,32 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { MessageParticipantRole } from 'twenty-shared/types'; -import { In } from 'typeorm'; +import { + MessageChannelVisibility, + MessageParticipantRole, +} from 'twenty-shared/types'; +import { In, type Repository } from 'typeorm'; import { type TimelineThreadDTO } from 'src/engine/core-modules/messaging/dtos/timeline-thread.dto'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; -import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { type MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity'; import { type MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity'; +import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @Injectable() export class TimelineMessagingService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, ) {} public async getAndCountMessageThreads( @@ -212,57 +225,126 @@ export class TimelineMessagingService { return this.globalWorkspaceOrmManager.executeInWorkspaceContext( async () => { + const workspaceMemberRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workspaceMember', + { shouldBypassPermissionChecks: true }, + ); + + const currentMember = await workspaceMemberRepository.findOne({ + where: { id: workspaceMemberId }, + select: { userId: true }, + }); + + if (!currentMember) { + return {}; + } + + const currentUserWorkspace = await this.userWorkspaceRepository.findOne( + { + where: { userId: currentMember.userId, workspaceId }, + select: { id: true }, + }, + ); + + if (!currentUserWorkspace) { + return {}; + } + + const currentUserWorkspaceId = currentUserWorkspace.id; + const messageThreadRepository = await this.globalWorkspaceOrmManager.getRepository( workspaceId, 'messageThread', ); - const threadVisibility = await messageThreadRepository + const threadChannelRows = await messageThreadRepository .createQueryBuilder() .select('messageThread.id', 'id') - .addSelect('messageChannel.visibility', 'visibility') - .addSelect('connectedAccount.accountOwnerId', 'accountOwnerId') + .addSelect( + 'messageChannelMessageAssociation.messageChannelId', + 'messageChannelId', + ) .leftJoin('messageThread.messages', 'message') .leftJoin( 'message.messageChannelMessageAssociations', 'messageChannelMessageAssociation', ) - .leftJoin( - 'messageChannelMessageAssociation.messageChannel', - 'messageChannel', - ) - .leftJoin('messageChannel.connectedAccount', 'connectedAccount') .where('messageThread.id = ANY(:messageThreadIds)', { - messageThreadIds: messageThreadIds, + messageThreadIds, }) - .getRawMany(); + .getRawMany<{ id: string; messageChannelId: string | null }>(); + + const allMessageChannelIds = [ + ...new Set( + threadChannelRows + .map((row) => row.messageChannelId) + .filter((id): id is string => id !== null && id !== undefined), + ), + ]; + + if (allMessageChannelIds.length === 0) { + return {}; + } + + const messageChannels = await this.messageChannelRepository.find({ + where: { id: In(allMessageChannelIds), workspaceId }, + select: { id: true, visibility: true, connectedAccountId: true }, + }); + + const allConnectedAccountIds = [ + ...new Set( + messageChannels.map((channel) => channel.connectedAccountId), + ), + ]; + + const ownedAccountIds = new Set( + ( + await this.connectedAccountRepository.find({ + where: { + id: In(allConnectedAccountIds), + userWorkspaceId: currentUserWorkspaceId, + }, + select: { id: true }, + }) + ).map((account) => account.id), + ); + + const channelVisibilityMap = new Map( + messageChannels.map((channel) => [ + channel.id, + ownedAccountIds.has(channel.connectedAccountId) + ? MessageChannelVisibility.SHARE_EVERYTHING + : channel.visibility, + ]), + ); const visibilityValues = Object.values(MessageChannelVisibility); const threadVisibilityByThreadId: { [key: string]: MessageChannelVisibility; - } = threadVisibility.reduce((threadVisibilityAcc, threadVisibility) => { - if (threadVisibility.accountOwnerId === workspaceMemberId) { - threadVisibilityAcc[threadVisibility.id] = - MessageChannelVisibility.SHARE_EVERYTHING; + } = {}; - return threadVisibilityAcc; - } + for (const { id: threadId, messageChannelId } of threadChannelRows) { + if (!messageChannelId) continue; - threadVisibilityAcc[threadVisibility.id] = + const channelVisibility = channelVisibilityMap.get(messageChannelId); + + if (!channelVisibility) continue; + + threadVisibilityByThreadId[threadId] = visibilityValues[ Math.max( - visibilityValues.indexOf(threadVisibility.visibility), + visibilityValues.indexOf(channelVisibility), visibilityValues.indexOf( - threadVisibilityAcc[threadVisibility.id] ?? + threadVisibilityByThreadId[threadId] ?? MessageChannelVisibility.METADATA, ), ) ]; - - return threadVisibilityAcc; - }, {}); + } return threadVisibilityByThreadId; }, diff --git a/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.module.ts b/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.module.ts index cfe1a28392..579aaefdcc 100644 --- a/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.module.ts +++ b/packages/twenty-server/src/engine/core-modules/messaging/timeline-messaging.module.ts @@ -1,13 +1,17 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { GetMessagesService } from 'src/engine/core-modules/messaging/services/get-messages.service'; import { TimelineMessagingService } from 'src/engine/core-modules/messaging/services/timeline-messaging.service'; import { TimelineMessagingResolver } from 'src/engine/core-modules/messaging/timeline-messaging.resolver'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserModule } from 'src/engine/core-modules/user/user.module'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; -import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; @Module({ imports: [ @@ -16,6 +20,11 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi ConnectedAccountModule, FeatureFlagModule, PermissionsModule, + TypeOrmModule.forFeature([ + MessageChannelEntity, + ConnectedAccountEntity, + UserWorkspaceEntity, + ]), ], exports: [], providers: [ diff --git a/packages/twenty-server/src/engine/core-modules/messaging/utils/format-threads.util.ts b/packages/twenty-server/src/engine/core-modules/messaging/utils/format-threads.util.ts index 2733735413..c6cf0aedc6 100644 --- a/packages/twenty-server/src/engine/core-modules/messaging/utils/format-threads.util.ts +++ b/packages/twenty-server/src/engine/core-modules/messaging/utils/format-threads.util.ts @@ -1,9 +1,9 @@ import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; import { isDefined } from 'twenty-shared/utils'; +import { MessageChannelVisibility } from 'twenty-shared/types'; import { type TimelineThreadDTO } from 'src/engine/core-modules/messaging/dtos/timeline-thread.dto'; import { extractParticipantSummary } from 'src/engine/core-modules/messaging/utils/extract-participant-summary.util'; -import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { type MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity'; export const formatThreads = ( diff --git a/packages/twenty-server/src/engine/core-modules/tool/tool.module.ts b/packages/twenty-server/src/engine/core-modules/tool/tool.module.ts index 9cf9b505a6..19948b9bcd 100644 --- a/packages/twenty-server/src/engine/core-modules/tool/tool.module.ts +++ b/packages/twenty-server/src/engine/core-modules/tool/tool.module.ts @@ -15,7 +15,7 @@ import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool import { NavigateAppTool } from 'src/engine/core-modules/tool/tools/navigate-tool/navigate-app-tool'; import { SearchHelpCenterTool } from 'src/engine/core-modules/tool/tools/search-help-center-tool/search-help-center-tool'; import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; -import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { NavigationMenuItemModule } from 'src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.module'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; import { ViewModule } from 'src/engine/metadata-modules/view/view.module'; @@ -26,7 +26,7 @@ import { MessagingSendManagerModule } from 'src/modules/messaging/message-outbou imports: [ MessagingImportManagerModule, MessagingSendManagerModule, - TypeOrmModule.forFeature([FileEntity]), + TypeOrmModule.forFeature([FileEntity, ConnectedAccountEntity]), ApplicationModule, FeatureFlagModule, FileModule, @@ -36,7 +36,6 @@ import { MessagingSendManagerModule } from 'src/modules/messaging/message-outbou ViewModule, NavigationMenuItemModule, WorkspaceManyOrAllFlatEntityMapsCacheModule, - ConnectedAccountDataAccessModule, ], providers: [ HttpTool, diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/email-tool/email-composer.service.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/email-tool/email-composer.service.ts index da56980849..e8cc8e038f 100644 --- a/packages/twenty-server/src/engine/core-modules/tool/tools/email-tool/email-composer.service.ts +++ b/packages/twenty-server/src/engine/core-modules/tool/tools/email-tool/email-composer.service.ts @@ -20,12 +20,11 @@ import { EmailComposerResult } from 'src/engine/core-modules/tool/tools/email-to import { EmailToolInput } from 'src/engine/core-modules/tool/tools/email-tool/types/email-tool-input.type'; import { parseCommaSeparatedEmails } from 'src/engine/core-modules/tool/tools/email-tool/utils/parse-comma-separated-emails.util'; import { type ToolExecutionContext } from 'src/engine/core-modules/tool/types/tool-execution-context.type'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { MessagingAccountAuthenticationService } from 'src/modules/messaging/message-import-manager/services/messaging-account-authentication.service'; import { type MessageAttachment } from 'src/modules/messaging/message-import-manager/types/message'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { parseEmailBody } from 'src/utils/parse-email-body'; import { streamToBuffer } from 'src/utils/stream-to-buffer'; @Injectable() @@ -34,7 +33,8 @@ export class EmailComposerService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, private readonly messagingAccountAuthenticationService: MessagingAccountAuthenticationService, @InjectRepository(FileEntity) private readonly fileRepository: Repository, @@ -56,15 +56,14 @@ export class EmailComposerService { return this.globalWorkspaceOrmManager.executeInWorkspaceContext( async () => { - const connectedAccount = - await this.connectedAccountDataAccessService.findOne(workspaceId, { - where: { id: connectedAccountId }, - relations: { - messageChannels: { - messageFolders: true, - }, + const connectedAccount = await this.connectedAccountRepository.findOne({ + where: { id: connectedAccountId, workspaceId }, + relations: { + messageChannels: { + messageFolders: true, }, - }); + }, + }); if (!isDefined(connectedAccount)) { throw new EmailToolException( @@ -73,7 +72,7 @@ export class EmailComposerService { ); } - return connectedAccount as unknown as ConnectedAccountWorkspaceEntity; + return connectedAccount; }, authContext, ); @@ -86,8 +85,9 @@ export class EmailComposerService { return this.globalWorkspaceOrmManager.executeInWorkspaceContext( async () => { - const allAccounts = - await this.connectedAccountDataAccessService.find(workspaceId); + const allAccounts = await this.connectedAccountRepository.find({ + where: { workspaceId }, + }); if (!allAccounts || allAccounts.length === 0) { throw new EmailToolException( @@ -271,20 +271,22 @@ export class EmailComposerService { ); } + const connectedAccountAsWorkspaceEntity = connectedAccount; + const { accessToken, refreshToken } = await this.messagingAccountAuthenticationService.validateAndRefreshConnectedAccountAuthentication( { - connectedAccount, + connectedAccount: connectedAccountAsWorkspaceEntity, workspaceId, messageChannelId: messageChannel.id, }, ); const connectedAccountWithFreshTokens = { - ...connectedAccount, + ...connectedAccountAsWorkspaceEntity, accessToken, refreshToken, - } as unknown as ConnectedAccountWorkspaceEntity; + }; const attachments = await this.getAttachments(files || [], workspaceId); diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/email-tool/types/composed-email.type.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/email-tool/types/composed-email.type.ts index 679fe21cc6..9efff36940 100644 --- a/packages/twenty-server/src/engine/core-modules/tool/tools/email-tool/types/composed-email.type.ts +++ b/packages/twenty-server/src/engine/core-modules/tool/tools/email-tool/types/composed-email.type.ts @@ -1,4 +1,4 @@ -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { type MessageAttachment } from 'src/modules/messaging/message-import-manager/types/message'; export type ComposedEmail = { @@ -8,6 +8,6 @@ export type ComposedEmail = { plainTextBody: string; sanitizedHtmlBody: string; attachments: MessageAttachment[]; - connectedAccount: ConnectedAccountWorkspaceEntity; + connectedAccount: ConnectedAccountEntity; inReplyTo?: string; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module.ts b/packages/twenty-server/src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module.ts deleted file mode 100644 index 6c2f244af2..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; -import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; -import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([CalendarChannelEntity]), - FeatureFlagModule, - ConnectedAccountDataAccessModule, - ], - providers: [CalendarChannelDataAccessService], - exports: [CalendarChannelDataAccessService], -}) -export class CalendarChannelDataAccessModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service.ts b/packages/twenty-server/src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service.ts deleted file mode 100644 index 502aa1b0be..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service.ts +++ /dev/null @@ -1,311 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import { FeatureFlagKey } from 'twenty-shared/types'; -import { - type FindManyOptions, - type FindOneOptions, - type FindOptionsWhere, - Repository, -} from 'typeorm'; - -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; -import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; -import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; -import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; - -@Injectable() -export class CalendarChannelDataAccessService { - private readonly logger = new Logger(CalendarChannelDataAccessService.name); - - constructor( - @InjectRepository(CalendarChannelEntity) - private readonly coreRepository: Repository, - private readonly featureFlagService: FeatureFlagService, - private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, - ) {} - - private async isMigrated(workspaceId: string): Promise { - return this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - workspaceId, - ); - } - - private async toCoreWhere( - workspaceId: string, - where: Record, - ): Promise> { - const coreWhere: Record = { ...where, workspaceId }; - - if ( - coreWhere.connectedAccount && - typeof coreWhere.connectedAccount === 'object' - ) { - const connectedAccountWhere = { - ...(coreWhere.connectedAccount as Record), - }; - - if ('accountOwnerId' in connectedAccountWhere) { - const { accountOwnerId, ...restConnectedAccount } = - connectedAccountWhere; - - const resolvedConnectedAccounts = - await this.connectedAccountDataAccessService.find(workspaceId, { - accountOwnerId, - } as never); - - if (resolvedConnectedAccounts.length > 0) { - coreWhere.connectedAccountId = resolvedConnectedAccounts[0].id; - } else { - coreWhere.connectedAccountId = '00000000-0000-0000-0000-000000000000'; - } - - if (Object.keys(restConnectedAccount).length > 0) { - coreWhere.connectedAccount = restConnectedAccount; - } else { - delete coreWhere.connectedAccount; - } - } - } - - return coreWhere; - } - - async getWorkspaceRepository(workspaceId: string) { - return this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'calendarChannel', - ); - } - - async findOne( - workspaceId: string, - options: FindOneOptions, - ): Promise { - if (await this.isMigrated(workspaceId)) { - const where = options.where as Record; - const coreWhere = Array.isArray(where) - ? await Promise.all( - where.map((whereItem: Record) => - this.toCoreWhere(workspaceId, whereItem), - ), - ) - : await this.toCoreWhere(workspaceId, where); - - const requestedRelations = - (options.relations as string[] | undefined)?.slice() ?? []; - - const needsConnectedAccount = - requestedRelations.includes('connectedAccount'); - - const coreRelations = requestedRelations.filter( - (r) => r !== 'connectedAccount', - ); - - const result = await this.coreRepository.findOne({ - ...options, - where: coreWhere, - relations: coreRelations, - } as FindOneOptions); - - if (!result) { - return null; - } - - const workspaceResult = - result as unknown as CalendarChannelWorkspaceEntity; - - if (needsConnectedAccount) { - const connectedAccount = - await this.connectedAccountDataAccessService.findOne(workspaceId, { - where: { id: result.connectedAccountId }, - }); - - if (connectedAccount) { - workspaceResult.connectedAccount = connectedAccount; - } - } - - return workspaceResult; - } - - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - return workspaceRepository.findOne(options); - } - - async find( - workspaceId: string, - whereOrOptions?: - | FindOptionsWhere - | FindManyOptions, - ): Promise { - if ( - whereOrOptions !== undefined && - typeof whereOrOptions === 'object' && - whereOrOptions !== null && - !Array.isArray(whereOrOptions) && - 'where' in whereOrOptions - ) { - const options = - whereOrOptions as FindManyOptions; - - if (await this.isMigrated(workspaceId)) { - const { where } = options; - - const coreWhere = Array.isArray(where) - ? await Promise.all( - where.map((whereItem) => - this.toCoreWhere( - workspaceId, - whereItem as Record, - ), - ), - ) - : await this.toCoreWhere( - workspaceId, - where as Record, - ); - - return this.coreRepository.find({ - ...options, - where: coreWhere, - } as FindManyOptions) as unknown as Promise< - CalendarChannelWorkspaceEntity[] - >; - } - - const workspaceRepository = - await this.getWorkspaceRepository(workspaceId); - - return workspaceRepository.find(options); - } - - const where = whereOrOptions as - | FindOptionsWhere - | undefined; - - if (await this.isMigrated(workspaceId)) { - const coreWhere = where - ? await this.toCoreWhere(workspaceId, where as Record) - : { workspaceId }; - - return this.coreRepository.find({ - where: coreWhere, - } as FindManyOptions) as unknown as Promise< - CalendarChannelWorkspaceEntity[] - >; - } - - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - return workspaceRepository.find({ where }); - } - - async save( - workspaceId: string, - data: Partial, - manager?: WorkspaceEntityManager, - ): Promise { - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - await workspaceRepository.save(data, {}, manager); - - if (await this.isMigrated(workspaceId)) { - try { - await this.coreRepository.save({ - ...data, - workspaceId, - } as unknown as CalendarChannelEntity); - } catch (error) { - this.logger.error( - `Failed to dual-write calendarChannel to core: ${error}`, - ); - throw error; - } - } - } - - async update( - workspaceId: string, - where: FindOptionsWhere, - data: Partial, - ): Promise { - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - await workspaceRepository.update(where, data); - - if (await this.isMigrated(workspaceId)) { - try { - await this.coreRepository.update( - { ...where, workspaceId } as FindOptionsWhere, - data as Record, - ); - } catch (error) { - this.logger.error( - `Failed to dual-write calendarChannel update to core: ${error}`, - ); - throw error; - } - } - } - - async increment( - workspaceId: string, - where: FindOptionsWhere, - propertyPath: string, - value: number, - ): Promise { - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - await workspaceRepository.increment(where, propertyPath, value, undefined, [ - propertyPath, - 'id', - ]); - - if (await this.isMigrated(workspaceId)) { - try { - await this.coreRepository.increment( - { - ...where, - workspaceId, - } as FindOptionsWhere, - propertyPath, - value, - ); - } catch (error) { - this.logger.error( - `Failed to dual-write calendarChannel increment to core: ${error}`, - ); - throw error; - } - } - } - - async delete( - workspaceId: string, - where: FindOptionsWhere, - ): Promise { - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - await workspaceRepository.delete(where); - - if (await this.isMigrated(workspaceId)) { - try { - await this.coreRepository.delete({ - ...where, - workspaceId, - } as FindOptionsWhere); - } catch (error) { - this.logger.error( - `Failed to dual-write calendarChannel delete to core: ${error}`, - ); - throw error; - } - } - } -} diff --git a/packages/twenty-server/src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity.ts b/packages/twenty-server/src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity.ts index 8075d3c5a9..62cb4306f5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity.ts @@ -1,3 +1,5 @@ +import { registerEnumType } from '@nestjs/graphql'; + import { Column, CreateDateColumn, @@ -19,6 +21,19 @@ import { import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { WorkspaceRelatedEntity } from 'src/engine/workspace-manager/types/workspace-related-entity'; +registerEnumType(CalendarChannelVisibility, { + name: 'CalendarChannelVisibility', +}); +registerEnumType(CalendarChannelSyncStatus, { + name: 'CalendarChannelSyncStatus', +}); +registerEnumType(CalendarChannelSyncStage, { + name: 'CalendarChannelSyncStage', +}); +registerEnumType(CalendarChannelContactAutoCreationPolicy, { + name: 'CalendarChannelContactAutoCreationPolicy', +}); + @Entity({ name: 'calendarChannel', schema: 'core' }) export class CalendarChannelEntity extends WorkspaceRelatedEntity { @PrimaryGeneratedColumn('uuid') diff --git a/packages/twenty-server/src/engine/metadata-modules/calendar-channel/resolvers/calendar-channel.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/calendar-channel/resolvers/calendar-channel.resolver.ts index 5726c1e96e..4c36444f49 100644 --- a/packages/twenty-server/src/engine/metadata-modules/calendar-channel/resolvers/calendar-channel.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/calendar-channel/resolvers/calendar-channel.resolver.ts @@ -1,17 +1,11 @@ import { UseGuards, UseInterceptors } from '@nestjs/common'; import { Args, Mutation, Query } from '@nestjs/graphql'; -import { FeatureFlagKey } from 'twenty-shared/types'; - import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; -import { - FeatureFlagGuard, - RequireFeatureFlag, -} from 'src/engine/guards/feature-flag.guard'; import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { CalendarChannelMetadataService } from 'src/engine/metadata-modules/calendar-channel/calendar-channel-metadata.service'; @@ -19,7 +13,7 @@ import { CalendarChannelDTO } from 'src/engine/metadata-modules/calendar-channel import { UpdateCalendarChannelInput } from 'src/engine/metadata-modules/calendar-channel/dtos/update-calendar-channel.input'; import { CalendarChannelGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/calendar-channel/interceptors/calendar-channel-graphql-api-exception.interceptor'; -@UseGuards(WorkspaceAuthGuard, FeatureFlagGuard) +@UseGuards(WorkspaceAuthGuard) @UseInterceptors(CalendarChannelGraphqlApiExceptionInterceptor) @MetadataResolver(() => CalendarChannelDTO) export class CalendarChannelResolver { @@ -29,7 +23,6 @@ export class CalendarChannelResolver { @Query(() => [CalendarChannelDTO]) @UseGuards(NoPermissionGuard) - @RequireFeatureFlag(FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED) async myCalendarChannels( @AuthWorkspace() workspace: WorkspaceEntity, @AuthUserWorkspaceId() userWorkspaceId: string, @@ -57,7 +50,6 @@ export class CalendarChannelResolver { @Mutation(() => CalendarChannelDTO) @UseGuards(NoPermissionGuard) - @RequireFeatureFlag(FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED) async updateCalendarChannel( @Args('input') input: UpdateCalendarChannelInput, @AuthWorkspace() workspace: WorkspaceEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/connected-account/connected-account-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/connected-account/connected-account-metadata.module.ts index c2c9c6389b..eb0138dd4c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/connected-account/connected-account-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/connected-account/connected-account-metadata.module.ts @@ -2,25 +2,27 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; -import { CalendarChannelDataAccessModule } from 'src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module'; -import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { ConnectedAccountMetadataService } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.service'; import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { ConnectedAccountGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/connected-account/interceptors/connected-account-graphql-api-exception.interceptor'; import { ConnectedAccountResolver } from 'src/engine/metadata-modules/connected-account/resolvers/connected-account.resolver'; import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; -import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; +import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module'; @Module({ imports: [ - TypeOrmModule.forFeature([ConnectedAccountEntity]), - CalendarChannelDataAccessModule, - ConnectedAccountDataAccessModule, + TypeOrmModule.forFeature([ + ConnectedAccountEntity, + CalendarChannelEntity, + MessageChannelEntity, + ]), FeatureFlagModule, - WorkspaceManyOrAllFlatEntityMapsCacheModule, - MessageChannelDataAccessModule, PermissionsModule, + WorkspaceEventEmitterModule, + WorkspaceManyOrAllFlatEntityMapsCacheModule, ], providers: [ ConnectedAccountMetadataService, diff --git a/packages/twenty-server/src/engine/metadata-modules/connected-account/connected-account-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/connected-account/connected-account-metadata.service.ts index db048aff17..90e3f29d18 100644 --- a/packages/twenty-server/src/engine/metadata-modules/connected-account/connected-account-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/connected-account/connected-account-metadata.service.ts @@ -11,11 +11,10 @@ import { } from 'src/engine/metadata-modules/connected-account/connected-account.exception'; import { ConnectedAccountDTO } from 'src/engine/metadata-modules/connected-account/dtos/connected-account.dto'; import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; @@ -27,9 +26,10 @@ export class ConnectedAccountMetadataService { constructor( @InjectRepository(ConnectedAccountEntity) private readonly repository: Repository, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, private readonly workspaceEventEmitter: WorkspaceEventEmitter, private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, @@ -164,15 +164,15 @@ export class ConnectedAccountMetadataService { const [messageChannels, calendarChannels] = await Promise.all([ this.globalWorkspaceOrmManager.executeInWorkspaceContext( async () => - this.messageChannelDataAccessService.find(workspaceId, { - connectedAccountId: id, + this.messageChannelRepository.find({ + where: { connectedAccountId: id, workspaceId }, }), authContext, ), this.globalWorkspaceOrmManager.executeInWorkspaceContext( async () => - this.calendarChannelDataAccessService.find(workspaceId, { - connectedAccountId: id, + this.calendarChannelRepository.find({ + where: { connectedAccountId: id, workspaceId }, }), authContext, ), @@ -182,6 +182,13 @@ export class ConnectedAccountMetadataService { `WorkspaceId: ${workspaceId} Deleting connected account ${id} with ${messageChannels.length} message channel(s) and ${calendarChannels.length} calendar channel(s)`, ); + await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + await this.repository.delete({ + id, + workspaceId, + }); + }, authContext); + const { flatObjectMetadataMaps } = await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( { @@ -234,12 +241,6 @@ export class ConnectedAccountMetadataService { }); } - await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.connectedAccountDataAccessService.delete(workspaceId, { - id, - }); - }, authContext); - return connectedAccount; } } diff --git a/packages/twenty-server/src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module.ts b/packages/twenty-server/src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module.ts deleted file mode 100644 index 3426b62fbf..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; -import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; -import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([ConnectedAccountEntity, UserWorkspaceEntity]), - FeatureFlagModule, - ], - providers: [ConnectedAccountDataAccessService], - exports: [ConnectedAccountDataAccessService], -}) -export class ConnectedAccountDataAccessModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service.ts b/packages/twenty-server/src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service.ts deleted file mode 100644 index c2764f6ff0..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import { isNonEmptyString } from '@sniptt/guards'; - -import { FeatureFlagKey } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; -import { - type FindOneOptions, - type FindOptionsWhere, - In, - Repository, -} from 'typeorm'; - -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; -import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; -import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; -import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; -import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; - -@Injectable() -export class ConnectedAccountDataAccessService { - private readonly logger = new Logger(ConnectedAccountDataAccessService.name); - - constructor( - @InjectRepository(ConnectedAccountEntity) - private readonly coreRepository: Repository, - @InjectRepository(UserWorkspaceEntity) - private readonly userWorkspaceRepository: Repository, - private readonly featureFlagService: FeatureFlagService, - private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - ) {} - - private async isMigrated(workspaceId: string): Promise { - return this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - workspaceId, - ); - } - - private async resolveUserWorkspaceId( - workspaceId: string, - workspaceMemberId: string, - ): Promise { - const workspaceMemberRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workspaceMember', - ); - - const workspaceMember = await workspaceMemberRepository.findOne({ - where: { id: workspaceMemberId }, - }); - - if (!workspaceMember) { - return null; - } - - const userWorkspace = await this.userWorkspaceRepository.findOne({ - where: { userId: workspaceMember.userId, workspaceId }, - }); - - return userWorkspace?.id ?? null; - } - - private async toCore( - workspaceId: string, - data: Partial, - ): Promise> { - const { - handleAliases, - lastSyncHistoryId: _lastSyncHistoryId, - accountOwnerId, - ...rest - } = data as Record; - - const coreData: Record = { ...rest }; - - if (handleAliases !== undefined) { - if (Array.isArray(handleAliases)) { - coreData.handleAliases = handleAliases; - } else if (isNonEmptyString(handleAliases)) { - coreData.handleAliases = handleAliases - .split(',') - .map((alias: string) => alias.trim()); - } else { - coreData.handleAliases = null; - } - } - - if (accountOwnerId !== undefined) { - const userWorkspaceId = await this.resolveUserWorkspaceId( - workspaceId, - accountOwnerId as string, - ); - - if (!userWorkspaceId) { - this.logger.warn( - `Could not resolve userWorkspaceId for workspaceMember ${accountOwnerId}`, - ); - } - - coreData.userWorkspaceId = userWorkspaceId; - } - - return coreData as Partial; - } - - private async toCoreWhere( - workspaceId: string, - where: Record, - ): Promise> { - const { accountOwnerId, ...rest } = where; - const coreWhere: Record = { ...rest, workspaceId }; - - if (accountOwnerId !== undefined) { - const userWorkspaceId = await this.resolveUserWorkspaceId( - workspaceId, - accountOwnerId as string, - ); - - if (userWorkspaceId) { - coreWhere.userWorkspaceId = userWorkspaceId; - } else { - this.logger.warn( - `toCoreWhere: could not resolve userWorkspaceId for workspaceMember ${accountOwnerId}, returning empty result`, - ); - coreWhere.id = '00000000-0000-0000-0000-000000000000'; - } - } - - return coreWhere as FindOptionsWhere; - } - - private async fromCoreEntities( - workspaceId: string, - entities: ConnectedAccountEntity[], - ): Promise { - if (entities.length === 0) { - return []; - } - - const userWorkspaceIds = entities - .map((entity) => entity.userWorkspaceId) - .filter(isDefined); - - const userWorkspaces = - userWorkspaceIds.length > 0 - ? await this.userWorkspaceRepository.find({ - where: { id: In(userWorkspaceIds) }, - select: ['id', 'userId'], - }) - : []; - - const userIdByUserWorkspaceId = new Map( - userWorkspaces.map((userWorkspace) => [ - userWorkspace.id, - userWorkspace.userId, - ]), - ); - - const uniqueUserIds = [ - ...new Set(userWorkspaces.map((userWorkspace) => userWorkspace.userId)), - ]; - - const workspaceMemberRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workspaceMember', - ); - - const workspaceMembers = - uniqueUserIds.length > 0 - ? await workspaceMemberRepository.find({ - where: { userId: In(uniqueUserIds) }, - }) - : []; - - const workspaceMemberIdByUserId = new Map( - workspaceMembers.map((workspaceMember) => [ - workspaceMember.userId, - workspaceMember.id, - ]), - ); - - return entities.map((entity) => { - const userId = entity.userWorkspaceId - ? userIdByUserWorkspaceId.get(entity.userWorkspaceId) - : undefined; - - const accountOwnerId = userId - ? workspaceMemberIdByUserId.get(userId) - : undefined; - - const handleAliases = Array.isArray(entity.handleAliases) - ? entity.handleAliases.join(',') - : (entity.handleAliases ?? ''); - - return { - ...entity, - handleAliases, - accountOwnerId: accountOwnerId ?? null, - } as unknown as ConnectedAccountWorkspaceEntity; - }); - } - - async getWorkspaceRepository(workspaceId: string) { - return this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'connectedAccount', - ); - } - - async findOne( - workspaceId: string, - options: FindOneOptions, - ): Promise { - if (await this.isMigrated(workspaceId)) { - const where = options.where as Record; - const coreWhere = Array.isArray(where) - ? await Promise.all( - where.map((whereItem: Record) => - this.toCoreWhere(workspaceId, whereItem), - ), - ) - : await this.toCoreWhere(workspaceId, where); - - const coreResult = await this.coreRepository.findOne({ - ...options, - where: coreWhere, - } as FindOneOptions); - - if (!coreResult) { - return null; - } - - const [transformed] = await this.fromCoreEntities(workspaceId, [ - coreResult, - ]); - - return transformed ?? null; - } - - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - return workspaceRepository.findOne(options); - } - - async find( - workspaceId: string, - where?: FindOptionsWhere, - ): Promise { - if (await this.isMigrated(workspaceId)) { - const coreWhere = where - ? await this.toCoreWhere(workspaceId, where as Record) - : { workspaceId }; - - const coreResults = await this.coreRepository.find({ - where: coreWhere, - }); - - return this.fromCoreEntities(workspaceId, coreResults); - } - - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - return workspaceRepository.find({ where }); - } - - async save( - workspaceId: string, - data: Partial, - manager?: WorkspaceEntityManager, - ): Promise { - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - await workspaceRepository.save(data, {}, manager); - - if (await this.isMigrated(workspaceId)) { - try { - const coreData = await this.toCore(workspaceId, data); - - await this.coreRepository.save({ - ...coreData, - workspaceId, - } as ConnectedAccountEntity); - } catch (error) { - this.logger.error( - `Failed to dual-write connectedAccount to core: ${error}`, - ); - throw error; - } - } - } - - async update( - workspaceId: string, - where: FindOptionsWhere, - data: Partial, - ): Promise { - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - await workspaceRepository.update(where, data); - - if (await this.isMigrated(workspaceId)) { - try { - const coreData = await this.toCore(workspaceId, data); - const coreWhere = await this.toCoreWhere( - workspaceId, - where as Record, - ); - - await this.coreRepository.update( - coreWhere, - coreData as Record, - ); - } catch (error) { - this.logger.error( - `Failed to dual-write connectedAccount update to core: ${error}`, - ); - throw error; - } - } - } - - async delete( - workspaceId: string, - where: FindOptionsWhere, - ): Promise { - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - await workspaceRepository.delete(where); - - if (await this.isMigrated(workspaceId)) { - try { - const coreWhere = await this.toCoreWhere( - workspaceId, - where as Record, - ); - - await this.coreRepository.delete(coreWhere); - } catch (error) { - this.logger.error( - `Failed to dual-write connectedAccount delete to core: ${error}`, - ); - throw error; - } - } - } -} diff --git a/packages/twenty-server/src/engine/metadata-modules/connected-account/entities/connected-account.entity.ts b/packages/twenty-server/src/engine/metadata-modules/connected-account/entities/connected-account.entity.ts index 17b27697a5..1343d6017f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/connected-account/entities/connected-account.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/connected-account/entities/connected-account.entity.ts @@ -8,6 +8,9 @@ import { UpdateDateColumn, } from 'typeorm'; +import { type ConnectedAccountProvider } from 'twenty-shared/types'; + +import { type ImapSmtpCaldavParams } from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type'; import { type CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { WorkspaceRelatedEntity } from 'src/engine/workspace-manager/types/workspace-related-entity'; @@ -21,7 +24,7 @@ export class ConnectedAccountEntity extends WorkspaceRelatedEntity { handle: string; @Column({ type: 'varchar', nullable: false }) - provider: string; + provider: ConnectedAccountProvider; @Column({ type: 'varchar', nullable: true }) accessToken: string | null; @@ -42,7 +45,7 @@ export class ConnectedAccountEntity extends WorkspaceRelatedEntity { scopes: string[] | null; @Column({ type: 'jsonb', nullable: true }) - connectionParameters: Record | null; + connectionParameters: ImapSmtpCaldavParams | null; @Column({ type: 'timestamptz', nullable: true }) lastSignedInAt: Date | null; diff --git a/packages/twenty-server/src/engine/metadata-modules/connected-account/resolvers/connected-account.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/connected-account/resolvers/connected-account.resolver.ts index edb9c84f0c..5cbd527a2a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/connected-account/resolvers/connected-account.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/connected-account/resolvers/connected-account.resolver.ts @@ -2,17 +2,12 @@ import { UseGuards, UseInterceptors } from '@nestjs/common'; import { Args, Mutation, Query } from '@nestjs/graphql'; import { PermissionFlagType } from 'twenty-shared/constants'; -import { FeatureFlagKey } from 'twenty-shared/types'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; -import { - FeatureFlagGuard, - RequireFeatureFlag, -} from 'src/engine/guards/feature-flag.guard'; import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard'; import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; @@ -20,7 +15,7 @@ import { ConnectedAccountMetadataService } from 'src/engine/metadata-modules/con import { ConnectedAccountDTO } from 'src/engine/metadata-modules/connected-account/dtos/connected-account.dto'; import { ConnectedAccountGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/connected-account/interceptors/connected-account-graphql-api-exception.interceptor'; -@UseGuards(WorkspaceAuthGuard, FeatureFlagGuard) +@UseGuards(WorkspaceAuthGuard) @UseInterceptors(ConnectedAccountGraphqlApiExceptionInterceptor) @MetadataResolver(() => ConnectedAccountDTO) export class ConnectedAccountResolver { @@ -30,7 +25,6 @@ export class ConnectedAccountResolver { @Query(() => [ConnectedAccountDTO]) @UseGuards(NoPermissionGuard) - @RequireFeatureFlag(FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED) async myConnectedAccounts( @AuthWorkspace() workspace: WorkspaceEntity, @AuthUserWorkspaceId() userWorkspaceId: string, @@ -43,7 +37,6 @@ export class ConnectedAccountResolver { @Query(() => [ConnectedAccountDTO]) @UseGuards(SettingsPermissionGuard(PermissionFlagType.CONNECTED_ACCOUNTS)) - @RequireFeatureFlag(FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED) async connectedAccounts( @AuthWorkspace() workspace: WorkspaceEntity, ): Promise { @@ -52,7 +45,6 @@ export class ConnectedAccountResolver { @Mutation(() => ConnectedAccountDTO) @UseGuards(NoPermissionGuard) - @RequireFeatureFlag(FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED) async deleteConnectedAccount( @Args('id', { type: () => UUIDScalarType }) id: string, @AuthWorkspace() workspace: WorkspaceEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module.ts b/packages/twenty-server/src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module.ts deleted file mode 100644 index 75b7e3dcd6..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; -import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; -import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; -import { MessageFolderDataAccessModule } from 'src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([MessageChannelEntity]), - FeatureFlagModule, - ConnectedAccountDataAccessModule, - MessageFolderDataAccessModule, - ], - providers: [MessageChannelDataAccessService], - exports: [MessageChannelDataAccessService], -}) -export class MessageChannelDataAccessModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service.ts b/packages/twenty-server/src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service.ts deleted file mode 100644 index 1dd99c1f1c..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service.ts +++ /dev/null @@ -1,327 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import { FeatureFlagKey } from 'twenty-shared/types'; -import { - type FindManyOptions, - type FindOneOptions, - type FindOptionsWhere, - Repository, -} from 'typeorm'; - -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; -import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; -import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service'; -import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; -import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; - -@Injectable() -export class MessageChannelDataAccessService { - private readonly logger = new Logger(MessageChannelDataAccessService.name); - - constructor( - @InjectRepository(MessageChannelEntity) - private readonly coreRepository: Repository, - private readonly featureFlagService: FeatureFlagService, - private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, - private readonly messageFolderDataAccessService: MessageFolderDataAccessService, - ) {} - - private async isMigrated(workspaceId: string): Promise { - return this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - workspaceId, - ); - } - - private async toCoreWhere( - workspaceId: string, - where: Record, - ): Promise> { - const coreWhere: Record = { ...where, workspaceId }; - - if ( - coreWhere.connectedAccount && - typeof coreWhere.connectedAccount === 'object' - ) { - const connectedAccountWhere = { - ...(coreWhere.connectedAccount as Record), - }; - - if ('accountOwnerId' in connectedAccountWhere) { - const { accountOwnerId, ...restConnectedAccount } = - connectedAccountWhere; - - const resolvedConnectedAccounts = - await this.connectedAccountDataAccessService.find(workspaceId, { - accountOwnerId, - } as never); - - if (resolvedConnectedAccounts.length > 0) { - coreWhere.connectedAccountId = resolvedConnectedAccounts[0].id; - } else { - coreWhere.connectedAccountId = '00000000-0000-0000-0000-000000000000'; - } - - if (Object.keys(restConnectedAccount).length > 0) { - coreWhere.connectedAccount = restConnectedAccount; - } else { - delete coreWhere.connectedAccount; - } - } - } - - return coreWhere; - } - - async getWorkspaceRepository(workspaceId: string) { - return this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageChannel', - ); - } - - async findOne( - workspaceId: string, - options: FindOneOptions, - ): Promise { - if (await this.isMigrated(workspaceId)) { - const where = options.where as Record; - const coreWhere = Array.isArray(where) - ? await Promise.all( - where.map((whereItem: Record) => - this.toCoreWhere(workspaceId, whereItem), - ), - ) - : await this.toCoreWhere(workspaceId, where); - - const requestedRelations = - (options.relations as string[] | undefined)?.slice() ?? []; - - const needsConnectedAccount = - requestedRelations.includes('connectedAccount'); - - const needsMessageFolders = requestedRelations.includes('messageFolders'); - - const coreRelations = requestedRelations.filter( - (r) => r !== 'connectedAccount' && r !== 'messageFolders', - ); - - const result = await this.coreRepository.findOne({ - ...options, - where: coreWhere, - relations: coreRelations, - } as FindOneOptions); - - if (!result) { - return null; - } - - const workspaceResult = - result as unknown as MessageChannelWorkspaceEntity; - - if (needsConnectedAccount) { - const connectedAccount = - await this.connectedAccountDataAccessService.findOne(workspaceId, { - where: { id: result.connectedAccountId }, - }); - - if (connectedAccount) { - workspaceResult.connectedAccount = connectedAccount; - } - } - - if (needsMessageFolders) { - const messageFolders = await this.messageFolderDataAccessService.find( - workspaceId, - { - messageChannelId: result.id, - }, - ); - - workspaceResult.messageFolders = - messageFolders as unknown as MessageChannelWorkspaceEntity['messageFolders']; - } - - return workspaceResult; - } - - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - return workspaceRepository.findOne(options); - } - - async find( - workspaceId: string, - where?: FindOptionsWhere, - ): Promise { - if (await this.isMigrated(workspaceId)) { - return this.coreRepository.find({ - where: { - ...(where as Record), - workspaceId, - } as FindOptionsWhere, - }) as unknown as Promise; - } - - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - return workspaceRepository.find({ where }); - } - - async findMany( - workspaceId: string, - options: FindManyOptions, - ): Promise { - if (await this.isMigrated(workspaceId)) { - const baseWhere = options.where; - - if (!baseWhere) { - return this.coreRepository.find({ - ...options, - where: { workspaceId }, - } as FindManyOptions) as unknown as Promise< - MessageChannelWorkspaceEntity[] - >; - } - - if (Array.isArray(baseWhere)) { - const coreWhereArray = await Promise.all( - baseWhere.map((whereItem) => - this.toCoreWhere(workspaceId, whereItem as Record), - ), - ); - - return this.coreRepository.find({ - ...options, - where: coreWhereArray, - } as FindManyOptions) as unknown as Promise< - MessageChannelWorkspaceEntity[] - >; - } - - const coreWhere = await this.toCoreWhere( - workspaceId, - baseWhere as Record, - ); - - return this.coreRepository.find({ - ...options, - where: coreWhere, - } as FindManyOptions) as unknown as Promise< - MessageChannelWorkspaceEntity[] - >; - } - - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - return workspaceRepository.find(options); - } - - async save( - workspaceId: string, - data: Partial, - manager?: WorkspaceEntityManager, - ): Promise { - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - await workspaceRepository.save(data, {}, manager); - - if (await this.isMigrated(workspaceId)) { - try { - await this.coreRepository.save({ - ...data, - workspaceId, - } as unknown as MessageChannelEntity); - } catch (error) { - this.logger.error( - `Failed to dual-write messageChannel to core: ${error}`, - ); - throw error; - } - } - } - - async update( - workspaceId: string, - where: FindOptionsWhere, - data: Partial, - manager?: WorkspaceEntityManager, - ): Promise { - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - await workspaceRepository.update(where, data, manager); - - if (await this.isMigrated(workspaceId)) { - try { - await this.coreRepository.update( - { ...where, workspaceId } as FindOptionsWhere, - data as never, - ); - } catch (error) { - this.logger.error( - `Failed to dual-write messageChannel update to core: ${error}`, - ); - throw error; - } - } - } - - async increment( - workspaceId: string, - where: FindOptionsWhere, - propertyPath: string, - value: number, - ): Promise { - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - await workspaceRepository.increment(where, propertyPath, value, undefined, [ - propertyPath, - 'id', - ]); - - if (await this.isMigrated(workspaceId)) { - try { - await this.coreRepository.increment( - { - ...where, - workspaceId, - } as FindOptionsWhere, - propertyPath, - value, - ); - } catch (error) { - this.logger.error( - `Failed to dual-write messageChannel increment to core: ${error}`, - ); - throw error; - } - } - } - - async delete( - workspaceId: string, - where: FindOptionsWhere, - ): Promise { - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - await workspaceRepository.delete(where); - - if (await this.isMigrated(workspaceId)) { - try { - await this.coreRepository.delete({ - ...where, - workspaceId, - } as FindOptionsWhere); - } catch (error) { - this.logger.error( - `Failed to dual-write messageChannel delete to core: ${error}`, - ); - throw error; - } - } - } -} diff --git a/packages/twenty-server/src/engine/metadata-modules/message-channel/entities/message-channel.entity.ts b/packages/twenty-server/src/engine/metadata-modules/message-channel/entities/message-channel.entity.ts index 50fb52e2ca..bebf10f42a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/message-channel/entities/message-channel.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/message-channel/entities/message-channel.entity.ts @@ -1,3 +1,5 @@ +import { registerEnumType } from '@nestjs/graphql'; + import { Column, CreateDateColumn, @@ -24,6 +26,26 @@ import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-ac import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { WorkspaceRelatedEntity } from 'src/engine/workspace-manager/types/workspace-related-entity'; +registerEnumType(MessageChannelVisibility, { + name: 'MessageChannelVisibility', +}); +registerEnumType(MessageChannelSyncStatus, { + name: 'MessageChannelSyncStatus', +}); +registerEnumType(MessageChannelSyncStage, { + name: 'MessageChannelSyncStage', +}); +registerEnumType(MessageChannelType, { name: 'MessageChannelType' }); +registerEnumType(MessageChannelContactAutoCreationPolicy, { + name: 'MessageChannelContactAutoCreationPolicy', +}); +registerEnumType(MessageFolderImportPolicy, { + name: 'MessageFolderImportPolicy', +}); +registerEnumType(MessageChannelPendingGroupEmailsAction, { + name: 'MessageChannelPendingGroupEmailsAction', +}); + @Entity({ name: 'messageChannel', schema: 'core' }) export class MessageChannelEntity extends WorkspaceRelatedEntity { @PrimaryGeneratedColumn('uuid') diff --git a/packages/twenty-server/src/engine/metadata-modules/message-channel/message-channel-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/message-channel/message-channel-metadata.module.ts index 59129a0ca7..c5615d77d9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/message-channel/message-channel-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/message-channel/message-channel-metadata.module.ts @@ -1,23 +1,20 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { ConnectedAccountMetadataModule } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.module'; import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { MessageChannelGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/message-channel/interceptors/message-channel-graphql-api-exception.interceptor'; import { MessageChannelMetadataService } from 'src/engine/metadata-modules/message-channel/message-channel-metadata.service'; import { MessageChannelResolver } from 'src/engine/metadata-modules/message-channel/resolvers/message-channel.resolver'; -import { MessageFolderDataAccessModule } from 'src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module'; +import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { MessagingImportManagerModule } from 'src/modules/messaging/message-import-manager/messaging-import-manager.module'; @Module({ imports: [ - TypeOrmModule.forFeature([MessageChannelEntity]), + TypeOrmModule.forFeature([MessageChannelEntity, MessageFolderEntity]), PermissionsModule, - FeatureFlagModule, ConnectedAccountMetadataModule, - MessageFolderDataAccessModule, MessagingImportManagerModule, ], providers: [ diff --git a/packages/twenty-server/src/engine/metadata-modules/message-channel/resolvers/message-channel.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/message-channel/resolvers/message-channel.resolver.ts index 6cd491b409..daf9070112 100644 --- a/packages/twenty-server/src/engine/metadata-modules/message-channel/resolvers/message-channel.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/message-channel/resolvers/message-channel.resolver.ts @@ -1,18 +1,22 @@ import { UseGuards, UseInterceptors } from '@nestjs/common'; import { Args, Mutation, Query } from '@nestjs/graphql'; +import { InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; -import { FeatureFlagKey } from 'twenty-shared/types'; +import { Not, Repository } from 'typeorm'; + +import { + MessageChannelPendingGroupEmailsAction, + MessageChannelSyncStage, + MessageFolderPendingSyncAction, +} from 'twenty-shared/types'; +import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; -import { - FeatureFlagGuard, - RequireFeatureFlag, -} from 'src/engine/guards/feature-flag.guard'; import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { MessageChannelDTO } from 'src/engine/metadata-modules/message-channel/dtos/message-channel.dto'; @@ -23,29 +27,22 @@ import { } from 'src/engine/metadata-modules/message-channel/message-channel.exception'; import { MessageChannelGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/message-channel/interceptors/message-channel-graphql-api-exception.interceptor'; import { MessageChannelMetadataService } from 'src/engine/metadata-modules/message-channel/message-channel-metadata.service'; -import { - MessageChannelPendingGroupEmailsAction, - MessageChannelSyncStage, - type MessageChannelWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; -import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service'; +import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { MessagingProcessGroupEmailActionsService } from 'src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service'; -import { Not } from 'typeorm'; -@UseGuards(WorkspaceAuthGuard, FeatureFlagGuard) +@UseGuards(WorkspaceAuthGuard) @UseInterceptors(MessageChannelGraphqlApiExceptionInterceptor) @MetadataResolver(() => MessageChannelDTO) export class MessageChannelResolver { constructor( private readonly messageChannelMetadataService: MessageChannelMetadataService, - private readonly messageFolderDataAccessService: MessageFolderDataAccessService, + @InjectRepository(MessageFolderEntity) + private readonly messageFolderRepository: Repository, private readonly messagingProcessGroupEmailActionsService: MessagingProcessGroupEmailActionsService, ) {} @Query(() => [MessageChannelDTO]) @UseGuards(NoPermissionGuard) - @RequireFeatureFlag(FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED) async myMessageChannels( @AuthWorkspace() workspace: WorkspaceEntity, @AuthUserWorkspaceId() userWorkspaceId: string, @@ -73,7 +70,6 @@ export class MessageChannelResolver { @Mutation(() => MessageChannelDTO) @UseGuards(NoPermissionGuard) - @RequireFeatureFlag(FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED) async updateMessageChannel( @Args('input') input: UpdateMessageChannelInput, @AuthWorkspace() workspace: WorkspaceEntity, @@ -90,11 +86,13 @@ export class MessageChannelResolver { messageChannel.syncStage === MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING; - const foldersWithPendingAction = - await this.messageFolderDataAccessService.find(workspace.id, { + const foldersWithPendingAction = await this.messageFolderRepository.find({ + where: { messageChannelId: messageChannel.id, pendingSyncAction: Not(MessageFolderPendingSyncAction.NONE), - }); + workspaceId: workspace.id, + }, + }); const hasPendingGroupEmailsAction = messageChannel.pendingGroupEmailsAction !== @@ -118,7 +116,7 @@ export class MessageChannelResolver { ) { // Service expects WorkspaceEntity type but only reads .id await this.messagingProcessGroupEmailActionsService.markMessageChannelAsPendingGroupEmailsAction( - messageChannel as unknown as MessageChannelWorkspaceEntity, + messageChannel as unknown as MessageChannelEntity, workspace.id, input.update.excludeGroupEmails ? MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_DELETION diff --git a/packages/twenty-server/src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module.ts b/packages/twenty-server/src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module.ts deleted file mode 100644 index a950922ff5..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; -import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service'; -import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; - -@Module({ - imports: [TypeOrmModule.forFeature([MessageFolderEntity]), FeatureFlagModule], - providers: [MessageFolderDataAccessService], - exports: [MessageFolderDataAccessService], -}) -export class MessageFolderDataAccessModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service.ts b/packages/twenty-server/src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service.ts deleted file mode 100644 index fb716632e0..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import { isNonEmptyString } from '@sniptt/guards'; -import { FeatureFlagKey } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; -import { validate as uuidValidate } from 'uuid'; -import { - type FindOneOptions, - type FindOptionsWhere, - Repository, -} from 'typeorm'; - -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; -import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; -import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; -import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; - -@Injectable() -export class MessageFolderDataAccessService { - private readonly logger = new Logger(MessageFolderDataAccessService.name); - - constructor( - @InjectRepository(MessageFolderEntity) - private readonly coreRepository: Repository, - private readonly featureFlagService: FeatureFlagService, - private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - ) {} - - private async isMigrated(workspaceId: string): Promise { - return this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - workspaceId, - ); - } - - private async resolveParentFolderIdForCore( - workspaceId: string, - parentFolderId: string | null, - messageChannelId: string | undefined, - ): Promise { - if (!isNonEmptyString(parentFolderId)) { - return null; - } - - if (uuidValidate(parentFolderId)) { - return parentFolderId; - } - - if (!isDefined(messageChannelId)) { - return null; - } - - const parentFolder = await this.coreRepository.findOne({ - where: { - workspaceId, - messageChannelId, - externalId: parentFolderId, - }, - select: ['id'], - }); - - return parentFolder?.id ?? null; - } - - private async toCore( - workspaceId: string, - data: Partial, - messageChannelId?: string, - ): Promise> { - const coreData: Record = { ...data, workspaceId }; - - const channelId = (coreData.messageChannelId as string) ?? messageChannelId; - - if ('parentFolderId' in coreData) { - coreData.parentFolderId = await this.resolveParentFolderIdForCore( - workspaceId, - coreData.parentFolderId as string | null, - channelId, - ); - } - - return coreData; - } - - async getWorkspaceRepository(workspaceId: string) { - return this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'messageFolder', - ); - } - - async findOne( - workspaceId: string, - options: FindOneOptions, - ): Promise { - if (await this.isMigrated(workspaceId)) { - const where = options.where as Record; - const coreWhere = Array.isArray(where) - ? where.map((whereItem) => ({ - ...(whereItem as Record), - workspaceId, - })) - : { - ...(where as Record), - workspaceId, - }; - - return this.coreRepository.findOne({ - ...options, - where: coreWhere, - } as FindOneOptions) as unknown as Promise; - } - - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - return workspaceRepository.findOne(options); - } - - async find( - workspaceId: string, - where?: FindOptionsWhere, - ): Promise { - if (await this.isMigrated(workspaceId)) { - return this.coreRepository.find({ - where: { - ...(where as Record), - workspaceId, - } as FindOptionsWhere, - }) as unknown as Promise; - } - - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - return workspaceRepository.find({ where }); - } - - async save( - workspaceId: string, - data: Partial, - ): Promise { - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - const savedData = await workspaceRepository.save(data); - - if (await this.isMigrated(workspaceId)) { - try { - const coreData = await this.toCore(workspaceId, savedData); - - await this.coreRepository.save( - coreData as unknown as MessageFolderEntity, - ); - } catch (error) { - this.logger.error( - `Failed to dual-write messageFolder to core: ${error}`, - ); - throw error; - } - } - } - - async update( - workspaceId: string, - where: FindOptionsWhere, - data: Partial, - manager?: WorkspaceEntityManager, - ): Promise { - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - await workspaceRepository.update(where, data, manager); - - if (await this.isMigrated(workspaceId)) { - try { - const coreData = await this.toCore( - workspaceId, - data, - (where as Record).messageChannelId as string, - ); - - await this.coreRepository.update( - { ...where, workspaceId } as FindOptionsWhere, - coreData, - ); - } catch (error) { - this.logger.error( - `Failed to dual-write messageFolder update to core: ${error}`, - ); - throw error; - } - } - } - - async delete( - workspaceId: string, - where: FindOptionsWhere, - ): Promise { - const workspaceRepository = await this.getWorkspaceRepository(workspaceId); - - await workspaceRepository.delete(where); - - if (await this.isMigrated(workspaceId)) { - try { - await this.coreRepository.delete({ - ...where, - workspaceId, - } as FindOptionsWhere); - } catch (error) { - this.logger.error( - `Failed to dual-write messageFolder delete to core: ${error}`, - ); - throw error; - } - } - } -} diff --git a/packages/twenty-server/src/engine/metadata-modules/message-folder/dtos/create-message-folder.input.ts b/packages/twenty-server/src/engine/metadata-modules/message-folder/dtos/create-message-folder.input.ts index 2f1bf605dc..a81a2d2b66 100644 --- a/packages/twenty-server/src/engine/metadata-modules/message-folder/dtos/create-message-folder.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/message-folder/dtos/create-message-folder.input.ts @@ -49,8 +49,8 @@ export class CreateMessageFolderInput { @Field(() => UUIDScalarType) messageChannelId: string; - @IsUUID() + @IsString() @IsOptional() - @Field(() => UUIDScalarType, { nullable: true }) + @Field(() => String, { nullable: true }) parentFolderId?: string; } diff --git a/packages/twenty-server/src/engine/metadata-modules/message-folder/dtos/message-folder.dto.ts b/packages/twenty-server/src/engine/metadata-modules/message-folder/dtos/message-folder.dto.ts index 3cdf9b56ef..3ff783d2a6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/message-folder/dtos/message-folder.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/message-folder/dtos/message-folder.dto.ts @@ -36,9 +36,9 @@ export class MessageFolderDTO { @Field() isSynced: boolean; - @IsUUID() + @IsString() @IsOptional() - @Field(() => UUIDScalarType, { nullable: true }) + @Field(() => String, { nullable: true }) parentFolderId: string | null; @IsString() diff --git a/packages/twenty-server/src/engine/metadata-modules/message-folder/resolvers/message-folder.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/message-folder/resolvers/message-folder.resolver.ts index 9588bffbee..e0660cc6f6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/message-folder/resolvers/message-folder.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/message-folder/resolvers/message-folder.resolver.ts @@ -1,17 +1,11 @@ import { UseGuards, UseInterceptors } from '@nestjs/common'; import { Args, Mutation, Query } from '@nestjs/graphql'; -import { FeatureFlagKey } from 'twenty-shared/types'; - import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; -import { - FeatureFlagGuard, - RequireFeatureFlag, -} from 'src/engine/guards/feature-flag.guard'; import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { MessageFolderDTO } from 'src/engine/metadata-modules/message-folder/dtos/message-folder.dto'; @@ -22,7 +16,7 @@ import { import { MessageFolderGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/message-folder/interceptors/message-folder-graphql-api-exception.interceptor'; import { MessageFolderMetadataService } from 'src/engine/metadata-modules/message-folder/message-folder-metadata.service'; -@UseGuards(WorkspaceAuthGuard, FeatureFlagGuard) +@UseGuards(WorkspaceAuthGuard) @UseInterceptors(MessageFolderGraphqlApiExceptionInterceptor) @MetadataResolver(() => MessageFolderDTO) export class MessageFolderResolver { @@ -32,7 +26,6 @@ export class MessageFolderResolver { @Query(() => [MessageFolderDTO]) @UseGuards(NoPermissionGuard) - @RequireFeatureFlag(FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED) async myMessageFolders( @AuthWorkspace() workspace: WorkspaceEntity, @AuthUserWorkspaceId() userWorkspaceId: string, @@ -58,7 +51,6 @@ export class MessageFolderResolver { @Mutation(() => MessageFolderDTO) @UseGuards(NoPermissionGuard) - @RequireFeatureFlag(FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED) async updateMessageFolder( @Args('input') input: UpdateMessageFolderInput, @AuthWorkspace() workspace: WorkspaceEntity, @@ -79,7 +71,6 @@ export class MessageFolderResolver { @Mutation(() => [MessageFolderDTO]) @UseGuards(NoPermissionGuard) - @RequireFeatureFlag(FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED) async updateMessageFolders( @Args('input') input: UpdateMessageFoldersInput, @AuthWorkspace() workspace: WorkspaceEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts index 8a3166319f..9fb0566c86 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts @@ -12,7 +12,6 @@ import { ApplicationException, ApplicationExceptionCode, } from 'src/engine/core-modules/application/application.exception'; -import { isApiKeyAuthContext } from 'src/engine/core-modules/auth/guards/is-api-key-auth-context.guard'; import { TOOL_PERMISSION_FLAGS } from 'src/engine/metadata-modules/permissions/constants/tool-permission-flags'; import { PermissionsException, diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts index 46a5409053..0fc822de24 100644 --- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts +++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts @@ -243,7 +243,6 @@ describe('WorkspaceEntityManager', () => { IS_USAGE_ANALYTICS_ENABLED: false, IS_RICH_TEXT_V1_MIGRATED: false, IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED: false, - IS_CONNECTED_ACCOUNT_MIGRATED: false, IS_RECORD_TABLE_WIDGET_ENABLED: false, IS_DATASOURCE_MIGRATED: false, IS_COMMAND_MENU_ITEM_ENABLED: false, diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/apply-row-level-permission-predicates.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/apply-row-level-permission-predicates.util.ts index 9053431d12..6489fb0982 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/apply-row-level-permission-predicates.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/apply-row-level-permission-predicates.util.ts @@ -30,7 +30,7 @@ export const applyRowLevelPermissionPredicates = ({ objectMetadata, internalContext, authContext, - featureFlagMap, + featureFlagMap: _featureFlagMap, }: ApplyRowLevelPermissionPredicatesArgs): void => { const userWorkspaceId = isUserAuthContext(authContext) ? authContext.userWorkspaceId diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-metadata-entities.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-metadata-entities.util.ts index 8f45bb2b80..076a4271ef 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-metadata-entities.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-metadata-entities.util.ts @@ -24,6 +24,84 @@ type SeedMetadataEntitiesArgs = { workspaceId: string; }; +// YC workspace needs its own entity IDs since core tables have a single PK +const YC_CONNECTED_ACCOUNT_IDS = { + TIM: '30303030-9ac0-4390-9a1a-ab4d2c4e1bb7', + JONY: '30303030-0cc8-4d60-a3a4-803245698908', + PHIL: '30303030-cafc-4323-908d-e5b42ad69fdf', + JANE: '30303030-b5c7-46f0-bf5c-3f4e4b3f7c1a', + JANE_DELETABLE: '30303030-d1e5-4a8f-9c3b-7f6d5e4c3b2a', +}; + +const YC_MESSAGE_CHANNEL_IDS = { + TIM: '30303030-9b80-4c2c-a597-383db48de1d6', + JONY: '30303030-5ffe-4b32-814a-983d5e4911cd', + PHIL: '30303030-e2f1-49b5-85d2-5d3a3386990c', + JANE: '30303030-8c4d-4e71-a672-2e6a8c9f1b3d', + SUPPORT: '30303030-e2f1-49b5-85d2-5d3a3386990d', + SALES: '30303030-e2f1-49b5-85d2-5d3a3386990e', +}; + +const YC_CALENDAR_CHANNEL_IDS = { + TIM: '30303030-a40f-4faf-bb9f-c6f9945b8203', + JONY: '30303030-a40f-4faf-bb9f-c6f9945b8204', + PHIL: '30303030-a40f-4faf-bb9f-c6f9945b8205', + JANE: '30303030-a40f-4faf-bb9f-c6f9945b8208', + COMPANY_MAIN: '30303030-a40f-4faf-bb9f-c6f9945b8206', + TEAM_CALENDAR: '30303030-a40f-4faf-bb9f-c6f9945b8207', +}; + +const YC_MESSAGE_FOLDER_IDS = { + TIM_INBOX: '30303030-1234-4567-8901-abcdef012345', + TIM_SENT: '30303030-1234-4567-8901-abcdef012349', + JONY_INBOX: '30303030-1234-4567-8901-abcdef012346', + JANE_INBOX: '30303030-1234-4567-8901-abcdef012347', + JANE_SENT: '30303030-1234-4567-8901-abcdef012348', +}; + +type WorkspaceSeedIds = { + userWorkspaceIds: { + TIM: string; + JONY: string; + PHIL: string; + JANE: string; + }; + connectedAccountIds: typeof CONNECTED_ACCOUNT_DATA_SEED_IDS; + messageChannelIds: typeof MESSAGE_CHANNEL_DATA_SEED_IDS; + calendarChannelIds: typeof CALENDAR_CHANNEL_DATA_SEED_IDS; + messageFolderIds: typeof MESSAGE_FOLDER_DATA_SEED_IDS; +}; + +const getSeedIds = (workspaceId: string): WorkspaceSeedIds => { + if (workspaceId === SEED_YCOMBINATOR_WORKSPACE_ID) { + return { + userWorkspaceIds: { + TIM: USER_WORKSPACE_DATA_SEED_IDS.TIM_ACME, + JONY: USER_WORKSPACE_DATA_SEED_IDS.JONY_ACME, + PHIL: USER_WORKSPACE_DATA_SEED_IDS.PHIL_ACME, + JANE: USER_WORKSPACE_DATA_SEED_IDS.JANE_ACME, + }, + connectedAccountIds: YC_CONNECTED_ACCOUNT_IDS, + messageChannelIds: YC_MESSAGE_CHANNEL_IDS, + calendarChannelIds: YC_CALENDAR_CHANNEL_IDS, + messageFolderIds: YC_MESSAGE_FOLDER_IDS, + }; + } + + return { + userWorkspaceIds: { + TIM: USER_WORKSPACE_DATA_SEED_IDS.TIM, + JONY: USER_WORKSPACE_DATA_SEED_IDS.JONY, + PHIL: USER_WORKSPACE_DATA_SEED_IDS.PHIL, + JANE: USER_WORKSPACE_DATA_SEED_IDS.JANE, + }, + connectedAccountIds: CONNECTED_ACCOUNT_DATA_SEED_IDS, + messageChannelIds: MESSAGE_CHANNEL_DATA_SEED_IDS, + calendarChannelIds: CALENDAR_CHANNEL_DATA_SEED_IDS, + messageFolderIds: MESSAGE_FOLDER_DATA_SEED_IDS, + }; +}; + export const seedMetadataEntities = async ({ queryRunner, schemaName, @@ -36,10 +114,6 @@ export const seedMetadataEntities = async ({ return; } - if (workspaceId === SEED_YCOMBINATOR_WORKSPACE_ID) { - return; - } - await seedConnectedAccounts({ queryRunner, schemaName, workspaceId }); await seedMessageChannels({ queryRunner, schemaName, workspaceId }); await seedCalendarChannels({ queryRunner, schemaName, workspaceId }); @@ -51,40 +125,42 @@ const seedConnectedAccounts = async ({ schemaName, workspaceId, }: SeedMetadataEntitiesArgs) => { + const ids = getSeedIds(workspaceId); + const connectedAccounts = [ { - id: CONNECTED_ACCOUNT_DATA_SEED_IDS.TIM, + id: ids.connectedAccountIds.TIM, handle: 'tim@apple.dev', provider: 'google', - userWorkspaceId: USER_WORKSPACE_DATA_SEED_IDS.TIM, + userWorkspaceId: ids.userWorkspaceIds.TIM, workspaceId, }, { - id: CONNECTED_ACCOUNT_DATA_SEED_IDS.JONY, + id: ids.connectedAccountIds.JONY, handle: 'jony.ive@apple.dev', provider: 'google', - userWorkspaceId: USER_WORKSPACE_DATA_SEED_IDS.JONY, + userWorkspaceId: ids.userWorkspaceIds.JONY, workspaceId, }, { - id: CONNECTED_ACCOUNT_DATA_SEED_IDS.PHIL, + id: ids.connectedAccountIds.PHIL, handle: 'phil.schiler@apple.dev', provider: 'google', - userWorkspaceId: USER_WORKSPACE_DATA_SEED_IDS.PHIL, + userWorkspaceId: ids.userWorkspaceIds.PHIL, workspaceId, }, { - id: CONNECTED_ACCOUNT_DATA_SEED_IDS.JANE, + id: ids.connectedAccountIds.JANE, handle: 'jane.austen@apple.dev', provider: 'google', - userWorkspaceId: USER_WORKSPACE_DATA_SEED_IDS.JANE, + userWorkspaceId: ids.userWorkspaceIds.JANE, workspaceId, }, { - id: CONNECTED_ACCOUNT_DATA_SEED_IDS.JANE_DELETABLE, + id: ids.connectedAccountIds.JANE_DELETABLE, handle: 'jane-deletable@apple.dev', provider: 'google', - userWorkspaceId: USER_WORKSPACE_DATA_SEED_IDS.JANE, + userWorkspaceId: ids.userWorkspaceIds.JANE, workspaceId, }, ]; @@ -109,9 +185,11 @@ const seedMessageChannels = async ({ schemaName, workspaceId, }: SeedMetadataEntitiesArgs) => { + const ids = getSeedIds(workspaceId); + const messageChannels = [ { - id: MESSAGE_CHANNEL_DATA_SEED_IDS.TIM, + id: ids.messageChannelIds.TIM, handle: 'tim@apple.dev', visibility: MessageChannelVisibility.SHARE_EVERYTHING, type: MessageChannelType.EMAIL, @@ -123,11 +201,11 @@ const seedMessageChannels = async ({ excludeGroupEmails: false, pendingGroupEmailsAction: 'NONE', isSyncEnabled: true, - connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.TIM, + connectedAccountId: ids.connectedAccountIds.TIM, workspaceId, }, { - id: MESSAGE_CHANNEL_DATA_SEED_IDS.JONY, + id: ids.messageChannelIds.JONY, handle: 'jony.ive@apple.dev', visibility: MessageChannelVisibility.SHARE_EVERYTHING, type: MessageChannelType.EMAIL, @@ -139,11 +217,27 @@ const seedMessageChannels = async ({ excludeGroupEmails: false, pendingGroupEmailsAction: 'NONE', isSyncEnabled: true, - connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.JONY, + connectedAccountId: ids.connectedAccountIds.JONY, workspaceId, }, { - id: MESSAGE_CHANNEL_DATA_SEED_IDS.JANE, + id: ids.messageChannelIds.PHIL, + handle: 'phil.schiler@apple.dev', + visibility: MessageChannelVisibility.SHARE_EVERYTHING, + type: MessageChannelType.EMAIL, + syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, + isContactAutoCreationEnabled: true, + contactAutoCreationPolicy: 'SENT_AND_RECEIVED', + messageFolderImportPolicy: 'ALL_FOLDERS', + excludeNonProfessionalEmails: false, + excludeGroupEmails: false, + pendingGroupEmailsAction: 'NONE', + isSyncEnabled: true, + connectedAccountId: ids.connectedAccountIds.PHIL, + workspaceId, + }, + { + id: ids.messageChannelIds.JANE, handle: 'jane.austen@apple.dev', visibility: MessageChannelVisibility.SHARE_EVERYTHING, type: MessageChannelType.EMAIL, @@ -155,7 +249,39 @@ const seedMessageChannels = async ({ excludeGroupEmails: false, pendingGroupEmailsAction: 'NONE', isSyncEnabled: true, - connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.JANE, + connectedAccountId: ids.connectedAccountIds.JANE, + workspaceId, + }, + { + id: ids.messageChannelIds.SUPPORT, + handle: 'support@apple.dev', + visibility: MessageChannelVisibility.SHARE_EVERYTHING, + type: MessageChannelType.EMAIL, + syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, + isContactAutoCreationEnabled: true, + contactAutoCreationPolicy: 'SENT_AND_RECEIVED', + messageFolderImportPolicy: 'ALL_FOLDERS', + excludeNonProfessionalEmails: false, + excludeGroupEmails: false, + pendingGroupEmailsAction: 'NONE', + isSyncEnabled: true, + connectedAccountId: ids.connectedAccountIds.TIM, + workspaceId, + }, + { + id: ids.messageChannelIds.SALES, + handle: 'sales@apple.dev', + visibility: MessageChannelVisibility.SHARE_EVERYTHING, + type: MessageChannelType.EMAIL, + syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, + isContactAutoCreationEnabled: true, + contactAutoCreationPolicy: 'SENT_AND_RECEIVED', + messageFolderImportPolicy: 'ALL_FOLDERS', + excludeNonProfessionalEmails: false, + excludeGroupEmails: false, + pendingGroupEmailsAction: 'NONE', + isSyncEnabled: true, + connectedAccountId: ids.connectedAccountIds.TIM, workspaceId, }, ]; @@ -189,38 +315,73 @@ const seedCalendarChannels = async ({ schemaName, workspaceId, }: SeedMetadataEntitiesArgs) => { + const ids = getSeedIds(workspaceId); + const calendarChannels = [ { - id: CALENDAR_CHANNEL_DATA_SEED_IDS.TIM, + id: ids.calendarChannelIds.TIM, handle: 'tim@apple.dev', visibility: CalendarChannelVisibility.METADATA, syncStage: 'CALENDAR_EVENT_LIST_FETCH_PENDING', isContactAutoCreationEnabled: true, contactAutoCreationPolicy: 'NONE', isSyncEnabled: true, - connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.TIM, + connectedAccountId: ids.connectedAccountIds.TIM, workspaceId, }, { - id: CALENDAR_CHANNEL_DATA_SEED_IDS.JONY, + id: ids.calendarChannelIds.JONY, handle: 'jony@apple.dev', visibility: CalendarChannelVisibility.SHARE_EVERYTHING, syncStage: 'CALENDAR_EVENT_LIST_FETCH_PENDING', isContactAutoCreationEnabled: true, contactAutoCreationPolicy: 'NONE', isSyncEnabled: true, - connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.JONY, + connectedAccountId: ids.connectedAccountIds.JONY, workspaceId, }, { - id: CALENDAR_CHANNEL_DATA_SEED_IDS.JANE, + id: ids.calendarChannelIds.PHIL, + handle: 'phil@apple.dev', + visibility: CalendarChannelVisibility.METADATA, + syncStage: 'CALENDAR_EVENT_LIST_FETCH_PENDING', + isContactAutoCreationEnabled: true, + contactAutoCreationPolicy: 'NONE', + isSyncEnabled: true, + connectedAccountId: ids.connectedAccountIds.PHIL, + workspaceId, + }, + { + id: ids.calendarChannelIds.JANE, handle: 'jane.austen@apple.dev', visibility: CalendarChannelVisibility.SHARE_EVERYTHING, syncStage: 'CALENDAR_EVENT_LIST_FETCH_PENDING', isContactAutoCreationEnabled: true, contactAutoCreationPolicy: 'NONE', isSyncEnabled: true, - connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.JANE, + connectedAccountId: ids.connectedAccountIds.JANE, + workspaceId, + }, + { + id: ids.calendarChannelIds.COMPANY_MAIN, + handle: 'company-main@apple.dev', + visibility: CalendarChannelVisibility.SHARE_EVERYTHING, + syncStage: 'CALENDAR_EVENT_LIST_FETCH_PENDING', + isContactAutoCreationEnabled: true, + contactAutoCreationPolicy: 'NONE', + isSyncEnabled: true, + connectedAccountId: ids.connectedAccountIds.TIM, + workspaceId, + }, + { + id: ids.calendarChannelIds.TEAM_CALENDAR, + handle: 'team-calendar@apple.dev', + visibility: CalendarChannelVisibility.SHARE_EVERYTHING, + syncStage: 'CALENDAR_EVENT_LIST_FETCH_PENDING', + isContactAutoCreationEnabled: true, + contactAutoCreationPolicy: 'NONE', + isSyncEnabled: true, + connectedAccountId: ids.connectedAccountIds.TIM, workspaceId, }, ]; @@ -249,40 +410,42 @@ const seedMessageFolders = async ({ schemaName, workspaceId, }: SeedMetadataEntitiesArgs) => { + const ids = getSeedIds(workspaceId); + const messageFolders = [ { - id: MESSAGE_FOLDER_DATA_SEED_IDS.TIM_INBOX, + id: ids.messageFolderIds.TIM_INBOX, name: 'INBOX', isSynced: true, isSentFolder: false, - messageChannelId: MESSAGE_CHANNEL_DATA_SEED_IDS.TIM, + messageChannelId: ids.messageChannelIds.TIM, workspaceId, pendingSyncAction: MessageFolderPendingSyncAction.NONE, }, { - id: MESSAGE_FOLDER_DATA_SEED_IDS.JONY_INBOX, + id: ids.messageFolderIds.JONY_INBOX, name: 'INBOX', isSynced: true, isSentFolder: false, - messageChannelId: MESSAGE_CHANNEL_DATA_SEED_IDS.JONY, + messageChannelId: ids.messageChannelIds.JONY, workspaceId, pendingSyncAction: MessageFolderPendingSyncAction.NONE, }, { - id: MESSAGE_FOLDER_DATA_SEED_IDS.JANE_INBOX, + id: ids.messageFolderIds.JANE_INBOX, name: 'INBOX', isSynced: true, isSentFolder: false, - messageChannelId: MESSAGE_CHANNEL_DATA_SEED_IDS.JANE, + messageChannelId: ids.messageChannelIds.JANE, workspaceId, pendingSyncAction: MessageFolderPendingSyncAction.NONE, }, { - id: MESSAGE_FOLDER_DATA_SEED_IDS.JANE_SENT, + id: ids.messageFolderIds.JANE_SENT, name: 'Sent', isSynced: true, isSentFolder: true, - messageChannelId: MESSAGE_CHANNEL_DATA_SEED_IDS.JANE, + messageChannelId: ids.messageChannelIds.JANE, workspaceId, pendingSyncAction: MessageFolderPendingSyncAction.NONE, }, diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/calendar-channel-data-seeds.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/calendar-channel-data-seeds.constant.ts index f8f6037647..f18158261e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/calendar-channel-data-seeds.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/calendar-channel-data-seeds.constant.ts @@ -1,5 +1,5 @@ +import { CalendarChannelVisibility } from 'twenty-shared/types'; import { CONNECTED_ACCOUNT_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/connected-account-data-seeds.constant'; -import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; type CalendarChannelDataSeed = { id: string; diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/message-channel-data-seeds.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/message-channel-data-seeds.constant.ts index 1c864c7bf7..0925195616 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/message-channel-data-seeds.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/message-channel-data-seeds.constant.ts @@ -1,9 +1,9 @@ -import { CONNECTED_ACCOUNT_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/connected-account-data-seeds.constant'; import { MessageChannelSyncStage, MessageChannelType, MessageChannelVisibility, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +} from 'twenty-shared/types'; +import { CONNECTED_ACCOUNT_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/connected-account-data-seeds.constant'; type MessageChannelDataSeed = { id: string; diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service.ts index fe1f05d0d7..ece79b18f2 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service.ts @@ -20,10 +20,6 @@ import { type AttachmentFileSeedMetadata, generateAttachmentSeedsForWorkspace, } from 'src/engine/workspace-manager/dev-seeder/data/constants/attachment-data-seeds.constant'; -import { - CALENDAR_CHANNEL_DATA_SEED_COLUMNS, - CALENDAR_CHANNEL_DATA_SEEDS, -} from 'src/engine/workspace-manager/dev-seeder/data/constants/calendar-channel-data-seeds.constant'; import { CALENDAR_CHANNEL_EVENT_ASSOCIATION_DATA_SEED_COLUMNS, CALENDAR_CHANNEL_EVENT_ASSOCIATION_DATA_SEEDS, @@ -40,10 +36,6 @@ import { COMPANY_DATA_SEED_COLUMNS, COMPANY_DATA_SEEDS, } from 'src/engine/workspace-manager/dev-seeder/data/constants/company-data-seeds.constant'; -import { - CONNECTED_ACCOUNT_DATA_SEED_COLUMNS, - CONNECTED_ACCOUNT_DATA_SEEDS, -} from 'src/engine/workspace-manager/dev-seeder/data/constants/connected-account-data-seeds.constant'; import { DASHBOARD_DATA_SEED_COLUMNS, getDashboardDataSeeds, @@ -52,10 +44,6 @@ import { EMPLOYMENT_HISTORY_DATA_SEED_COLUMNS, EMPLOYMENT_HISTORY_DATA_SEEDS, } from 'src/engine/workspace-manager/dev-seeder/data/constants/employment-history-data-seeds.constant'; -import { - MESSAGE_CHANNEL_DATA_SEED_COLUMNS, - MESSAGE_CHANNEL_DATA_SEEDS, -} from 'src/engine/workspace-manager/dev-seeder/data/constants/message-channel-data-seeds.constant'; import { MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_DATA_SEED_COLUMNS, MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_DATA_SEEDS, @@ -64,10 +52,6 @@ import { MESSAGE_DATA_SEED_COLUMNS, MESSAGE_DATA_SEEDS, } from 'src/engine/workspace-manager/dev-seeder/data/constants/message-data-seeds.constant'; -import { - MESSAGE_FOLDER_DATA_SEED_COLUMNS, - MESSAGE_FOLDER_DATA_SEEDS, -} from 'src/engine/workspace-manager/dev-seeder/data/constants/message-folder-data-seeds.constant'; import { getMessageParticipantDataSeeds, MESSAGE_PARTICIPANT_DATA_SEED_COLUMNS, @@ -168,11 +152,6 @@ const getRecordSeedsBatches = ( pgColumns: COMPANY_DATA_SEED_COLUMNS, recordSeeds: COMPANY_DATA_SEEDS, }, - { - tableName: 'connectedAccount', - pgColumns: CONNECTED_ACCOUNT_DATA_SEED_COLUMNS, - recordSeeds: CONNECTED_ACCOUNT_DATA_SEEDS, - }, { tableName: 'dashboard', pgColumns: DASHBOARD_DATA_SEED_COLUMNS, @@ -192,25 +171,10 @@ const getRecordSeedsBatches = ( pgColumns: PET_DATA_SEED_COLUMNS, recordSeeds: PET_DATA_SEEDS, }, - { - tableName: 'calendarChannel', - pgColumns: CALENDAR_CHANNEL_DATA_SEED_COLUMNS, - recordSeeds: CALENDAR_CHANNEL_DATA_SEEDS, - }, - { - tableName: 'messageChannel', - pgColumns: MESSAGE_CHANNEL_DATA_SEED_COLUMNS, - recordSeeds: MESSAGE_CHANNEL_DATA_SEEDS, - }, ]; // Batch 4: Depends on person/company/messageChannel or independent const batch4: RecordSeedConfig[] = [ - { - tableName: 'messageFolder', - pgColumns: MESSAGE_FOLDER_DATA_SEED_COLUMNS, - recordSeeds: MESSAGE_FOLDER_DATA_SEEDS, - }, { tableName: 'opportunity', pgColumns: OPPORTUNITY_DATA_SEED_COLUMNS, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap index fb7c0c4bb9..110b0589cd 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`getStandardObjectMetadataRelatedEntityIds should return standard object metadata related entity ids 1`] = ` { @@ -184,9 +184,6 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object }, "calendarChannel": { "fields": { - "calendarChannelEventAssociations": { - "id": "00000000-0000-0000-0000-000000000091", - }, "connectedAccount": { "id": "00000000-0000-0000-0000-000000000084", }, @@ -197,7 +194,7 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "id": "00000000-0000-0000-0000-000000000081", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000097", + "id": "00000000-0000-0000-0000-000000000096", }, "deletedAt": { "id": "00000000-0000-0000-0000-000000000083", @@ -215,95 +212,95 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "id": "00000000-0000-0000-0000-000000000089", }, "position": { - "id": "00000000-0000-0000-0000-000000000099", + "id": "00000000-0000-0000-0000-000000000098", }, "searchVector": { - "id": "00000000-0000-0000-0000-000000000100", + "id": "00000000-0000-0000-0000-000000000099", }, "syncCursor": { "id": "00000000-0000-0000-0000-000000000090", }, "syncStage": { - "id": "00000000-0000-0000-0000-000000000094", - }, - "syncStageStartedAt": { - "id": "00000000-0000-0000-0000-000000000095", - }, - "syncStatus": { "id": "00000000-0000-0000-0000-000000000093", }, + "syncStageStartedAt": { + "id": "00000000-0000-0000-0000-000000000094", + }, + "syncStatus": { + "id": "00000000-0000-0000-0000-000000000092", + }, "syncedAt": { - "id": "00000000-0000-0000-0000-000000000096", + "id": "00000000-0000-0000-0000-000000000095", }, "throttleFailureCount": { - "id": "00000000-0000-0000-0000-000000000092", + "id": "00000000-0000-0000-0000-000000000091", }, "updatedAt": { "id": "00000000-0000-0000-0000-000000000082", }, "updatedBy": { - "id": "00000000-0000-0000-0000-000000000098", + "id": "00000000-0000-0000-0000-000000000097", }, "visibility": { "id": "00000000-0000-0000-0000-000000000086", }, }, - "id": "00000000-0000-0000-0000-000000000117", + "id": "00000000-0000-0000-0000-000000000116", "views": { "allCalendarChannels": { - "id": "00000000-0000-0000-0000-000000000107", + "id": "00000000-0000-0000-0000-000000000106", "viewFieldGroups": {}, "viewFields": { "connectedAccount": { - "id": "00000000-0000-0000-0000-000000000102", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000106", - }, - "handle": { "id": "00000000-0000-0000-0000-000000000101", }, - "isSyncEnabled": { - "id": "00000000-0000-0000-0000-000000000104", - }, - "syncStatus": { + "createdAt": { "id": "00000000-0000-0000-0000-000000000105", }, - "visibility": { + "handle": { + "id": "00000000-0000-0000-0000-000000000100", + }, + "isSyncEnabled": { "id": "00000000-0000-0000-0000-000000000103", }, + "syncStatus": { + "id": "00000000-0000-0000-0000-000000000104", + }, + "visibility": { + "id": "00000000-0000-0000-0000-000000000102", + }, }, "viewGroups": {}, }, "calendarChannelRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000116", + "id": "00000000-0000-0000-0000-000000000115", "viewFieldGroups": { "general": { - "id": "00000000-0000-0000-0000-000000000114", + "id": "00000000-0000-0000-0000-000000000113", }, "other": { - "id": "00000000-0000-0000-0000-000000000115", + "id": "00000000-0000-0000-0000-000000000114", }, }, "viewFields": { "connectedAccount": { - "id": "00000000-0000-0000-0000-000000000108", + "id": "00000000-0000-0000-0000-000000000107", }, "createdAt": { - "id": "00000000-0000-0000-0000-000000000112", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000113", - }, - "isSyncEnabled": { - "id": "00000000-0000-0000-0000-000000000110", - }, - "syncStatus": { "id": "00000000-0000-0000-0000-000000000111", }, - "visibility": { + "createdBy": { + "id": "00000000-0000-0000-0000-000000000112", + }, + "isSyncEnabled": { "id": "00000000-0000-0000-0000-000000000109", }, + "syncStatus": { + "id": "00000000-0000-0000-0000-000000000110", + }, + "visibility": { + "id": "00000000-0000-0000-0000-000000000108", + }, }, "viewGroups": {}, }, @@ -311,7 +308,7 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object }, "calendarChannelEventAssociation": { "fields": { - "calendarChannel": { + "calendarChannelId": { "id": "00000000-0000-0000-0000-000000000058", }, "calendarEvent": { @@ -354,7 +351,7 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "id": "00000000-0000-0000-0000-000000000070", "viewFieldGroups": {}, "viewFields": { - "calendarChannel": { + "calendarChannelId": { "id": "00000000-0000-0000-0000-000000000066", }, "calendarEvent": { @@ -380,7 +377,7 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object }, }, "viewFields": { - "calendarChannel": { + "calendarChannelId": { "id": "00000000-0000-0000-0000-000000000071", }, "calendarEvent": { @@ -403,102 +400,102 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "calendarEvent": { "fields": { "calendarChannelEventAssociations": { - "id": "00000000-0000-0000-0000-000000000171", - }, - "calendarEventParticipants": { - "id": "00000000-0000-0000-0000-000000000172", - }, - "conferenceLink": { "id": "00000000-0000-0000-0000-000000000170", }, - "conferenceSolution": { + "calendarEventParticipants": { + "id": "00000000-0000-0000-0000-000000000171", + }, + "conferenceLink": { "id": "00000000-0000-0000-0000-000000000169", }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000156", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000173", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000158", - }, - "description": { - "id": "00000000-0000-0000-0000-000000000166", - }, - "endsAt": { - "id": "00000000-0000-0000-0000-000000000163", - }, - "externalCreatedAt": { - "id": "00000000-0000-0000-0000-000000000164", - }, - "externalUpdatedAt": { - "id": "00000000-0000-0000-0000-000000000165", - }, - "iCalUid": { + "conferenceSolution": { "id": "00000000-0000-0000-0000-000000000168", }, - "id": { + "createdAt": { "id": "00000000-0000-0000-0000-000000000155", }, - "isCanceled": { - "id": "00000000-0000-0000-0000-000000000160", + "createdBy": { + "id": "00000000-0000-0000-0000-000000000172", }, - "isFullDay": { - "id": "00000000-0000-0000-0000-000000000161", - }, - "location": { - "id": "00000000-0000-0000-0000-000000000167", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000175", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000176", - }, - "startsAt": { - "id": "00000000-0000-0000-0000-000000000162", - }, - "title": { - "id": "00000000-0000-0000-0000-000000000159", - }, - "updatedAt": { + "deletedAt": { "id": "00000000-0000-0000-0000-000000000157", }, - "updatedBy": { + "description": { + "id": "00000000-0000-0000-0000-000000000165", + }, + "endsAt": { + "id": "00000000-0000-0000-0000-000000000162", + }, + "externalCreatedAt": { + "id": "00000000-0000-0000-0000-000000000163", + }, + "externalUpdatedAt": { + "id": "00000000-0000-0000-0000-000000000164", + }, + "iCalUid": { + "id": "00000000-0000-0000-0000-000000000167", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000154", + }, + "isCanceled": { + "id": "00000000-0000-0000-0000-000000000159", + }, + "isFullDay": { + "id": "00000000-0000-0000-0000-000000000160", + }, + "location": { + "id": "00000000-0000-0000-0000-000000000166", + }, + "position": { "id": "00000000-0000-0000-0000-000000000174", }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000175", + }, + "startsAt": { + "id": "00000000-0000-0000-0000-000000000161", + }, + "title": { + "id": "00000000-0000-0000-0000-000000000158", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000156", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000173", + }, }, - "id": "00000000-0000-0000-0000-000000000186", + "id": "00000000-0000-0000-0000-000000000185", "views": { "allCalendarEvents": { - "id": "00000000-0000-0000-0000-000000000185", + "id": "00000000-0000-0000-0000-000000000184", "viewFieldGroups": {}, "viewFields": { "conferenceLink": { - "id": "00000000-0000-0000-0000-000000000182", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000184", - }, - "endsAt": { - "id": "00000000-0000-0000-0000-000000000179", - }, - "isCanceled": { - "id": "00000000-0000-0000-0000-000000000183", - }, - "isFullDay": { - "id": "00000000-0000-0000-0000-000000000180", - }, - "location": { "id": "00000000-0000-0000-0000-000000000181", }, - "startsAt": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000183", + }, + "endsAt": { "id": "00000000-0000-0000-0000-000000000178", }, - "title": { + "isCanceled": { + "id": "00000000-0000-0000-0000-000000000182", + }, + "isFullDay": { + "id": "00000000-0000-0000-0000-000000000179", + }, + "location": { + "id": "00000000-0000-0000-0000-000000000180", + }, + "startsAt": { "id": "00000000-0000-0000-0000-000000000177", }, + "title": { + "id": "00000000-0000-0000-0000-000000000176", + }, }, "viewGroups": {}, }, @@ -507,121 +504,121 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "calendarEventParticipant": { "fields": { "calendarEvent": { - "id": "00000000-0000-0000-0000-000000000122", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000119", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000129", - }, - "deletedAt": { "id": "00000000-0000-0000-0000-000000000121", }, - "displayName": { - "id": "00000000-0000-0000-0000-000000000124", - }, - "handle": { - "id": "00000000-0000-0000-0000-000000000123", - }, - "id": { + "createdAt": { "id": "00000000-0000-0000-0000-000000000118", }, - "isOrganizer": { - "id": "00000000-0000-0000-0000-000000000125", - }, - "person": { - "id": "00000000-0000-0000-0000-000000000127", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000131", - }, - "responseStatus": { - "id": "00000000-0000-0000-0000-000000000126", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000132", - }, - "updatedAt": { - "id": "00000000-0000-0000-0000-000000000120", - }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000130", - }, - "workspaceMember": { + "createdBy": { "id": "00000000-0000-0000-0000-000000000128", }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000120", + }, + "displayName": { + "id": "00000000-0000-0000-0000-000000000123", + }, + "handle": { + "id": "00000000-0000-0000-0000-000000000122", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000117", + }, + "isOrganizer": { + "id": "00000000-0000-0000-0000-000000000124", + }, + "person": { + "id": "00000000-0000-0000-0000-000000000126", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000130", + }, + "responseStatus": { + "id": "00000000-0000-0000-0000-000000000125", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000131", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000119", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000129", + }, + "workspaceMember": { + "id": "00000000-0000-0000-0000-000000000127", + }, }, - "id": "00000000-0000-0000-0000-000000000154", + "id": "00000000-0000-0000-0000-000000000153", "views": { "allCalendarEventParticipants": { - "id": "00000000-0000-0000-0000-000000000141", + "id": "00000000-0000-0000-0000-000000000140", "viewFieldGroups": {}, "viewFields": { "calendarEvent": { - "id": "00000000-0000-0000-0000-000000000133", + "id": "00000000-0000-0000-0000-000000000132", }, "createdAt": { - "id": "00000000-0000-0000-0000-000000000140", + "id": "00000000-0000-0000-0000-000000000139", }, "displayName": { - "id": "00000000-0000-0000-0000-000000000135", - }, - "handle": { "id": "00000000-0000-0000-0000-000000000134", }, + "handle": { + "id": "00000000-0000-0000-0000-000000000133", + }, "isOrganizer": { - "id": "00000000-0000-0000-0000-000000000136", + "id": "00000000-0000-0000-0000-000000000135", }, "person": { - "id": "00000000-0000-0000-0000-000000000138", - }, - "responseStatus": { "id": "00000000-0000-0000-0000-000000000137", }, + "responseStatus": { + "id": "00000000-0000-0000-0000-000000000136", + }, "workspaceMember": { - "id": "00000000-0000-0000-0000-000000000139", + "id": "00000000-0000-0000-0000-000000000138", }, }, "viewGroups": {}, }, "calendarEventParticipantRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000153", + "id": "00000000-0000-0000-0000-000000000152", "viewFieldGroups": { "general": { - "id": "00000000-0000-0000-0000-000000000151", + "id": "00000000-0000-0000-0000-000000000150", }, "other": { - "id": "00000000-0000-0000-0000-000000000152", + "id": "00000000-0000-0000-0000-000000000151", }, }, "viewFields": { "calendarEvent": { - "id": "00000000-0000-0000-0000-000000000142", + "id": "00000000-0000-0000-0000-000000000141", }, "createdAt": { - "id": "00000000-0000-0000-0000-000000000149", + "id": "00000000-0000-0000-0000-000000000148", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000150", + "id": "00000000-0000-0000-0000-000000000149", }, "displayName": { - "id": "00000000-0000-0000-0000-000000000144", - }, - "handle": { "id": "00000000-0000-0000-0000-000000000143", }, + "handle": { + "id": "00000000-0000-0000-0000-000000000142", + }, "isOrganizer": { - "id": "00000000-0000-0000-0000-000000000145", + "id": "00000000-0000-0000-0000-000000000144", }, "person": { - "id": "00000000-0000-0000-0000-000000000147", - }, - "responseStatus": { "id": "00000000-0000-0000-0000-000000000146", }, + "responseStatus": { + "id": "00000000-0000-0000-0000-000000000145", + }, "workspaceMember": { - "id": "00000000-0000-0000-0000-000000000148", + "id": "00000000-0000-0000-0000-000000000147", }, }, "viewGroups": {}, @@ -631,181 +628,181 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "company": { "fields": { "accountOwner": { - "id": "00000000-0000-0000-0000-000000000203", - }, - "address": { - "id": "00000000-0000-0000-0000-000000000193", - }, - "annualRecurringRevenue": { - "id": "00000000-0000-0000-0000-000000000197", - }, - "attachments": { - "id": "00000000-0000-0000-0000-000000000208", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000188", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000200", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000190", - }, - "domainName": { - "id": "00000000-0000-0000-0000-000000000192", - }, - "employees": { - "id": "00000000-0000-0000-0000-000000000194", - }, - "favorites": { - "id": "00000000-0000-0000-0000-000000000207", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000187", - }, - "idealCustomerProfile": { - "id": "00000000-0000-0000-0000-000000000198", - }, - "linkedinLink": { - "id": "00000000-0000-0000-0000-000000000195", - }, - "name": { - "id": "00000000-0000-0000-0000-000000000191", - }, - "noteTargets": { - "id": "00000000-0000-0000-0000-000000000205", - }, - "opportunities": { - "id": "00000000-0000-0000-0000-000000000206", - }, - "people": { "id": "00000000-0000-0000-0000-000000000202", }, - "position": { - "id": "00000000-0000-0000-0000-000000000199", + "address": { + "id": "00000000-0000-0000-0000-000000000192", }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000210", - }, - "taskTargets": { - "id": "00000000-0000-0000-0000-000000000204", - }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000209", - }, - "updatedAt": { - "id": "00000000-0000-0000-0000-000000000189", - }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000201", - }, - "xLink": { + "annualRecurringRevenue": { "id": "00000000-0000-0000-0000-000000000196", }, + "attachments": { + "id": "00000000-0000-0000-0000-000000000207", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000187", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000199", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000189", + }, + "domainName": { + "id": "00000000-0000-0000-0000-000000000191", + }, + "employees": { + "id": "00000000-0000-0000-0000-000000000193", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000206", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000186", + }, + "idealCustomerProfile": { + "id": "00000000-0000-0000-0000-000000000197", + }, + "linkedinLink": { + "id": "00000000-0000-0000-0000-000000000194", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000190", + }, + "noteTargets": { + "id": "00000000-0000-0000-0000-000000000204", + }, + "opportunities": { + "id": "00000000-0000-0000-0000-000000000205", + }, + "people": { + "id": "00000000-0000-0000-0000-000000000201", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000198", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000209", + }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000203", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000208", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000188", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000200", + }, + "xLink": { + "id": "00000000-0000-0000-0000-000000000195", + }, }, - "id": "00000000-0000-0000-0000-000000000243", + "id": "00000000-0000-0000-0000-000000000242", "views": { "allCompanies": { - "id": "00000000-0000-0000-0000-000000000219", + "id": "00000000-0000-0000-0000-000000000218", "viewFieldGroups": {}, "viewFields": { "accountOwner": { - "id": "00000000-0000-0000-0000-000000000214", - }, - "address": { - "id": "00000000-0000-0000-0000-000000000218", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000215", - }, - "createdBy": { "id": "00000000-0000-0000-0000-000000000213", }, - "domainName": { - "id": "00000000-0000-0000-0000-000000000212", - }, - "employees": { - "id": "00000000-0000-0000-0000-000000000216", - }, - "linkedinLink": { + "address": { "id": "00000000-0000-0000-0000-000000000217", }, - "name": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000214", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000212", + }, + "domainName": { "id": "00000000-0000-0000-0000-000000000211", }, + "employees": { + "id": "00000000-0000-0000-0000-000000000215", + }, + "linkedinLink": { + "id": "00000000-0000-0000-0000-000000000216", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000210", + }, }, "viewGroups": {}, }, "companyRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000242", + "id": "00000000-0000-0000-0000-000000000241", "viewFieldGroups": { "additional": { - "id": "00000000-0000-0000-0000-000000000240", - }, - "general": { "id": "00000000-0000-0000-0000-000000000239", }, + "general": { + "id": "00000000-0000-0000-0000-000000000238", + }, "other": { - "id": "00000000-0000-0000-0000-000000000241", + "id": "00000000-0000-0000-0000-000000000240", }, }, "viewFields": { "accountOwner": { - "id": "00000000-0000-0000-0000-000000000221", - }, - "address": { - "id": "00000000-0000-0000-0000-000000000227", - }, - "annualRecurringRevenue": { - "id": "00000000-0000-0000-0000-000000000222", - }, - "attachments": { - "id": "00000000-0000-0000-0000-000000000237", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000228", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000229", - }, - "domainName": { "id": "00000000-0000-0000-0000-000000000220", }, - "employees": { - "id": "00000000-0000-0000-0000-000000000224", + "address": { + "id": "00000000-0000-0000-0000-000000000226", }, - "favorites": { + "annualRecurringRevenue": { + "id": "00000000-0000-0000-0000-000000000221", + }, + "attachments": { "id": "00000000-0000-0000-0000-000000000236", }, - "idealCustomerProfile": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000227", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000228", + }, + "domainName": { + "id": "00000000-0000-0000-0000-000000000219", + }, + "employees": { "id": "00000000-0000-0000-0000-000000000223", }, - "linkedinLink": { - "id": "00000000-0000-0000-0000-000000000225", - }, - "noteTargets": { - "id": "00000000-0000-0000-0000-000000000234", - }, - "opportunities": { + "favorites": { "id": "00000000-0000-0000-0000-000000000235", }, - "people": { - "id": "00000000-0000-0000-0000-000000000232", + "idealCustomerProfile": { + "id": "00000000-0000-0000-0000-000000000222", }, - "taskTargets": { + "linkedinLink": { + "id": "00000000-0000-0000-0000-000000000224", + }, + "noteTargets": { "id": "00000000-0000-0000-0000-000000000233", }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000238", + "opportunities": { + "id": "00000000-0000-0000-0000-000000000234", }, - "updatedAt": { - "id": "00000000-0000-0000-0000-000000000230", - }, - "updatedBy": { + "people": { "id": "00000000-0000-0000-0000-000000000231", }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000232", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000237", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000229", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000230", + }, "xLink": { - "id": "00000000-0000-0000-0000-000000000226", + "id": "00000000-0000-0000-0000-000000000225", }, }, "viewGroups": {}, @@ -815,118 +812,118 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "connectedAccount": { "fields": { "accessToken": { - "id": "00000000-0000-0000-0000-000000000250", - }, - "accountOwner": { - "id": "00000000-0000-0000-0000-000000000252", - }, - "authFailedAt": { - "id": "00000000-0000-0000-0000-000000000254", - }, - "calendarChannels": { - "id": "00000000-0000-0000-0000-000000000257", - }, - "connectionParameters": { - "id": "00000000-0000-0000-0000-000000000260", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000245", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000261", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000247", - }, - "handle": { - "id": "00000000-0000-0000-0000-000000000248", - }, - "handleAliases": { - "id": "00000000-0000-0000-0000-000000000258", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000244", - }, - "lastCredentialsRefreshedAt": { - "id": "00000000-0000-0000-0000-000000000255", - }, - "lastSyncHistoryId": { - "id": "00000000-0000-0000-0000-000000000253", - }, - "messageChannels": { - "id": "00000000-0000-0000-0000-000000000256", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000263", - }, - "provider": { "id": "00000000-0000-0000-0000-000000000249", }, - "refreshToken": { + "accountOwner": { "id": "00000000-0000-0000-0000-000000000251", }, - "scopes": { + "authFailedAt": { + "id": "00000000-0000-0000-0000-000000000253", + }, + "calendarChannels": { + "id": "00000000-0000-0000-0000-000000000256", + }, + "connectionParameters": { "id": "00000000-0000-0000-0000-000000000259", }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000264", + "createdAt": { + "id": "00000000-0000-0000-0000-000000000244", }, - "updatedAt": { + "createdBy": { + "id": "00000000-0000-0000-0000-000000000260", + }, + "deletedAt": { "id": "00000000-0000-0000-0000-000000000246", }, - "updatedBy": { + "handle": { + "id": "00000000-0000-0000-0000-000000000247", + }, + "handleAliases": { + "id": "00000000-0000-0000-0000-000000000257", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000243", + }, + "lastCredentialsRefreshedAt": { + "id": "00000000-0000-0000-0000-000000000254", + }, + "lastSyncHistoryId": { + "id": "00000000-0000-0000-0000-000000000252", + }, + "messageChannels": { + "id": "00000000-0000-0000-0000-000000000255", + }, + "position": { "id": "00000000-0000-0000-0000-000000000262", }, + "provider": { + "id": "00000000-0000-0000-0000-000000000248", + }, + "refreshToken": { + "id": "00000000-0000-0000-0000-000000000250", + }, + "scopes": { + "id": "00000000-0000-0000-0000-000000000258", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000263", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000245", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000261", + }, }, - "id": "00000000-0000-0000-0000-000000000279", + "id": "00000000-0000-0000-0000-000000000278", "views": { "allConnectedAccounts": { - "id": "00000000-0000-0000-0000-000000000270", + "id": "00000000-0000-0000-0000-000000000269", "viewFieldGroups": {}, "viewFields": { "accountOwner": { - "id": "00000000-0000-0000-0000-000000000267", + "id": "00000000-0000-0000-0000-000000000266", }, "authFailedAt": { - "id": "00000000-0000-0000-0000-000000000268", + "id": "00000000-0000-0000-0000-000000000267", }, "createdAt": { - "id": "00000000-0000-0000-0000-000000000269", + "id": "00000000-0000-0000-0000-000000000268", }, "handle": { - "id": "00000000-0000-0000-0000-000000000265", + "id": "00000000-0000-0000-0000-000000000264", }, "provider": { - "id": "00000000-0000-0000-0000-000000000266", + "id": "00000000-0000-0000-0000-000000000265", }, }, "viewGroups": {}, }, "connectedAccountRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000278", + "id": "00000000-0000-0000-0000-000000000277", "viewFieldGroups": { "general": { - "id": "00000000-0000-0000-0000-000000000276", + "id": "00000000-0000-0000-0000-000000000275", }, "other": { - "id": "00000000-0000-0000-0000-000000000277", + "id": "00000000-0000-0000-0000-000000000276", }, }, "viewFields": { "accountOwner": { - "id": "00000000-0000-0000-0000-000000000272", + "id": "00000000-0000-0000-0000-000000000271", }, "authFailedAt": { - "id": "00000000-0000-0000-0000-000000000273", + "id": "00000000-0000-0000-0000-000000000272", }, "createdAt": { - "id": "00000000-0000-0000-0000-000000000274", + "id": "00000000-0000-0000-0000-000000000273", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000275", + "id": "00000000-0000-0000-0000-000000000274", }, "provider": { - "id": "00000000-0000-0000-0000-000000000271", + "id": "00000000-0000-0000-0000-000000000270", }, }, "viewGroups": {}, @@ -936,62 +933,62 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "dashboard": { "fields": { "attachments": { - "id": "00000000-0000-0000-0000-000000000291", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000281", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000287", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000283", - }, - "favorites": { "id": "00000000-0000-0000-0000-000000000290", }, - "id": { + "createdAt": { "id": "00000000-0000-0000-0000-000000000280", }, - "pageLayoutId": { + "createdBy": { "id": "00000000-0000-0000-0000-000000000286", }, - "position": { - "id": "00000000-0000-0000-0000-000000000285", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000292", - }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000289", - }, - "title": { - "id": "00000000-0000-0000-0000-000000000284", - }, - "updatedAt": { + "deletedAt": { "id": "00000000-0000-0000-0000-000000000282", }, - "updatedBy": { + "favorites": { + "id": "00000000-0000-0000-0000-000000000289", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000279", + }, + "pageLayoutId": { + "id": "00000000-0000-0000-0000-000000000285", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000284", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000291", + }, + "timelineActivities": { "id": "00000000-0000-0000-0000-000000000288", }, + "title": { + "id": "00000000-0000-0000-0000-000000000283", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000281", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000287", + }, }, - "id": "00000000-0000-0000-0000-000000000298", + "id": "00000000-0000-0000-0000-000000000297", "views": { "allDashboards": { - "id": "00000000-0000-0000-0000-000000000297", + "id": "00000000-0000-0000-0000-000000000296", "viewFieldGroups": {}, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000295", - }, - "createdBy": { "id": "00000000-0000-0000-0000-000000000294", }, - "title": { + "createdBy": { "id": "00000000-0000-0000-0000-000000000293", }, + "title": { + "id": "00000000-0000-0000-0000-000000000292", + }, "updatedAt": { - "id": "00000000-0000-0000-0000-000000000296", + "id": "00000000-0000-0000-0000-000000000295", }, }, "viewGroups": {}, @@ -1001,143 +998,143 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "favorite": { "fields": { "company": { - "id": "00000000-0000-0000-0000-000000000306", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000300", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000316", - }, - "dashboard": { - "id": "00000000-0000-0000-0000-000000000315", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000302", - }, - "favoriteFolder": { - "id": "00000000-0000-0000-0000-000000000314", - }, - "forWorkspaceMember": { - "id": "00000000-0000-0000-0000-000000000304", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000299", - }, - "note": { - "id": "00000000-0000-0000-0000-000000000312", - }, - "opportunity": { - "id": "00000000-0000-0000-0000-000000000307", - }, - "person": { "id": "00000000-0000-0000-0000-000000000305", }, - "position": { - "id": "00000000-0000-0000-0000-000000000303", + "createdAt": { + "id": "00000000-0000-0000-0000-000000000299", }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000318", + "createdBy": { + "id": "00000000-0000-0000-0000-000000000315", }, - "task": { - "id": "00000000-0000-0000-0000-000000000311", + "dashboard": { + "id": "00000000-0000-0000-0000-000000000314", }, - "updatedAt": { + "deletedAt": { "id": "00000000-0000-0000-0000-000000000301", }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000317", - }, - "viewId": { + "favoriteFolder": { "id": "00000000-0000-0000-0000-000000000313", }, - "workflow": { - "id": "00000000-0000-0000-0000-000000000308", + "forWorkspaceMember": { + "id": "00000000-0000-0000-0000-000000000303", }, - "workflowRun": { + "id": { + "id": "00000000-0000-0000-0000-000000000298", + }, + "note": { + "id": "00000000-0000-0000-0000-000000000311", + }, + "opportunity": { + "id": "00000000-0000-0000-0000-000000000306", + }, + "person": { + "id": "00000000-0000-0000-0000-000000000304", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000302", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000317", + }, + "task": { "id": "00000000-0000-0000-0000-000000000310", }, - "workflowVersion": { + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000300", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000316", + }, + "viewId": { + "id": "00000000-0000-0000-0000-000000000312", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000307", + }, + "workflowRun": { "id": "00000000-0000-0000-0000-000000000309", }, + "workflowVersion": { + "id": "00000000-0000-0000-0000-000000000308", + }, }, - "id": "00000000-0000-0000-0000-000000000342", + "id": "00000000-0000-0000-0000-000000000341", "views": { "allFavorites": { - "id": "00000000-0000-0000-0000-000000000328", + "id": "00000000-0000-0000-0000-000000000327", "viewFieldGroups": {}, "viewFields": { "company": { - "id": "00000000-0000-0000-0000-000000000321", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000327", - }, - "dashboard": { - "id": "00000000-0000-0000-0000-000000000325", - }, - "favoriteFolder": { - "id": "00000000-0000-0000-0000-000000000326", - }, - "forWorkspaceMember": { - "id": "00000000-0000-0000-0000-000000000319", - }, - "note": { - "id": "00000000-0000-0000-0000-000000000324", - }, - "opportunity": { - "id": "00000000-0000-0000-0000-000000000322", - }, - "person": { "id": "00000000-0000-0000-0000-000000000320", }, - "task": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000326", + }, + "dashboard": { + "id": "00000000-0000-0000-0000-000000000324", + }, + "favoriteFolder": { + "id": "00000000-0000-0000-0000-000000000325", + }, + "forWorkspaceMember": { + "id": "00000000-0000-0000-0000-000000000318", + }, + "note": { "id": "00000000-0000-0000-0000-000000000323", }, + "opportunity": { + "id": "00000000-0000-0000-0000-000000000321", + }, + "person": { + "id": "00000000-0000-0000-0000-000000000319", + }, + "task": { + "id": "00000000-0000-0000-0000-000000000322", + }, }, "viewGroups": {}, }, "favoriteRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000341", + "id": "00000000-0000-0000-0000-000000000340", "viewFieldGroups": { "general": { - "id": "00000000-0000-0000-0000-000000000339", + "id": "00000000-0000-0000-0000-000000000338", }, "other": { - "id": "00000000-0000-0000-0000-000000000340", + "id": "00000000-0000-0000-0000-000000000339", }, }, "viewFields": { "company": { - "id": "00000000-0000-0000-0000-000000000331", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000337", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000338", - }, - "dashboard": { - "id": "00000000-0000-0000-0000-000000000335", - }, - "favoriteFolder": { - "id": "00000000-0000-0000-0000-000000000336", - }, - "forWorkspaceMember": { - "id": "00000000-0000-0000-0000-000000000329", - }, - "note": { - "id": "00000000-0000-0000-0000-000000000334", - }, - "opportunity": { - "id": "00000000-0000-0000-0000-000000000332", - }, - "person": { "id": "00000000-0000-0000-0000-000000000330", }, - "task": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000336", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000337", + }, + "dashboard": { + "id": "00000000-0000-0000-0000-000000000334", + }, + "favoriteFolder": { + "id": "00000000-0000-0000-0000-000000000335", + }, + "forWorkspaceMember": { + "id": "00000000-0000-0000-0000-000000000328", + }, + "note": { "id": "00000000-0000-0000-0000-000000000333", }, + "opportunity": { + "id": "00000000-0000-0000-0000-000000000331", + }, + "person": { + "id": "00000000-0000-0000-0000-000000000329", + }, + "task": { + "id": "00000000-0000-0000-0000-000000000332", + }, }, "viewGroups": {}, }, @@ -1146,67 +1143,67 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "favoriteFolder": { "fields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000344", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000350", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000346", - }, - "favorites": { - "id": "00000000-0000-0000-0000-000000000349", - }, - "id": { "id": "00000000-0000-0000-0000-000000000343", }, - "name": { - "id": "00000000-0000-0000-0000-000000000348", + "createdBy": { + "id": "00000000-0000-0000-0000-000000000349", }, - "position": { - "id": "00000000-0000-0000-0000-000000000347", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000352", - }, - "updatedAt": { + "deletedAt": { "id": "00000000-0000-0000-0000-000000000345", }, - "updatedBy": { + "favorites": { + "id": "00000000-0000-0000-0000-000000000348", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000342", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000347", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000346", + }, + "searchVector": { "id": "00000000-0000-0000-0000-000000000351", }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000344", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000350", + }, }, - "id": "00000000-0000-0000-0000-000000000361", + "id": "00000000-0000-0000-0000-000000000360", "views": { "allFavoriteFolders": { - "id": "00000000-0000-0000-0000-000000000355", + "id": "00000000-0000-0000-0000-000000000354", "viewFieldGroups": {}, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000354", + "id": "00000000-0000-0000-0000-000000000353", }, "name": { - "id": "00000000-0000-0000-0000-000000000353", + "id": "00000000-0000-0000-0000-000000000352", }, }, "viewGroups": {}, }, "favoriteFolderRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000360", + "id": "00000000-0000-0000-0000-000000000359", "viewFieldGroups": { "general": { - "id": "00000000-0000-0000-0000-000000000358", + "id": "00000000-0000-0000-0000-000000000357", }, "other": { - "id": "00000000-0000-0000-0000-000000000359", + "id": "00000000-0000-0000-0000-000000000358", }, }, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000356", + "id": "00000000-0000-0000-0000-000000000355", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000357", + "id": "00000000-0000-0000-0000-000000000356", }, }, "viewGroups": {}, @@ -1216,80 +1213,80 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "message": { "fields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000542", + "id": "00000000-0000-0000-0000-000000000538", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000553", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000544", - }, - "direction": { - "id": "00000000-0000-0000-0000-000000000547", - }, - "headerMessageId": { - "id": "00000000-0000-0000-0000-000000000545", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000541", - }, - "messageChannelMessageAssociations": { - "id": "00000000-0000-0000-0000-000000000552", - }, - "messageParticipants": { - "id": "00000000-0000-0000-0000-000000000551", - }, - "messageThread": { - "id": "00000000-0000-0000-0000-000000000546", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000555", - }, - "receivedAt": { - "id": "00000000-0000-0000-0000-000000000550", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000556", - }, - "subject": { - "id": "00000000-0000-0000-0000-000000000548", - }, - "text": { "id": "00000000-0000-0000-0000-000000000549", }, - "updatedAt": { + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000540", + }, + "direction": { "id": "00000000-0000-0000-0000-000000000543", }, + "headerMessageId": { + "id": "00000000-0000-0000-0000-000000000541", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000537", + }, + "messageChannelMessageAssociations": { + "id": "00000000-0000-0000-0000-000000000548", + }, + "messageParticipants": { + "id": "00000000-0000-0000-0000-000000000547", + }, + "messageThread": { + "id": "00000000-0000-0000-0000-000000000542", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000551", + }, + "receivedAt": { + "id": "00000000-0000-0000-0000-000000000546", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000552", + }, + "subject": { + "id": "00000000-0000-0000-0000-000000000544", + }, + "text": { + "id": "00000000-0000-0000-0000-000000000545", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000539", + }, "updatedBy": { - "id": "00000000-0000-0000-0000-000000000554", + "id": "00000000-0000-0000-0000-000000000550", }, }, - "id": "00000000-0000-0000-0000-000000000565", + "id": "00000000-0000-0000-0000-000000000561", "views": { "allMessages": { - "id": "00000000-0000-0000-0000-000000000564", + "id": "00000000-0000-0000-0000-000000000560", "viewFieldGroups": {}, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000563", - }, - "headerMessageId": { - "id": "00000000-0000-0000-0000-000000000561", - }, - "messageParticipants": { "id": "00000000-0000-0000-0000-000000000559", }, - "messageThread": { - "id": "00000000-0000-0000-0000-000000000558", - }, - "receivedAt": { - "id": "00000000-0000-0000-0000-000000000560", - }, - "subject": { + "headerMessageId": { "id": "00000000-0000-0000-0000-000000000557", }, + "messageParticipants": { + "id": "00000000-0000-0000-0000-000000000555", + }, + "messageThread": { + "id": "00000000-0000-0000-0000-000000000554", + }, + "receivedAt": { + "id": "00000000-0000-0000-0000-000000000556", + }, + "subject": { + "id": "00000000-0000-0000-0000-000000000553", + }, "text": { - "id": "00000000-0000-0000-0000-000000000562", + "id": "00000000-0000-0000-0000-000000000558", }, }, "viewGroups": {}, @@ -1299,151 +1296,145 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "messageChannel": { "fields": { "connectedAccount": { - "id": "00000000-0000-0000-0000-000000000421", - }, - "contactAutoCreationPolicy": { - "id": "00000000-0000-0000-0000-000000000424", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000416", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000439", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000418", - }, - "excludeGroupEmails": { - "id": "00000000-0000-0000-0000-000000000426", - }, - "excludeNonProfessionalEmails": { - "id": "00000000-0000-0000-0000-000000000425", - }, - "handle": { "id": "00000000-0000-0000-0000-000000000420", }, - "id": { - "id": "00000000-0000-0000-0000-000000000415", - }, - "isContactAutoCreationEnabled": { + "contactAutoCreationPolicy": { "id": "00000000-0000-0000-0000-000000000423", }, - "isSyncEnabled": { - "id": "00000000-0000-0000-0000-000000000431", + "createdAt": { + "id": "00000000-0000-0000-0000-000000000415", }, - "messageChannelMessageAssociations": { - "id": "00000000-0000-0000-0000-000000000427", - }, - "messageFolderImportPolicy": { - "id": "00000000-0000-0000-0000-000000000429", - }, - "messageFolders": { - "id": "00000000-0000-0000-0000-000000000428", - }, - "pendingGroupEmailsAction": { - "id": "00000000-0000-0000-0000-000000000430", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000441", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000442", - }, - "syncCursor": { - "id": "00000000-0000-0000-0000-000000000432", - }, - "syncStage": { - "id": "00000000-0000-0000-0000-000000000435", - }, - "syncStageStartedAt": { + "createdBy": { "id": "00000000-0000-0000-0000-000000000436", }, - "syncStatus": { - "id": "00000000-0000-0000-0000-000000000434", - }, - "syncedAt": { - "id": "00000000-0000-0000-0000-000000000433", - }, - "throttleFailureCount": { - "id": "00000000-0000-0000-0000-000000000437", - }, - "throttleRetryAfter": { - "id": "00000000-0000-0000-0000-000000000438", - }, - "type": { - "id": "00000000-0000-0000-0000-000000000422", - }, - "updatedAt": { + "deletedAt": { "id": "00000000-0000-0000-0000-000000000417", }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000440", + "excludeGroupEmails": { + "id": "00000000-0000-0000-0000-000000000425", }, - "visibility": { + "excludeNonProfessionalEmails": { + "id": "00000000-0000-0000-0000-000000000424", + }, + "handle": { "id": "00000000-0000-0000-0000-000000000419", }, + "id": { + "id": "00000000-0000-0000-0000-000000000414", + }, + "isContactAutoCreationEnabled": { + "id": "00000000-0000-0000-0000-000000000422", + }, + "isSyncEnabled": { + "id": "00000000-0000-0000-0000-000000000428", + }, + "messageFolderImportPolicy": { + "id": "00000000-0000-0000-0000-000000000426", + }, + "pendingGroupEmailsAction": { + "id": "00000000-0000-0000-0000-000000000427", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000438", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000439", + }, + "syncCursor": { + "id": "00000000-0000-0000-0000-000000000429", + }, + "syncStage": { + "id": "00000000-0000-0000-0000-000000000432", + }, + "syncStageStartedAt": { + "id": "00000000-0000-0000-0000-000000000433", + }, + "syncStatus": { + "id": "00000000-0000-0000-0000-000000000431", + }, + "syncedAt": { + "id": "00000000-0000-0000-0000-000000000430", + }, + "throttleFailureCount": { + "id": "00000000-0000-0000-0000-000000000434", + }, + "throttleRetryAfter": { + "id": "00000000-0000-0000-0000-000000000435", + }, + "type": { + "id": "00000000-0000-0000-0000-000000000421", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000416", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000437", + }, + "visibility": { + "id": "00000000-0000-0000-0000-000000000418", + }, }, - "id": "00000000-0000-0000-0000-000000000461", + "id": "00000000-0000-0000-0000-000000000458", "views": { "allMessageChannels": { - "id": "00000000-0000-0000-0000-000000000450", + "id": "00000000-0000-0000-0000-000000000447", "viewFieldGroups": {}, "viewFields": { "connectedAccount": { - "id": "00000000-0000-0000-0000-000000000444", + "id": "00000000-0000-0000-0000-000000000441", }, "createdAt": { - "id": "00000000-0000-0000-0000-000000000449", + "id": "00000000-0000-0000-0000-000000000446", }, "handle": { - "id": "00000000-0000-0000-0000-000000000443", + "id": "00000000-0000-0000-0000-000000000440", }, "isSyncEnabled": { - "id": "00000000-0000-0000-0000-000000000447", + "id": "00000000-0000-0000-0000-000000000444", }, "syncStatus": { - "id": "00000000-0000-0000-0000-000000000448", - }, - "type": { "id": "00000000-0000-0000-0000-000000000445", }, + "type": { + "id": "00000000-0000-0000-0000-000000000442", + }, "visibility": { - "id": "00000000-0000-0000-0000-000000000446", + "id": "00000000-0000-0000-0000-000000000443", }, }, "viewGroups": {}, }, "messageChannelRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000460", + "id": "00000000-0000-0000-0000-000000000457", "viewFieldGroups": { "general": { - "id": "00000000-0000-0000-0000-000000000458", + "id": "00000000-0000-0000-0000-000000000455", }, "other": { - "id": "00000000-0000-0000-0000-000000000459", + "id": "00000000-0000-0000-0000-000000000456", }, }, "viewFields": { "connectedAccount": { - "id": "00000000-0000-0000-0000-000000000451", + "id": "00000000-0000-0000-0000-000000000448", }, "createdAt": { - "id": "00000000-0000-0000-0000-000000000456", + "id": "00000000-0000-0000-0000-000000000453", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000457", - }, - "isSyncEnabled": { "id": "00000000-0000-0000-0000-000000000454", }, - "syncStatus": { - "id": "00000000-0000-0000-0000-000000000455", + "isSyncEnabled": { + "id": "00000000-0000-0000-0000-000000000451", }, - "type": { + "syncStatus": { "id": "00000000-0000-0000-0000-000000000452", }, + "type": { + "id": "00000000-0000-0000-0000-000000000449", + }, "visibility": { - "id": "00000000-0000-0000-0000-000000000453", + "id": "00000000-0000-0000-0000-000000000450", }, }, "viewGroups": {}, @@ -1453,103 +1444,103 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "messageChannelMessageAssociation": { "fields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000363", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000372", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000365", - }, - "direction": { - "id": "00000000-0000-0000-0000-000000000371", - }, - "id": { "id": "00000000-0000-0000-0000-000000000362", }, - "message": { - "id": "00000000-0000-0000-0000-000000000367", + "createdBy": { + "id": "00000000-0000-0000-0000-000000000371", }, - "messageChannel": { - "id": "00000000-0000-0000-0000-000000000366", - }, - "messageExternalId": { - "id": "00000000-0000-0000-0000-000000000368", - }, - "messageFolders": { - "id": "00000000-0000-0000-0000-000000000376", - }, - "messageThread": { - "id": "00000000-0000-0000-0000-000000000369", - }, - "messageThreadExternalId": { - "id": "00000000-0000-0000-0000-000000000370", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000374", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000375", - }, - "updatedAt": { + "deletedAt": { "id": "00000000-0000-0000-0000-000000000364", }, - "updatedBy": { + "direction": { + "id": "00000000-0000-0000-0000-000000000370", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000361", + }, + "message": { + "id": "00000000-0000-0000-0000-000000000366", + }, + "messageChannelId": { + "id": "00000000-0000-0000-0000-000000000365", + }, + "messageExternalId": { + "id": "00000000-0000-0000-0000-000000000367", + }, + "messageFolders": { + "id": "00000000-0000-0000-0000-000000000375", + }, + "messageThread": { + "id": "00000000-0000-0000-0000-000000000368", + }, + "messageThreadExternalId": { + "id": "00000000-0000-0000-0000-000000000369", + }, + "position": { "id": "00000000-0000-0000-0000-000000000373", }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000374", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000363", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000372", + }, }, - "id": "00000000-0000-0000-0000-000000000392", + "id": "00000000-0000-0000-0000-000000000391", "views": { "allMessageChannelMessageAssociations": { - "id": "00000000-0000-0000-0000-000000000382", + "id": "00000000-0000-0000-0000-000000000381", "viewFieldGroups": {}, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000381", - }, - "direction": { "id": "00000000-0000-0000-0000-000000000380", }, - "message": { - "id": "00000000-0000-0000-0000-000000000378", + "direction": { + "id": "00000000-0000-0000-0000-000000000379", }, - "messageChannel": { + "message": { "id": "00000000-0000-0000-0000-000000000377", }, + "messageChannelId": { + "id": "00000000-0000-0000-0000-000000000376", + }, "messageExternalId": { - "id": "00000000-0000-0000-0000-000000000379", + "id": "00000000-0000-0000-0000-000000000378", }, }, "viewGroups": {}, }, "messageChannelMessageAssociationRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000391", + "id": "00000000-0000-0000-0000-000000000390", "viewFieldGroups": { "general": { - "id": "00000000-0000-0000-0000-000000000389", + "id": "00000000-0000-0000-0000-000000000388", }, "other": { - "id": "00000000-0000-0000-0000-000000000390", + "id": "00000000-0000-0000-0000-000000000389", }, }, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000387", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000388", - }, - "direction": { "id": "00000000-0000-0000-0000-000000000386", }, - "message": { - "id": "00000000-0000-0000-0000-000000000384", + "createdBy": { + "id": "00000000-0000-0000-0000-000000000387", }, - "messageChannel": { + "direction": { + "id": "00000000-0000-0000-0000-000000000385", + }, + "message": { "id": "00000000-0000-0000-0000-000000000383", }, + "messageChannelId": { + "id": "00000000-0000-0000-0000-000000000382", + }, "messageExternalId": { - "id": "00000000-0000-0000-0000-000000000385", + "id": "00000000-0000-0000-0000-000000000384", }, }, "viewGroups": {}, @@ -1559,76 +1550,76 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "messageChannelMessageAssociationMessageFolder": { "fields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000394", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000397", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000396", - }, - "id": { "id": "00000000-0000-0000-0000-000000000393", }, - "messageChannelMessageAssociation": { - "id": "00000000-0000-0000-0000-000000000401", + "createdBy": { + "id": "00000000-0000-0000-0000-000000000396", }, - "messageFolder": { - "id": "00000000-0000-0000-0000-000000000402", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000399", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000400", - }, - "updatedAt": { + "deletedAt": { "id": "00000000-0000-0000-0000-000000000395", }, - "updatedBy": { + "id": { + "id": "00000000-0000-0000-0000-000000000392", + }, + "messageChannelMessageAssociation": { + "id": "00000000-0000-0000-0000-000000000400", + }, + "messageFolderId": { + "id": "00000000-0000-0000-0000-000000000401", + }, + "position": { "id": "00000000-0000-0000-0000-000000000398", }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000399", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000394", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000397", + }, }, - "id": "00000000-0000-0000-0000-000000000414", + "id": "00000000-0000-0000-0000-000000000413", "views": { "allMessageChannelMessageAssociationMessageFolders": { - "id": "00000000-0000-0000-0000-000000000406", + "id": "00000000-0000-0000-0000-000000000405", "viewFieldGroups": {}, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000405", + "id": "00000000-0000-0000-0000-000000000404", }, "messageChannelMessageAssociation": { - "id": "00000000-0000-0000-0000-000000000403", + "id": "00000000-0000-0000-0000-000000000402", }, - "messageFolder": { - "id": "00000000-0000-0000-0000-000000000404", + "messageFolderId": { + "id": "00000000-0000-0000-0000-000000000403", }, }, "viewGroups": {}, }, "messageChannelMessageAssociationMessageFolderRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000413", + "id": "00000000-0000-0000-0000-000000000412", "viewFieldGroups": { "general": { - "id": "00000000-0000-0000-0000-000000000411", + "id": "00000000-0000-0000-0000-000000000410", }, "other": { - "id": "00000000-0000-0000-0000-000000000412", + "id": "00000000-0000-0000-0000-000000000411", }, }, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000409", + "id": "00000000-0000-0000-0000-000000000408", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000410", + "id": "00000000-0000-0000-0000-000000000409", }, "messageChannelMessageAssociation": { - "id": "00000000-0000-0000-0000-000000000407", + "id": "00000000-0000-0000-0000-000000000406", }, - "messageFolder": { - "id": "00000000-0000-0000-0000-000000000408", + "messageFolderId": { + "id": "00000000-0000-0000-0000-000000000407", }, }, "viewGroups": {}, @@ -1638,106 +1629,103 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "messageFolder": { "fields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000463", + "id": "00000000-0000-0000-0000-000000000460", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000474", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000465", - }, - "externalId": { - "id": "00000000-0000-0000-0000-000000000472", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000462", - }, - "isSentFolder": { - "id": "00000000-0000-0000-0000-000000000470", - }, - "isSynced": { "id": "00000000-0000-0000-0000-000000000471", }, - "messageChannel": { - "id": "00000000-0000-0000-0000-000000000468", + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000462", }, - "messageChannelMessageAssociationMessageFolders": { - "id": "00000000-0000-0000-0000-000000000478", - }, - "name": { - "id": "00000000-0000-0000-0000-000000000466", - }, - "parentFolderId": { - "id": "00000000-0000-0000-0000-000000000467", - }, - "pendingSyncAction": { - "id": "00000000-0000-0000-0000-000000000473", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000476", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000477", - }, - "syncCursor": { + "externalId": { "id": "00000000-0000-0000-0000-000000000469", }, - "updatedAt": { + "id": { + "id": "00000000-0000-0000-0000-000000000459", + }, + "isSentFolder": { + "id": "00000000-0000-0000-0000-000000000467", + }, + "isSynced": { + "id": "00000000-0000-0000-0000-000000000468", + }, + "messageChannelId": { + "id": "00000000-0000-0000-0000-000000000465", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000463", + }, + "parentFolderId": { "id": "00000000-0000-0000-0000-000000000464", }, + "pendingSyncAction": { + "id": "00000000-0000-0000-0000-000000000470", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000473", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000474", + }, + "syncCursor": { + "id": "00000000-0000-0000-0000-000000000466", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000461", + }, "updatedBy": { - "id": "00000000-0000-0000-0000-000000000475", + "id": "00000000-0000-0000-0000-000000000472", }, }, - "id": "00000000-0000-0000-0000-000000000493", + "id": "00000000-0000-0000-0000-000000000489", "views": { "allMessageFolders": { - "id": "00000000-0000-0000-0000-000000000484", + "id": "00000000-0000-0000-0000-000000000480", "viewFieldGroups": {}, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000483", + "id": "00000000-0000-0000-0000-000000000479", }, "isSentFolder": { - "id": "00000000-0000-0000-0000-000000000481", + "id": "00000000-0000-0000-0000-000000000477", }, "isSynced": { - "id": "00000000-0000-0000-0000-000000000482", + "id": "00000000-0000-0000-0000-000000000478", }, - "messageChannel": { - "id": "00000000-0000-0000-0000-000000000480", + "messageChannelId": { + "id": "00000000-0000-0000-0000-000000000476", }, "name": { - "id": "00000000-0000-0000-0000-000000000479", + "id": "00000000-0000-0000-0000-000000000475", }, }, "viewGroups": {}, }, "messageFolderRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000492", + "id": "00000000-0000-0000-0000-000000000488", "viewFieldGroups": { "general": { - "id": "00000000-0000-0000-0000-000000000490", + "id": "00000000-0000-0000-0000-000000000486", }, "other": { - "id": "00000000-0000-0000-0000-000000000491", + "id": "00000000-0000-0000-0000-000000000487", }, }, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000488", + "id": "00000000-0000-0000-0000-000000000484", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000489", + "id": "00000000-0000-0000-0000-000000000485", }, "isSentFolder": { - "id": "00000000-0000-0000-0000-000000000486", + "id": "00000000-0000-0000-0000-000000000482", }, "isSynced": { - "id": "00000000-0000-0000-0000-000000000487", + "id": "00000000-0000-0000-0000-000000000483", }, - "messageChannel": { - "id": "00000000-0000-0000-0000-000000000485", + "messageChannelId": { + "id": "00000000-0000-0000-0000-000000000481", }, }, "viewGroups": {}, @@ -1747,109 +1735,109 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "messageParticipant": { "fields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000495", + "id": "00000000-0000-0000-0000-000000000491", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000504", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000497", - }, - "displayName": { - "id": "00000000-0000-0000-0000-000000000501", - }, - "handle": { "id": "00000000-0000-0000-0000-000000000500", }, - "id": { - "id": "00000000-0000-0000-0000-000000000494", + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000493", }, - "message": { - "id": "00000000-0000-0000-0000-000000000498", + "displayName": { + "id": "00000000-0000-0000-0000-000000000497", }, - "person": { - "id": "00000000-0000-0000-0000-000000000502", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000506", - }, - "role": { - "id": "00000000-0000-0000-0000-000000000499", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000507", - }, - "updatedAt": { + "handle": { "id": "00000000-0000-0000-0000-000000000496", }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000505", + "id": { + "id": "00000000-0000-0000-0000-000000000490", }, - "workspaceMember": { + "message": { + "id": "00000000-0000-0000-0000-000000000494", + }, + "person": { + "id": "00000000-0000-0000-0000-000000000498", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000502", + }, + "role": { + "id": "00000000-0000-0000-0000-000000000495", + }, + "searchVector": { "id": "00000000-0000-0000-0000-000000000503", }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000492", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000501", + }, + "workspaceMember": { + "id": "00000000-0000-0000-0000-000000000499", + }, }, - "id": "00000000-0000-0000-0000-000000000526", + "id": "00000000-0000-0000-0000-000000000522", "views": { "allMessageParticipants": { - "id": "00000000-0000-0000-0000-000000000515", + "id": "00000000-0000-0000-0000-000000000511", "viewFieldGroups": {}, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000514", - }, - "displayName": { - "id": "00000000-0000-0000-0000-000000000511", - }, - "handle": { "id": "00000000-0000-0000-0000-000000000510", }, + "displayName": { + "id": "00000000-0000-0000-0000-000000000507", + }, + "handle": { + "id": "00000000-0000-0000-0000-000000000506", + }, "message": { - "id": "00000000-0000-0000-0000-000000000508", + "id": "00000000-0000-0000-0000-000000000504", }, "person": { - "id": "00000000-0000-0000-0000-000000000512", + "id": "00000000-0000-0000-0000-000000000508", }, "role": { - "id": "00000000-0000-0000-0000-000000000509", + "id": "00000000-0000-0000-0000-000000000505", }, "workspaceMember": { - "id": "00000000-0000-0000-0000-000000000513", + "id": "00000000-0000-0000-0000-000000000509", }, }, "viewGroups": {}, }, "messageParticipantRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000525", + "id": "00000000-0000-0000-0000-000000000521", "viewFieldGroups": { "general": { - "id": "00000000-0000-0000-0000-000000000523", + "id": "00000000-0000-0000-0000-000000000519", }, "other": { - "id": "00000000-0000-0000-0000-000000000524", + "id": "00000000-0000-0000-0000-000000000520", }, }, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000521", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000522", - }, - "displayName": { - "id": "00000000-0000-0000-0000-000000000518", - }, - "message": { - "id": "00000000-0000-0000-0000-000000000516", - }, - "person": { - "id": "00000000-0000-0000-0000-000000000519", - }, - "role": { "id": "00000000-0000-0000-0000-000000000517", }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000518", + }, + "displayName": { + "id": "00000000-0000-0000-0000-000000000514", + }, + "message": { + "id": "00000000-0000-0000-0000-000000000512", + }, + "person": { + "id": "00000000-0000-0000-0000-000000000515", + }, + "role": { + "id": "00000000-0000-0000-0000-000000000513", + }, "workspaceMember": { - "id": "00000000-0000-0000-0000-000000000520", + "id": "00000000-0000-0000-0000-000000000516", }, }, "viewGroups": {}, @@ -1859,47 +1847,47 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "messageThread": { "fields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000528", + "id": "00000000-0000-0000-0000-000000000524", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000533", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000530", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000527", - }, - "messageChannelMessageAssociations": { - "id": "00000000-0000-0000-0000-000000000532", - }, - "messages": { - "id": "00000000-0000-0000-0000-000000000531", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000535", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000536", - }, - "updatedAt": { "id": "00000000-0000-0000-0000-000000000529", }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000526", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000523", + }, + "messageChannelMessageAssociations": { + "id": "00000000-0000-0000-0000-000000000528", + }, + "messages": { + "id": "00000000-0000-0000-0000-000000000527", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000531", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000532", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000525", + }, "updatedBy": { - "id": "00000000-0000-0000-0000-000000000534", + "id": "00000000-0000-0000-0000-000000000530", }, }, - "id": "00000000-0000-0000-0000-000000000540", + "id": "00000000-0000-0000-0000-000000000536", "views": { "allMessageThreads": { - "id": "00000000-0000-0000-0000-000000000539", + "id": "00000000-0000-0000-0000-000000000535", "viewFieldGroups": {}, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000538", + "id": "00000000-0000-0000-0000-000000000534", }, "messages": { - "id": "00000000-0000-0000-0000-000000000537", + "id": "00000000-0000-0000-0000-000000000533", }, }, "viewGroups": {}, @@ -1909,112 +1897,112 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "note": { "fields": { "attachments": { - "id": "00000000-0000-0000-0000-000000000576", - }, - "bodyV2": { "id": "00000000-0000-0000-0000-000000000572", }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000567", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000573", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000569", - }, - "favorites": { - "id": "00000000-0000-0000-0000-000000000578", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000566", - }, - "noteTargets": { - "id": "00000000-0000-0000-0000-000000000575", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000570", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000579", - }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000577", - }, - "title": { - "id": "00000000-0000-0000-0000-000000000571", - }, - "updatedAt": { + "bodyV2": { "id": "00000000-0000-0000-0000-000000000568", }, - "updatedBy": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000563", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000569", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000565", + }, + "favorites": { "id": "00000000-0000-0000-0000-000000000574", }, + "id": { + "id": "00000000-0000-0000-0000-000000000562", + }, + "noteTargets": { + "id": "00000000-0000-0000-0000-000000000571", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000566", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000575", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000573", + }, + "title": { + "id": "00000000-0000-0000-0000-000000000567", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000564", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000570", + }, }, - "id": "00000000-0000-0000-0000-000000000599", + "id": "00000000-0000-0000-0000-000000000595", "views": { "allNotes": { - "id": "00000000-0000-0000-0000-000000000585", + "id": "00000000-0000-0000-0000-000000000581", "viewFieldGroups": {}, "viewFields": { "bodyV2": { - "id": "00000000-0000-0000-0000-000000000582", + "id": "00000000-0000-0000-0000-000000000578", }, "createdAt": { - "id": "00000000-0000-0000-0000-000000000584", + "id": "00000000-0000-0000-0000-000000000580", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000583", + "id": "00000000-0000-0000-0000-000000000579", }, "noteTargets": { - "id": "00000000-0000-0000-0000-000000000581", + "id": "00000000-0000-0000-0000-000000000577", }, "title": { - "id": "00000000-0000-0000-0000-000000000580", + "id": "00000000-0000-0000-0000-000000000576", }, }, "viewGroups": {}, }, "noteRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000598", + "id": "00000000-0000-0000-0000-000000000594", "viewFieldGroups": { "additional": { - "id": "00000000-0000-0000-0000-000000000596", + "id": "00000000-0000-0000-0000-000000000592", }, "general": { - "id": "00000000-0000-0000-0000-000000000595", + "id": "00000000-0000-0000-0000-000000000591", }, "other": { - "id": "00000000-0000-0000-0000-000000000597", + "id": "00000000-0000-0000-0000-000000000593", }, }, "viewFields": { "attachments": { - "id": "00000000-0000-0000-0000-000000000592", - }, - "bodyV2": { - "id": "00000000-0000-0000-0000-000000000589", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000586", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000587", - }, - "favorites": { - "id": "00000000-0000-0000-0000-000000000594", - }, - "noteTargets": { "id": "00000000-0000-0000-0000-000000000588", }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000593", + "bodyV2": { + "id": "00000000-0000-0000-0000-000000000585", }, - "updatedAt": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000582", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000583", + }, + "favorites": { "id": "00000000-0000-0000-0000-000000000590", }, + "noteTargets": { + "id": "00000000-0000-0000-0000-000000000584", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000589", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000586", + }, "updatedBy": { - "id": "00000000-0000-0000-0000-000000000591", + "id": "00000000-0000-0000-0000-000000000587", }, }, "viewGroups": {}, @@ -2024,62 +2012,62 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "noteTarget": { "fields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000601", + "id": "00000000-0000-0000-0000-000000000597", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000608", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000603", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000600", - }, - "note": { "id": "00000000-0000-0000-0000-000000000604", }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000599", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000596", + }, + "note": { + "id": "00000000-0000-0000-0000-000000000600", + }, "position": { - "id": "00000000-0000-0000-0000-000000000610", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000611", - }, - "targetCompany": { "id": "00000000-0000-0000-0000-000000000606", }, - "targetOpportunity": { + "searchVector": { "id": "00000000-0000-0000-0000-000000000607", }, - "targetPerson": { - "id": "00000000-0000-0000-0000-000000000605", - }, - "updatedAt": { + "targetCompany": { "id": "00000000-0000-0000-0000-000000000602", }, + "targetOpportunity": { + "id": "00000000-0000-0000-0000-000000000603", + }, + "targetPerson": { + "id": "00000000-0000-0000-0000-000000000601", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000598", + }, "updatedBy": { - "id": "00000000-0000-0000-0000-000000000609", + "id": "00000000-0000-0000-0000-000000000605", }, }, - "id": "00000000-0000-0000-0000-000000000618", + "id": "00000000-0000-0000-0000-000000000614", "views": { "allNoteTargets": { - "id": "00000000-0000-0000-0000-000000000617", + "id": "00000000-0000-0000-0000-000000000613", "viewFieldGroups": {}, "viewFields": { "id": { - "id": "00000000-0000-0000-0000-000000000612", + "id": "00000000-0000-0000-0000-000000000608", }, "note": { - "id": "00000000-0000-0000-0000-000000000613", + "id": "00000000-0000-0000-0000-000000000609", }, "targetCompany": { - "id": "00000000-0000-0000-0000-000000000615", + "id": "00000000-0000-0000-0000-000000000611", }, "targetOpportunity": { - "id": "00000000-0000-0000-0000-000000000616", + "id": "00000000-0000-0000-0000-000000000612", }, "targetPerson": { - "id": "00000000-0000-0000-0000-000000000614", + "id": "00000000-0000-0000-0000-000000000610", }, }, "viewGroups": {}, @@ -2089,192 +2077,192 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "opportunity": { "fields": { "amount": { - "id": "00000000-0000-0000-0000-000000000624", - }, - "attachments": { - "id": "00000000-0000-0000-0000-000000000636", - }, - "closeDate": { - "id": "00000000-0000-0000-0000-000000000625", - }, - "company": { - "id": "00000000-0000-0000-0000-000000000631", - }, - "createdAt": { "id": "00000000-0000-0000-0000-000000000620", }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000628", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000622", - }, - "favorites": { - "id": "00000000-0000-0000-0000-000000000633", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000619", - }, - "name": { - "id": "00000000-0000-0000-0000-000000000623", - }, - "noteTargets": { - "id": "00000000-0000-0000-0000-000000000635", - }, - "owner": { + "attachments": { "id": "00000000-0000-0000-0000-000000000632", }, - "pointOfContact": { - "id": "00000000-0000-0000-0000-000000000630", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000627", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000638", - }, - "stage": { - "id": "00000000-0000-0000-0000-000000000626", - }, - "taskTargets": { - "id": "00000000-0000-0000-0000-000000000634", - }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000637", - }, - "updatedAt": { + "closeDate": { "id": "00000000-0000-0000-0000-000000000621", }, - "updatedBy": { + "company": { + "id": "00000000-0000-0000-0000-000000000627", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000616", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000624", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000618", + }, + "favorites": { "id": "00000000-0000-0000-0000-000000000629", }, + "id": { + "id": "00000000-0000-0000-0000-000000000615", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000619", + }, + "noteTargets": { + "id": "00000000-0000-0000-0000-000000000631", + }, + "owner": { + "id": "00000000-0000-0000-0000-000000000628", + }, + "pointOfContact": { + "id": "00000000-0000-0000-0000-000000000626", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000623", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000634", + }, + "stage": { + "id": "00000000-0000-0000-0000-000000000622", + }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000630", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000633", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000617", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000625", + }, }, - "id": "00000000-0000-0000-0000-000000000677", + "id": "00000000-0000-0000-0000-000000000673", "views": { "allOpportunities": { - "id": "00000000-0000-0000-0000-000000000645", + "id": "00000000-0000-0000-0000-000000000641", "viewFieldGroups": {}, "viewFields": { "amount": { - "id": "00000000-0000-0000-0000-000000000640", + "id": "00000000-0000-0000-0000-000000000636", }, "closeDate": { - "id": "00000000-0000-0000-0000-000000000642", + "id": "00000000-0000-0000-0000-000000000638", }, "company": { - "id": "00000000-0000-0000-0000-000000000643", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000641", - }, - "name": { "id": "00000000-0000-0000-0000-000000000639", }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000637", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000635", + }, "pointOfContact": { - "id": "00000000-0000-0000-0000-000000000644", + "id": "00000000-0000-0000-0000-000000000640", }, }, "viewGroups": {}, }, "byStage": { - "id": "00000000-0000-0000-0000-000000000657", + "id": "00000000-0000-0000-0000-000000000653", "viewFieldGroups": {}, "viewFields": { "amount": { - "id": "00000000-0000-0000-0000-000000000647", + "id": "00000000-0000-0000-0000-000000000643", }, "closeDate": { - "id": "00000000-0000-0000-0000-000000000649", + "id": "00000000-0000-0000-0000-000000000645", }, "company": { - "id": "00000000-0000-0000-0000-000000000650", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000648", - }, - "name": { "id": "00000000-0000-0000-0000-000000000646", }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000644", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000642", + }, "pointOfContact": { - "id": "00000000-0000-0000-0000-000000000651", + "id": "00000000-0000-0000-0000-000000000647", }, }, "viewGroups": { "customer": { - "id": "00000000-0000-0000-0000-000000000656", - }, - "meeting": { - "id": "00000000-0000-0000-0000-000000000654", - }, - "new": { "id": "00000000-0000-0000-0000-000000000652", }, + "meeting": { + "id": "00000000-0000-0000-0000-000000000650", + }, + "new": { + "id": "00000000-0000-0000-0000-000000000648", + }, "proposal": { - "id": "00000000-0000-0000-0000-000000000655", + "id": "00000000-0000-0000-0000-000000000651", }, "screening": { - "id": "00000000-0000-0000-0000-000000000653", + "id": "00000000-0000-0000-0000-000000000649", }, }, }, "opportunityRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000676", + "id": "00000000-0000-0000-0000-000000000672", "viewFieldGroups": { "additional": { - "id": "00000000-0000-0000-0000-000000000674", + "id": "00000000-0000-0000-0000-000000000670", }, "general": { - "id": "00000000-0000-0000-0000-000000000673", + "id": "00000000-0000-0000-0000-000000000669", }, "other": { - "id": "00000000-0000-0000-0000-000000000675", + "id": "00000000-0000-0000-0000-000000000671", }, }, "viewFields": { "amount": { - "id": "00000000-0000-0000-0000-000000000658", + "id": "00000000-0000-0000-0000-000000000654", }, "attachments": { - "id": "00000000-0000-0000-0000-000000000671", + "id": "00000000-0000-0000-0000-000000000667", }, "closeDate": { - "id": "00000000-0000-0000-0000-000000000659", + "id": "00000000-0000-0000-0000-000000000655", }, "company": { - "id": "00000000-0000-0000-0000-000000000661", + "id": "00000000-0000-0000-0000-000000000657", }, "createdAt": { - "id": "00000000-0000-0000-0000-000000000664", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000665", - }, - "favorites": { - "id": "00000000-0000-0000-0000-000000000668", - }, - "noteTargets": { - "id": "00000000-0000-0000-0000-000000000670", - }, - "owner": { - "id": "00000000-0000-0000-0000-000000000663", - }, - "pointOfContact": { - "id": "00000000-0000-0000-0000-000000000662", - }, - "stage": { "id": "00000000-0000-0000-0000-000000000660", }, - "taskTargets": { - "id": "00000000-0000-0000-0000-000000000669", + "createdBy": { + "id": "00000000-0000-0000-0000-000000000661", }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000672", + "favorites": { + "id": "00000000-0000-0000-0000-000000000664", }, - "updatedAt": { + "noteTargets": { "id": "00000000-0000-0000-0000-000000000666", }, + "owner": { + "id": "00000000-0000-0000-0000-000000000659", + }, + "pointOfContact": { + "id": "00000000-0000-0000-0000-000000000658", + }, + "stage": { + "id": "00000000-0000-0000-0000-000000000656", + }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000665", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000668", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000662", + }, "updatedBy": { - "id": "00000000-0000-0000-0000-000000000667", + "id": "00000000-0000-0000-0000-000000000663", }, }, "viewGroups": {}, @@ -2284,199 +2272,199 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "person": { "fields": { "attachments": { - "id": "00000000-0000-0000-0000-000000000699", - }, - "avatarFile": { - "id": "00000000-0000-0000-0000-000000000690", - }, - "avatarUrl": { - "id": "00000000-0000-0000-0000-000000000689", - }, - "calendarEventParticipants": { - "id": "00000000-0000-0000-0000-000000000701", - }, - "city": { - "id": "00000000-0000-0000-0000-000000000688", - }, - "company": { - "id": "00000000-0000-0000-0000-000000000694", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000679", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000692", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000681", - }, - "emails": { - "id": "00000000-0000-0000-0000-000000000683", - }, - "favorites": { - "id": "00000000-0000-0000-0000-000000000698", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000678", - }, - "jobTitle": { - "id": "00000000-0000-0000-0000-000000000686", - }, - "linkedinLink": { - "id": "00000000-0000-0000-0000-000000000684", - }, - "messageParticipants": { - "id": "00000000-0000-0000-0000-000000000700", - }, - "name": { - "id": "00000000-0000-0000-0000-000000000682", - }, - "noteTargets": { - "id": "00000000-0000-0000-0000-000000000697", - }, - "phones": { - "id": "00000000-0000-0000-0000-000000000687", - }, - "pointOfContactForOpportunities": { "id": "00000000-0000-0000-0000-000000000695", }, - "position": { - "id": "00000000-0000-0000-0000-000000000691", + "avatarFile": { + "id": "00000000-0000-0000-0000-000000000686", }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000703", - }, - "taskTargets": { - "id": "00000000-0000-0000-0000-000000000696", - }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000702", - }, - "updatedAt": { - "id": "00000000-0000-0000-0000-000000000680", - }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000693", - }, - "xLink": { + "avatarUrl": { "id": "00000000-0000-0000-0000-000000000685", }, + "calendarEventParticipants": { + "id": "00000000-0000-0000-0000-000000000697", + }, + "city": { + "id": "00000000-0000-0000-0000-000000000684", + }, + "company": { + "id": "00000000-0000-0000-0000-000000000690", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000675", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000688", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000677", + }, + "emails": { + "id": "00000000-0000-0000-0000-000000000679", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000694", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000674", + }, + "jobTitle": { + "id": "00000000-0000-0000-0000-000000000682", + }, + "linkedinLink": { + "id": "00000000-0000-0000-0000-000000000680", + }, + "messageParticipants": { + "id": "00000000-0000-0000-0000-000000000696", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000678", + }, + "noteTargets": { + "id": "00000000-0000-0000-0000-000000000693", + }, + "phones": { + "id": "00000000-0000-0000-0000-000000000683", + }, + "pointOfContactForOpportunities": { + "id": "00000000-0000-0000-0000-000000000691", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000687", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000699", + }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000692", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000698", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000676", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000689", + }, + "xLink": { + "id": "00000000-0000-0000-0000-000000000681", + }, }, - "id": "00000000-0000-0000-0000-000000000740", + "id": "00000000-0000-0000-0000-000000000736", "views": { "allPeople": { - "id": "00000000-0000-0000-0000-000000000714", + "id": "00000000-0000-0000-0000-000000000710", "viewFieldGroups": {}, "viewFields": { "city": { - "id": "00000000-0000-0000-0000-000000000710", - }, - "company": { - "id": "00000000-0000-0000-0000-000000000707", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000709", - }, - "createdBy": { "id": "00000000-0000-0000-0000-000000000706", }, - "emails": { + "company": { + "id": "00000000-0000-0000-0000-000000000703", + }, + "createdAt": { "id": "00000000-0000-0000-0000-000000000705", }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000702", + }, + "emails": { + "id": "00000000-0000-0000-0000-000000000701", + }, "jobTitle": { - "id": "00000000-0000-0000-0000-000000000711", + "id": "00000000-0000-0000-0000-000000000707", }, "linkedinLink": { - "id": "00000000-0000-0000-0000-000000000712", - }, - "name": { - "id": "00000000-0000-0000-0000-000000000704", - }, - "phones": { "id": "00000000-0000-0000-0000-000000000708", }, + "name": { + "id": "00000000-0000-0000-0000-000000000700", + }, + "phones": { + "id": "00000000-0000-0000-0000-000000000704", + }, "xLink": { - "id": "00000000-0000-0000-0000-000000000713", + "id": "00000000-0000-0000-0000-000000000709", }, }, "viewGroups": {}, }, "personRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000739", + "id": "00000000-0000-0000-0000-000000000735", "viewFieldGroups": { "additional": { - "id": "00000000-0000-0000-0000-000000000737", + "id": "00000000-0000-0000-0000-000000000733", }, "general": { - "id": "00000000-0000-0000-0000-000000000736", + "id": "00000000-0000-0000-0000-000000000732", }, "other": { - "id": "00000000-0000-0000-0000-000000000738", + "id": "00000000-0000-0000-0000-000000000734", }, }, "viewFields": { "attachments": { - "id": "00000000-0000-0000-0000-000000000732", - }, - "avatarFile": { - "id": "00000000-0000-0000-0000-000000000727", - }, - "avatarUrl": { - "id": "00000000-0000-0000-0000-000000000722", - }, - "calendarEventParticipants": { - "id": "00000000-0000-0000-0000-000000000734", - }, - "city": { - "id": "00000000-0000-0000-0000-000000000721", - }, - "company": { - "id": "00000000-0000-0000-0000-000000000717", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000723", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000724", - }, - "emails": { - "id": "00000000-0000-0000-0000-000000000715", - }, - "favorites": { - "id": "00000000-0000-0000-0000-000000000731", - }, - "jobTitle": { - "id": "00000000-0000-0000-0000-000000000718", - }, - "linkedinLink": { - "id": "00000000-0000-0000-0000-000000000719", - }, - "messageParticipants": { - "id": "00000000-0000-0000-0000-000000000733", - }, - "noteTargets": { - "id": "00000000-0000-0000-0000-000000000730", - }, - "phones": { - "id": "00000000-0000-0000-0000-000000000716", - }, - "pointOfContactForOpportunities": { "id": "00000000-0000-0000-0000-000000000728", }, - "taskTargets": { + "avatarFile": { + "id": "00000000-0000-0000-0000-000000000723", + }, + "avatarUrl": { + "id": "00000000-0000-0000-0000-000000000718", + }, + "calendarEventParticipants": { + "id": "00000000-0000-0000-0000-000000000730", + }, + "city": { + "id": "00000000-0000-0000-0000-000000000717", + }, + "company": { + "id": "00000000-0000-0000-0000-000000000713", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000719", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000720", + }, + "emails": { + "id": "00000000-0000-0000-0000-000000000711", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000727", + }, + "jobTitle": { + "id": "00000000-0000-0000-0000-000000000714", + }, + "linkedinLink": { + "id": "00000000-0000-0000-0000-000000000715", + }, + "messageParticipants": { "id": "00000000-0000-0000-0000-000000000729", }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000735", - }, - "updatedAt": { - "id": "00000000-0000-0000-0000-000000000725", - }, - "updatedBy": { + "noteTargets": { "id": "00000000-0000-0000-0000-000000000726", }, + "phones": { + "id": "00000000-0000-0000-0000-000000000712", + }, + "pointOfContactForOpportunities": { + "id": "00000000-0000-0000-0000-000000000724", + }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000725", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000731", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000721", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000722", + }, "xLink": { - "id": "00000000-0000-0000-0000-000000000720", + "id": "00000000-0000-0000-0000-000000000716", }, }, "viewGroups": {}, @@ -2486,212 +2474,212 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "task": { "fields": { "assignee": { - "id": "00000000-0000-0000-0000-000000000754", - }, - "attachments": { - "id": "00000000-0000-0000-0000-000000000753", - }, - "bodyV2": { - "id": "00000000-0000-0000-0000-000000000747", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000742", - }, - "createdBy": { "id": "00000000-0000-0000-0000-000000000750", }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000744", - }, - "dueAt": { - "id": "00000000-0000-0000-0000-000000000748", - }, - "favorites": { - "id": "00000000-0000-0000-0000-000000000756", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000741", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000745", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000757", - }, - "status": { + "attachments": { "id": "00000000-0000-0000-0000-000000000749", }, - "taskTargets": { - "id": "00000000-0000-0000-0000-000000000752", - }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000755", - }, - "title": { - "id": "00000000-0000-0000-0000-000000000746", - }, - "updatedAt": { + "bodyV2": { "id": "00000000-0000-0000-0000-000000000743", }, - "updatedBy": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000738", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000746", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000740", + }, + "dueAt": { + "id": "00000000-0000-0000-0000-000000000744", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000752", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000737", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000741", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000753", + }, + "status": { + "id": "00000000-0000-0000-0000-000000000745", + }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000748", + }, + "timelineActivities": { "id": "00000000-0000-0000-0000-000000000751", }, + "title": { + "id": "00000000-0000-0000-0000-000000000742", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000739", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000747", + }, }, - "id": "00000000-0000-0000-0000-000000000804", + "id": "00000000-0000-0000-0000-000000000800", "views": { "allTasks": { - "id": "00000000-0000-0000-0000-000000000766", + "id": "00000000-0000-0000-0000-000000000762", "viewFieldGroups": {}, "viewFields": { "assignee": { - "id": "00000000-0000-0000-0000-000000000763", - }, - "bodyV2": { - "id": "00000000-0000-0000-0000-000000000764", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000765", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000761", - }, - "dueAt": { - "id": "00000000-0000-0000-0000-000000000762", - }, - "status": { "id": "00000000-0000-0000-0000-000000000759", }, - "taskTargets": { + "bodyV2": { "id": "00000000-0000-0000-0000-000000000760", }, - "title": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000761", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000757", + }, + "dueAt": { "id": "00000000-0000-0000-0000-000000000758", }, + "status": { + "id": "00000000-0000-0000-0000-000000000755", + }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000756", + }, + "title": { + "id": "00000000-0000-0000-0000-000000000754", + }, }, "viewGroups": {}, }, "assignedToMe": { - "id": "00000000-0000-0000-0000-000000000778", + "id": "00000000-0000-0000-0000-000000000774", "viewFieldGroups": {}, "viewFields": { "assignee": { - "id": "00000000-0000-0000-0000-000000000771", + "id": "00000000-0000-0000-0000-000000000767", }, "bodyV2": { - "id": "00000000-0000-0000-0000-000000000772", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000773", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000769", - }, - "dueAt": { - "id": "00000000-0000-0000-0000-000000000770", - }, - "taskTargets": { "id": "00000000-0000-0000-0000-000000000768", }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000769", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000765", + }, + "dueAt": { + "id": "00000000-0000-0000-0000-000000000766", + }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000764", + }, "title": { - "id": "00000000-0000-0000-0000-000000000767", + "id": "00000000-0000-0000-0000-000000000763", }, }, "viewGroups": { "done": { - "id": "00000000-0000-0000-0000-000000000776", + "id": "00000000-0000-0000-0000-000000000772", }, "empty": { - "id": "00000000-0000-0000-0000-000000000777", + "id": "00000000-0000-0000-0000-000000000773", }, "inProgress": { - "id": "00000000-0000-0000-0000-000000000775", + "id": "00000000-0000-0000-0000-000000000771", }, "todo": { - "id": "00000000-0000-0000-0000-000000000774", + "id": "00000000-0000-0000-0000-000000000770", }, }, }, "byStatus": { - "id": "00000000-0000-0000-0000-000000000787", + "id": "00000000-0000-0000-0000-000000000783", "viewFieldGroups": {}, "viewFields": { "assignee": { - "id": "00000000-0000-0000-0000-000000000782", + "id": "00000000-0000-0000-0000-000000000778", }, "createdAt": { - "id": "00000000-0000-0000-0000-000000000783", + "id": "00000000-0000-0000-0000-000000000779", }, "dueAt": { - "id": "00000000-0000-0000-0000-000000000781", + "id": "00000000-0000-0000-0000-000000000777", }, "status": { - "id": "00000000-0000-0000-0000-000000000780", + "id": "00000000-0000-0000-0000-000000000776", }, "title": { - "id": "00000000-0000-0000-0000-000000000779", + "id": "00000000-0000-0000-0000-000000000775", }, }, "viewGroups": { "done": { - "id": "00000000-0000-0000-0000-000000000786", + "id": "00000000-0000-0000-0000-000000000782", }, "inProgress": { - "id": "00000000-0000-0000-0000-000000000785", + "id": "00000000-0000-0000-0000-000000000781", }, "todo": { - "id": "00000000-0000-0000-0000-000000000784", + "id": "00000000-0000-0000-0000-000000000780", }, }, }, "taskRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000803", + "id": "00000000-0000-0000-0000-000000000799", "viewFieldGroups": { "additional": { - "id": "00000000-0000-0000-0000-000000000801", + "id": "00000000-0000-0000-0000-000000000797", }, "general": { - "id": "00000000-0000-0000-0000-000000000800", + "id": "00000000-0000-0000-0000-000000000796", }, "other": { - "id": "00000000-0000-0000-0000-000000000802", + "id": "00000000-0000-0000-0000-000000000798", }, }, "viewFields": { "assignee": { - "id": "00000000-0000-0000-0000-000000000790", + "id": "00000000-0000-0000-0000-000000000786", }, "attachments": { - "id": "00000000-0000-0000-0000-000000000797", - }, - "bodyV2": { - "id": "00000000-0000-0000-0000-000000000794", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000791", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000792", - }, - "dueAt": { - "id": "00000000-0000-0000-0000-000000000788", - }, - "favorites": { - "id": "00000000-0000-0000-0000-000000000799", - }, - "status": { - "id": "00000000-0000-0000-0000-000000000789", - }, - "taskTargets": { "id": "00000000-0000-0000-0000-000000000793", }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000798", + "bodyV2": { + "id": "00000000-0000-0000-0000-000000000790", }, - "updatedAt": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000787", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000788", + }, + "dueAt": { + "id": "00000000-0000-0000-0000-000000000784", + }, + "favorites": { "id": "00000000-0000-0000-0000-000000000795", }, + "status": { + "id": "00000000-0000-0000-0000-000000000785", + }, + "taskTargets": { + "id": "00000000-0000-0000-0000-000000000789", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000794", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000791", + }, "updatedBy": { - "id": "00000000-0000-0000-0000-000000000796", + "id": "00000000-0000-0000-0000-000000000792", }, }, "viewGroups": {}, @@ -2701,62 +2689,62 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "taskTarget": { "fields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000806", + "id": "00000000-0000-0000-0000-000000000802", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000813", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000808", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000805", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000815", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000816", - }, - "targetCompany": { - "id": "00000000-0000-0000-0000-000000000811", - }, - "targetOpportunity": { - "id": "00000000-0000-0000-0000-000000000812", - }, - "targetPerson": { - "id": "00000000-0000-0000-0000-000000000810", - }, - "task": { "id": "00000000-0000-0000-0000-000000000809", }, - "updatedAt": { + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000804", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000801", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000811", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000812", + }, + "targetCompany": { "id": "00000000-0000-0000-0000-000000000807", }, + "targetOpportunity": { + "id": "00000000-0000-0000-0000-000000000808", + }, + "targetPerson": { + "id": "00000000-0000-0000-0000-000000000806", + }, + "task": { + "id": "00000000-0000-0000-0000-000000000805", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000803", + }, "updatedBy": { - "id": "00000000-0000-0000-0000-000000000814", + "id": "00000000-0000-0000-0000-000000000810", }, }, - "id": "00000000-0000-0000-0000-000000000823", + "id": "00000000-0000-0000-0000-000000000819", "views": { "allTaskTargets": { - "id": "00000000-0000-0000-0000-000000000822", + "id": "00000000-0000-0000-0000-000000000818", "viewFieldGroups": {}, "viewFields": { "id": { - "id": "00000000-0000-0000-0000-000000000817", + "id": "00000000-0000-0000-0000-000000000813", }, "targetCompany": { - "id": "00000000-0000-0000-0000-000000000820", + "id": "00000000-0000-0000-0000-000000000816", }, "targetOpportunity": { - "id": "00000000-0000-0000-0000-000000000821", + "id": "00000000-0000-0000-0000-000000000817", }, "targetPerson": { - "id": "00000000-0000-0000-0000-000000000819", + "id": "00000000-0000-0000-0000-000000000815", }, "task": { - "id": "00000000-0000-0000-0000-000000000818", + "id": "00000000-0000-0000-0000-000000000814", }, }, "viewGroups": {}, @@ -2766,125 +2754,125 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "timelineActivity": { "fields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000825", + "id": "00000000-0000-0000-0000-000000000821", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000844", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000827", - }, - "happensAt": { - "id": "00000000-0000-0000-0000-000000000828", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000824", - }, - "linkedObjectMetadataId": { - "id": "00000000-0000-0000-0000-000000000843", - }, - "linkedRecordCachedName": { - "id": "00000000-0000-0000-0000-000000000841", - }, - "linkedRecordId": { - "id": "00000000-0000-0000-0000-000000000842", - }, - "name": { - "id": "00000000-0000-0000-0000-000000000829", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000846", - }, - "properties": { - "id": "00000000-0000-0000-0000-000000000830", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000847", - }, - "targetCompany": { - "id": "00000000-0000-0000-0000-000000000833", - }, - "targetDashboard": { "id": "00000000-0000-0000-0000-000000000840", }, - "targetNote": { - "id": "00000000-0000-0000-0000-000000000836", + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000823", }, - "targetOpportunity": { - "id": "00000000-0000-0000-0000-000000000834", + "happensAt": { + "id": "00000000-0000-0000-0000-000000000824", }, - "targetPerson": { - "id": "00000000-0000-0000-0000-000000000832", + "id": { + "id": "00000000-0000-0000-0000-000000000820", }, - "targetTask": { - "id": "00000000-0000-0000-0000-000000000835", - }, - "targetWorkflow": { - "id": "00000000-0000-0000-0000-000000000837", - }, - "targetWorkflowRun": { + "linkedObjectMetadataId": { "id": "00000000-0000-0000-0000-000000000839", }, - "targetWorkflowVersion": { + "linkedRecordCachedName": { + "id": "00000000-0000-0000-0000-000000000837", + }, + "linkedRecordId": { "id": "00000000-0000-0000-0000-000000000838", }, - "updatedAt": { + "name": { + "id": "00000000-0000-0000-0000-000000000825", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000842", + }, + "properties": { "id": "00000000-0000-0000-0000-000000000826", }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000845", + "searchVector": { + "id": "00000000-0000-0000-0000-000000000843", }, - "workspaceMember": { + "targetCompany": { + "id": "00000000-0000-0000-0000-000000000829", + }, + "targetDashboard": { + "id": "00000000-0000-0000-0000-000000000836", + }, + "targetNote": { + "id": "00000000-0000-0000-0000-000000000832", + }, + "targetOpportunity": { + "id": "00000000-0000-0000-0000-000000000830", + }, + "targetPerson": { + "id": "00000000-0000-0000-0000-000000000828", + }, + "targetTask": { "id": "00000000-0000-0000-0000-000000000831", }, + "targetWorkflow": { + "id": "00000000-0000-0000-0000-000000000833", + }, + "targetWorkflowRun": { + "id": "00000000-0000-0000-0000-000000000835", + }, + "targetWorkflowVersion": { + "id": "00000000-0000-0000-0000-000000000834", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000822", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000841", + }, + "workspaceMember": { + "id": "00000000-0000-0000-0000-000000000827", + }, }, - "id": "00000000-0000-0000-0000-000000000863", + "id": "00000000-0000-0000-0000-000000000859", "views": { "allTimelineActivities": { - "id": "00000000-0000-0000-0000-000000000862", + "id": "00000000-0000-0000-0000-000000000858", "viewFieldGroups": {}, "viewFields": { "happensAt": { - "id": "00000000-0000-0000-0000-000000000849", + "id": "00000000-0000-0000-0000-000000000845", }, "linkedRecordCachedName": { - "id": "00000000-0000-0000-0000-000000000852", - }, - "name": { "id": "00000000-0000-0000-0000-000000000848", }, + "name": { + "id": "00000000-0000-0000-0000-000000000844", + }, "properties": { - "id": "00000000-0000-0000-0000-000000000850", + "id": "00000000-0000-0000-0000-000000000846", }, "targetCompany": { - "id": "00000000-0000-0000-0000-000000000854", + "id": "00000000-0000-0000-0000-000000000850", }, "targetDashboard": { - "id": "00000000-0000-0000-0000-000000000861", - }, - "targetNote": { "id": "00000000-0000-0000-0000-000000000857", }, - "targetOpportunity": { - "id": "00000000-0000-0000-0000-000000000855", - }, - "targetPerson": { + "targetNote": { "id": "00000000-0000-0000-0000-000000000853", }, + "targetOpportunity": { + "id": "00000000-0000-0000-0000-000000000851", + }, + "targetPerson": { + "id": "00000000-0000-0000-0000-000000000849", + }, "targetTask": { - "id": "00000000-0000-0000-0000-000000000856", + "id": "00000000-0000-0000-0000-000000000852", }, "targetWorkflow": { - "id": "00000000-0000-0000-0000-000000000858", + "id": "00000000-0000-0000-0000-000000000854", }, "targetWorkflowRun": { - "id": "00000000-0000-0000-0000-000000000860", + "id": "00000000-0000-0000-0000-000000000856", }, "targetWorkflowVersion": { - "id": "00000000-0000-0000-0000-000000000859", + "id": "00000000-0000-0000-0000-000000000855", }, "workspaceMember": { - "id": "00000000-0000-0000-0000-000000000851", + "id": "00000000-0000-0000-0000-000000000847", }, }, "viewGroups": {}, @@ -2894,80 +2882,80 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "workflow": { "fields": { "attachments": { - "id": "00000000-0000-0000-0000-000000000877", - }, - "automatedTriggers": { - "id": "00000000-0000-0000-0000-000000000874", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000865", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000878", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000867", - }, - "favorites": { - "id": "00000000-0000-0000-0000-000000000875", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000864", - }, - "lastPublishedVersionId": { - "id": "00000000-0000-0000-0000-000000000869", - }, - "name": { - "id": "00000000-0000-0000-0000-000000000868", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000871", - }, - "runs": { "id": "00000000-0000-0000-0000-000000000873", }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000880", - }, - "statuses": { + "automatedTriggers": { "id": "00000000-0000-0000-0000-000000000870", }, - "timelineActivities": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000861", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000874", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000863", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000871", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000860", + }, + "lastPublishedVersionId": { + "id": "00000000-0000-0000-0000-000000000865", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000864", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000867", + }, + "runs": { + "id": "00000000-0000-0000-0000-000000000869", + }, + "searchVector": { "id": "00000000-0000-0000-0000-000000000876", }, - "updatedAt": { + "statuses": { "id": "00000000-0000-0000-0000-000000000866", }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000879", - }, - "versions": { + "timelineActivities": { "id": "00000000-0000-0000-0000-000000000872", }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000862", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000875", + }, + "versions": { + "id": "00000000-0000-0000-0000-000000000868", + }, }, - "id": "00000000-0000-0000-0000-000000000888", + "id": "00000000-0000-0000-0000-000000000884", "views": { "allWorkflows": { - "id": "00000000-0000-0000-0000-000000000887", + "id": "00000000-0000-0000-0000-000000000883", "viewFieldGroups": {}, "viewFields": { "createdBy": { - "id": "00000000-0000-0000-0000-000000000884", + "id": "00000000-0000-0000-0000-000000000880", }, "name": { - "id": "00000000-0000-0000-0000-000000000881", + "id": "00000000-0000-0000-0000-000000000877", }, "runs": { - "id": "00000000-0000-0000-0000-000000000886", - }, - "statuses": { "id": "00000000-0000-0000-0000-000000000882", }, + "statuses": { + "id": "00000000-0000-0000-0000-000000000878", + }, "updatedAt": { - "id": "00000000-0000-0000-0000-000000000883", + "id": "00000000-0000-0000-0000-000000000879", }, "versions": { - "id": "00000000-0000-0000-0000-000000000885", + "id": "00000000-0000-0000-0000-000000000881", }, }, "viewGroups": {}, @@ -2977,48 +2965,74 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "workflowAutomatedTrigger": { "fields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000890", + "id": "00000000-0000-0000-0000-000000000886", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000896", - }, - "deletedAt": { "id": "00000000-0000-0000-0000-000000000892", }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000888", + }, "id": { - "id": "00000000-0000-0000-0000-000000000889", + "id": "00000000-0000-0000-0000-000000000885", }, "position": { - "id": "00000000-0000-0000-0000-000000000898", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000899", - }, - "settings": { "id": "00000000-0000-0000-0000-000000000894", }, - "type": { - "id": "00000000-0000-0000-0000-000000000893", - }, - "updatedAt": { - "id": "00000000-0000-0000-0000-000000000891", - }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000897", - }, - "workflow": { + "searchVector": { "id": "00000000-0000-0000-0000-000000000895", }, + "settings": { + "id": "00000000-0000-0000-0000-000000000890", + }, + "type": { + "id": "00000000-0000-0000-0000-000000000889", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000887", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000893", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000891", + }, }, - "id": "00000000-0000-0000-0000-000000000911", + "id": "00000000-0000-0000-0000-000000000907", "views": { "allWorkflowAutomatedTriggers": { - "id": "00000000-0000-0000-0000-000000000903", + "id": "00000000-0000-0000-0000-000000000899", "viewFieldGroups": {}, + "viewFields": { + "createdAt": { + "id": "00000000-0000-0000-0000-000000000898", + }, + "type": { + "id": "00000000-0000-0000-0000-000000000896", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000897", + }, + }, + "viewGroups": {}, + }, + "workflowAutomatedTriggerRecordPageFields": { + "id": "00000000-0000-0000-0000-000000000906", + "viewFieldGroups": { + "general": { + "id": "00000000-0000-0000-0000-000000000904", + }, + "other": { + "id": "00000000-0000-0000-0000-000000000905", + }, + }, "viewFields": { "createdAt": { "id": "00000000-0000-0000-0000-000000000902", }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000000903", + }, "type": { "id": "00000000-0000-0000-0000-000000000900", }, @@ -3028,171 +3042,145 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object }, "viewGroups": {}, }, - "workflowAutomatedTriggerRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000910", - "viewFieldGroups": { - "general": { - "id": "00000000-0000-0000-0000-000000000908", - }, - "other": { - "id": "00000000-0000-0000-0000-000000000909", - }, - }, - "viewFields": { - "createdAt": { - "id": "00000000-0000-0000-0000-000000000906", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000907", - }, - "type": { - "id": "00000000-0000-0000-0000-000000000904", - }, - "workflow": { - "id": "00000000-0000-0000-0000-000000000905", - }, - }, - "viewGroups": {}, - }, }, }, "workflowRun": { "fields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000913", + "id": "00000000-0000-0000-0000-000000000909", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000924", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000915", - }, - "endedAt": { - "id": "00000000-0000-0000-0000-000000000921", - }, - "enqueuedAt": { - "id": "00000000-0000-0000-0000-000000000919", - }, - "favorites": { - "id": "00000000-0000-0000-0000-000000000927", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000912", - }, - "name": { - "id": "00000000-0000-0000-0000-000000000916", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000923", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000929", - }, - "startedAt": { "id": "00000000-0000-0000-0000-000000000920", }, - "state": { - "id": "00000000-0000-0000-0000-000000000926", + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000911", }, - "status": { - "id": "00000000-0000-0000-0000-000000000922", - }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000928", - }, - "updatedAt": { - "id": "00000000-0000-0000-0000-000000000914", - }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000925", - }, - "workflow": { - "id": "00000000-0000-0000-0000-000000000918", - }, - "workflowVersion": { + "endedAt": { "id": "00000000-0000-0000-0000-000000000917", }, + "enqueuedAt": { + "id": "00000000-0000-0000-0000-000000000915", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000923", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000908", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000912", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000919", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000925", + }, + "startedAt": { + "id": "00000000-0000-0000-0000-000000000916", + }, + "state": { + "id": "00000000-0000-0000-0000-000000000922", + }, + "status": { + "id": "00000000-0000-0000-0000-000000000918", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000924", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000910", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000921", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000914", + }, + "workflowVersion": { + "id": "00000000-0000-0000-0000-000000000913", + }, }, - "id": "00000000-0000-0000-0000-000000000954", + "id": "00000000-0000-0000-0000-000000000950", "views": { "allWorkflowRuns": { - "id": "00000000-0000-0000-0000-000000000936", + "id": "00000000-0000-0000-0000-000000000932", "viewFieldGroups": {}, "viewFields": { "createdBy": { - "id": "00000000-0000-0000-0000-000000000934", - }, - "name": { "id": "00000000-0000-0000-0000-000000000930", }, + "name": { + "id": "00000000-0000-0000-0000-000000000926", + }, "startedAt": { - "id": "00000000-0000-0000-0000-000000000933", + "id": "00000000-0000-0000-0000-000000000929", }, "status": { - "id": "00000000-0000-0000-0000-000000000932", + "id": "00000000-0000-0000-0000-000000000928", }, "workflow": { - "id": "00000000-0000-0000-0000-000000000931", + "id": "00000000-0000-0000-0000-000000000927", }, "workflowVersion": { - "id": "00000000-0000-0000-0000-000000000935", + "id": "00000000-0000-0000-0000-000000000931", }, }, "viewGroups": {}, }, "workflowRunRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000953", + "id": "00000000-0000-0000-0000-000000000949", "viewFieldGroups": { "additional": { - "id": "00000000-0000-0000-0000-000000000951", + "id": "00000000-0000-0000-0000-000000000947", }, "general": { - "id": "00000000-0000-0000-0000-000000000950", + "id": "00000000-0000-0000-0000-000000000946", }, "other": { - "id": "00000000-0000-0000-0000-000000000952", + "id": "00000000-0000-0000-0000-000000000948", }, }, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000942", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000000943", - }, - "endedAt": { - "id": "00000000-0000-0000-0000-000000000941", - }, - "enqueuedAt": { - "id": "00000000-0000-0000-0000-000000000944", - }, - "favorites": { - "id": "00000000-0000-0000-0000-000000000948", - }, - "startedAt": { - "id": "00000000-0000-0000-0000-000000000940", - }, - "state": { - "id": "00000000-0000-0000-0000-000000000945", - }, - "status": { - "id": "00000000-0000-0000-0000-000000000937", - }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000949", - }, - "updatedAt": { - "id": "00000000-0000-0000-0000-000000000946", - }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000947", - }, - "workflow": { "id": "00000000-0000-0000-0000-000000000938", }, - "workflowVersion": { + "createdBy": { "id": "00000000-0000-0000-0000-000000000939", }, + "endedAt": { + "id": "00000000-0000-0000-0000-000000000937", + }, + "enqueuedAt": { + "id": "00000000-0000-0000-0000-000000000940", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000000944", + }, + "startedAt": { + "id": "00000000-0000-0000-0000-000000000936", + }, + "state": { + "id": "00000000-0000-0000-0000-000000000941", + }, + "status": { + "id": "00000000-0000-0000-0000-000000000933", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000945", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000942", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000943", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000934", + }, + "workflowVersion": { + "id": "00000000-0000-0000-0000-000000000935", + }, }, "viewGroups": {}, }, @@ -3201,124 +3189,124 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "workflowVersion": { "fields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000956", + "id": "00000000-0000-0000-0000-000000000952", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000969", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000958", - }, - "favorites": { - "id": "00000000-0000-0000-0000-000000000966", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000955", - }, - "name": { - "id": "00000000-0000-0000-0000-000000000959", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000963", - }, - "runs": { - "id": "00000000-0000-0000-0000-000000000964", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000000968", - }, - "status": { - "id": "00000000-0000-0000-0000-000000000962", - }, - "steps": { "id": "00000000-0000-0000-0000-000000000965", }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000967", + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000954", }, - "trigger": { - "id": "00000000-0000-0000-0000-000000000961", + "favorites": { + "id": "00000000-0000-0000-0000-000000000962", }, - "updatedAt": { - "id": "00000000-0000-0000-0000-000000000957", + "id": { + "id": "00000000-0000-0000-0000-000000000951", }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000000970", + "name": { + "id": "00000000-0000-0000-0000-000000000955", }, - "workflow": { + "position": { + "id": "00000000-0000-0000-0000-000000000959", + }, + "runs": { "id": "00000000-0000-0000-0000-000000000960", }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000000964", + }, + "status": { + "id": "00000000-0000-0000-0000-000000000958", + }, + "steps": { + "id": "00000000-0000-0000-0000-000000000961", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000000963", + }, + "trigger": { + "id": "00000000-0000-0000-0000-000000000957", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000953", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000000966", + }, + "workflow": { + "id": "00000000-0000-0000-0000-000000000956", + }, }, - "id": "00000000-0000-0000-0000-000000000992", + "id": "00000000-0000-0000-0000-000000000988", "views": { "allWorkflowVersions": { - "id": "00000000-0000-0000-0000-000000000976", + "id": "00000000-0000-0000-0000-000000000972", "viewFieldGroups": {}, "viewFields": { "name": { - "id": "00000000-0000-0000-0000-000000000971", + "id": "00000000-0000-0000-0000-000000000967", }, "runs": { - "id": "00000000-0000-0000-0000-000000000975", + "id": "00000000-0000-0000-0000-000000000971", }, "status": { - "id": "00000000-0000-0000-0000-000000000973", + "id": "00000000-0000-0000-0000-000000000969", }, "updatedAt": { - "id": "00000000-0000-0000-0000-000000000974", + "id": "00000000-0000-0000-0000-000000000970", }, "workflow": { - "id": "00000000-0000-0000-0000-000000000972", + "id": "00000000-0000-0000-0000-000000000968", }, }, "viewGroups": {}, }, "workflowVersionRecordPageFields": { - "id": "00000000-0000-0000-0000-000000000991", + "id": "00000000-0000-0000-0000-000000000987", "viewFieldGroups": { "additional": { - "id": "00000000-0000-0000-0000-000000000989", + "id": "00000000-0000-0000-0000-000000000985", }, "general": { - "id": "00000000-0000-0000-0000-000000000988", + "id": "00000000-0000-0000-0000-000000000984", }, "other": { - "id": "00000000-0000-0000-0000-000000000990", + "id": "00000000-0000-0000-0000-000000000986", }, }, "viewFields": { "createdAt": { - "id": "00000000-0000-0000-0000-000000000980", + "id": "00000000-0000-0000-0000-000000000976", }, "createdBy": { - "id": "00000000-0000-0000-0000-000000000982", + "id": "00000000-0000-0000-0000-000000000978", }, "favorites": { - "id": "00000000-0000-0000-0000-000000000986", + "id": "00000000-0000-0000-0000-000000000982", }, "runs": { - "id": "00000000-0000-0000-0000-000000000985", - }, - "status": { - "id": "00000000-0000-0000-0000-000000000977", - }, - "steps": { "id": "00000000-0000-0000-0000-000000000981", }, + "status": { + "id": "00000000-0000-0000-0000-000000000973", + }, + "steps": { + "id": "00000000-0000-0000-0000-000000000977", + }, "timelineActivities": { - "id": "00000000-0000-0000-0000-000000000987", - }, - "trigger": { - "id": "00000000-0000-0000-0000-000000000979", - }, - "updatedAt": { "id": "00000000-0000-0000-0000-000000000983", }, + "trigger": { + "id": "00000000-0000-0000-0000-000000000975", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000979", + }, "updatedBy": { - "id": "00000000-0000-0000-0000-000000000984", + "id": "00000000-0000-0000-0000-000000000980", }, "workflow": { - "id": "00000000-0000-0000-0000-000000000978", + "id": "00000000-0000-0000-0000-000000000974", }, }, "viewGroups": {}, @@ -3328,129 +3316,129 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "workspaceMember": { "fields": { "accountOwnerForCompanies": { - "id": "00000000-0000-0000-0000-000000001007", - }, - "assignedTasks": { - "id": "00000000-0000-0000-0000-000000001004", - }, - "avatarUrl": { - "id": "00000000-0000-0000-0000-000000001001", - }, - "blocklist": { - "id": "00000000-0000-0000-0000-000000001010", - }, - "calendarEventParticipants": { - "id": "00000000-0000-0000-0000-000000001011", - }, - "calendarStartDay": { - "id": "00000000-0000-0000-0000-000000001017", - }, - "colorScheme": { - "id": "00000000-0000-0000-0000-000000000999", - }, - "connectedAccounts": { - "id": "00000000-0000-0000-0000-000000001008", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000000994", - }, - "createdBy": { - "id": "00000000-0000-0000-0000-000000001019", - }, - "dateFormat": { - "id": "00000000-0000-0000-0000-000000001014", - }, - "deletedAt": { - "id": "00000000-0000-0000-0000-000000000996", - }, - "favorites": { - "id": "00000000-0000-0000-0000-000000001006", - }, - "id": { - "id": "00000000-0000-0000-0000-000000000993", - }, - "locale": { - "id": "00000000-0000-0000-0000-000000001000", - }, - "messageParticipants": { - "id": "00000000-0000-0000-0000-000000001009", - }, - "name": { - "id": "00000000-0000-0000-0000-000000000998", - }, - "numberFormat": { - "id": "00000000-0000-0000-0000-000000001018", - }, - "ownedOpportunities": { - "id": "00000000-0000-0000-0000-000000001005", - }, - "position": { - "id": "00000000-0000-0000-0000-000000000997", - }, - "searchVector": { - "id": "00000000-0000-0000-0000-000000001016", - }, - "timeFormat": { - "id": "00000000-0000-0000-0000-000000001015", - }, - "timeZone": { - "id": "00000000-0000-0000-0000-000000001013", - }, - "timelineActivities": { - "id": "00000000-0000-0000-0000-000000001012", - }, - "updatedAt": { - "id": "00000000-0000-0000-0000-000000000995", - }, - "updatedBy": { - "id": "00000000-0000-0000-0000-000000001020", - }, - "userEmail": { - "id": "00000000-0000-0000-0000-000000001002", - }, - "userId": { "id": "00000000-0000-0000-0000-000000001003", }, + "assignedTasks": { + "id": "00000000-0000-0000-0000-000000001000", + }, + "avatarUrl": { + "id": "00000000-0000-0000-0000-000000000997", + }, + "blocklist": { + "id": "00000000-0000-0000-0000-000000001006", + }, + "calendarEventParticipants": { + "id": "00000000-0000-0000-0000-000000001007", + }, + "calendarStartDay": { + "id": "00000000-0000-0000-0000-000000001013", + }, + "colorScheme": { + "id": "00000000-0000-0000-0000-000000000995", + }, + "connectedAccounts": { + "id": "00000000-0000-0000-0000-000000001004", + }, + "createdAt": { + "id": "00000000-0000-0000-0000-000000000990", + }, + "createdBy": { + "id": "00000000-0000-0000-0000-000000001015", + }, + "dateFormat": { + "id": "00000000-0000-0000-0000-000000001010", + }, + "deletedAt": { + "id": "00000000-0000-0000-0000-000000000992", + }, + "favorites": { + "id": "00000000-0000-0000-0000-000000001002", + }, + "id": { + "id": "00000000-0000-0000-0000-000000000989", + }, + "locale": { + "id": "00000000-0000-0000-0000-000000000996", + }, + "messageParticipants": { + "id": "00000000-0000-0000-0000-000000001005", + }, + "name": { + "id": "00000000-0000-0000-0000-000000000994", + }, + "numberFormat": { + "id": "00000000-0000-0000-0000-000000001014", + }, + "ownedOpportunities": { + "id": "00000000-0000-0000-0000-000000001001", + }, + "position": { + "id": "00000000-0000-0000-0000-000000000993", + }, + "searchVector": { + "id": "00000000-0000-0000-0000-000000001012", + }, + "timeFormat": { + "id": "00000000-0000-0000-0000-000000001011", + }, + "timeZone": { + "id": "00000000-0000-0000-0000-000000001009", + }, + "timelineActivities": { + "id": "00000000-0000-0000-0000-000000001008", + }, + "updatedAt": { + "id": "00000000-0000-0000-0000-000000000991", + }, + "updatedBy": { + "id": "00000000-0000-0000-0000-000000001016", + }, + "userEmail": { + "id": "00000000-0000-0000-0000-000000000998", + }, + "userId": { + "id": "00000000-0000-0000-0000-000000000999", + }, }, - "id": "00000000-0000-0000-0000-000000001033", + "id": "00000000-0000-0000-0000-000000001029", "views": { "allWorkspaceMembers": { - "id": "00000000-0000-0000-0000-000000001032", + "id": "00000000-0000-0000-0000-000000001028", "viewFieldGroups": {}, "viewFields": { "assignedTasks": { - "id": "00000000-0000-0000-0000-000000001031", - }, - "avatarUrl": { - "id": "00000000-0000-0000-0000-000000001023", - }, - "colorScheme": { - "id": "00000000-0000-0000-0000-000000001024", - }, - "createdAt": { - "id": "00000000-0000-0000-0000-000000001029", - }, - "dateFormat": { "id": "00000000-0000-0000-0000-000000001027", }, - "locale": { + "avatarUrl": { + "id": "00000000-0000-0000-0000-000000001019", + }, + "colorScheme": { + "id": "00000000-0000-0000-0000-000000001020", + }, + "createdAt": { "id": "00000000-0000-0000-0000-000000001025", }, - "name": { + "dateFormat": { + "id": "00000000-0000-0000-0000-000000001023", + }, + "locale": { "id": "00000000-0000-0000-0000-000000001021", }, + "name": { + "id": "00000000-0000-0000-0000-000000001017", + }, "ownedOpportunities": { - "id": "00000000-0000-0000-0000-000000001030", - }, - "timeFormat": { - "id": "00000000-0000-0000-0000-000000001028", - }, - "timeZone": { "id": "00000000-0000-0000-0000-000000001026", }, - "userEmail": { + "timeFormat": { + "id": "00000000-0000-0000-0000-000000001024", + }, + "timeZone": { "id": "00000000-0000-0000-0000-000000001022", }, + "userEmail": { + "id": "00000000-0000-0000-0000-000000001018", + }, }, "viewGroups": {}, }, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/build-standard-flat-field-metadata-maps.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/build-standard-flat-field-metadata-maps.util.ts index f1d49b65b1..15dc3d8d58 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/build-standard-flat-field-metadata-maps.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/build-standard-flat-field-metadata-maps.util.ts @@ -17,8 +17,8 @@ import { buildDashboardStandardFlatFieldMetadatas } from 'src/engine/workspace-m import { buildFavoriteFolderStandardFlatFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-favorite-folder-standard-flat-field-metadata.util'; import { buildFavoriteStandardFlatFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-favorite-standard-flat-field-metadata.util'; import { buildMessageChannelMessageAssociationMessageFolderStandardFlatFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-message-association-message-folder-standard-flat-field-metadata.util'; -import { buildMessageChannelMessageAssociationStandardFlatFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-message-association-standard-flat-field-metadata.util'; import { buildMessageChannelStandardFlatFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-standard-flat-field-metadata.util'; +import { buildMessageChannelMessageAssociationStandardFlatFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-message-association-standard-flat-field-metadata.util'; import { buildMessageFolderStandardFlatFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-folder-standard-flat-field-metadata.util'; import { buildMessageParticipantStandardFlatFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-participant-standard-flat-field-metadata.util'; import { buildMessageStandardFlatFieldMetadatas } from 'src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-standard-flat-field-metadata.util'; @@ -44,9 +44,9 @@ type StandardFieldBuilder

= ( const STANDARD_FLAT_FIELD_METADATA_BUILDERS_BY_OBJECT_NAME = { attachment: buildAttachmentStandardFlatFieldMetadatas, blocklist: buildBlocklistStandardFlatFieldMetadatas, + calendarChannel: buildCalendarChannelStandardFlatFieldMetadatas, calendarChannelEventAssociation: buildCalendarChannelEventAssociationStandardFlatFieldMetadatas, - calendarChannel: buildCalendarChannelStandardFlatFieldMetadatas, calendarEventParticipant: buildCalendarEventParticipantStandardFlatFieldMetadatas, calendarEvent: buildCalendarEventStandardFlatFieldMetadatas, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-calendar-channel-event-association-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-calendar-channel-event-association-standard-flat-field-metadata.util.ts index 0b0a9a33cd..8408020551 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-calendar-channel-event-association-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-calendar-channel-event-association-standard-flat-field-metadata.util.ts @@ -234,25 +234,17 @@ export const buildCalendarChannelEventAssociationStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), - calendarChannel: createStandardRelationFieldFlatMetadata({ + calendarChannelId: createStandardFieldFlatMetadata({ objectName, workspaceId, context: { - type: FieldMetadataType.RELATION, - morphId: null, - fieldName: 'calendarChannel', + fieldName: 'calendarChannelId', + type: FieldMetadataType.UUID, label: i18nLabel(msg`Channel ID`), description: i18nLabel(msg`Channel ID`), icon: 'IconCalendar', isNullable: false, isUIReadOnly: true, - targetObjectName: 'calendarChannel', - targetFieldName: 'calendarChannelEventAssociations', - settings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: RelationOnDeleteAction.CASCADE, - joinColumnName: 'calendarChannelId', - }, }, standardObjectMetadataRelatedEntityIds, dependencyFlatEntityMaps, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-calendar-channel-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-calendar-channel-standard-flat-field-metadata.util.ts index 502a998fcc..5f5f50bae2 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-calendar-channel-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-calendar-channel-standard-flat-field-metadata.util.ts @@ -561,27 +561,4 @@ export const buildCalendarChannelStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), - calendarChannelEventAssociations: createStandardRelationFieldFlatMetadata({ - objectName, - workspaceId, - context: { - type: FieldMetadataType.RELATION, - morphId: null, - fieldName: 'calendarChannelEventAssociations', - label: i18nLabel(msg`Calendar Channel Event Associations`), - description: i18nLabel(msg`Calendar Channel Event Associations`), - icon: 'IconCalendar', - isNullable: false, - isUIReadOnly: true, - targetObjectName: 'calendarChannelEventAssociation', - targetFieldName: 'calendarChannel', - settings: { - relationType: RelationType.ONE_TO_MANY, - }, - }, - standardObjectMetadataRelatedEntityIds, - dependencyFlatEntityMaps, - twentyStandardApplicationId, - now, - }), }); diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-message-association-message-folder-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-message-association-message-folder-standard-flat-field-metadata.util.ts index ad59ce3673..14c6f3cf1a 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-message-association-message-folder-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-message-association-message-folder-standard-flat-field-metadata.util.ts @@ -229,25 +229,17 @@ export const buildMessageChannelMessageAssociationMessageFolderStandardFlatField twentyStandardApplicationId, now, }), - messageFolder: createStandardRelationFieldFlatMetadata({ + messageFolderId: createStandardFieldFlatMetadata({ objectName, workspaceId, context: { - type: FieldMetadataType.RELATION, - morphId: null, - fieldName: 'messageFolder', + fieldName: 'messageFolderId', + type: FieldMetadataType.UUID, label: i18nLabel(msg`Message Folder`), description: i18nLabel(msg`Message Folder`), icon: 'IconFolder', isNullable: false, isUIReadOnly: true, - targetObjectName: 'messageFolder', - targetFieldName: 'messageChannelMessageAssociationMessageFolders', - settings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: RelationOnDeleteAction.CASCADE, - joinColumnName: 'messageFolderId', - }, }, standardObjectMetadataRelatedEntityIds, dependencyFlatEntityMaps, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-message-association-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-message-association-standard-flat-field-metadata.util.ts index f526053d49..da0918f14e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-message-association-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-message-association-standard-flat-field-metadata.util.ts @@ -273,25 +273,17 @@ export const buildMessageChannelMessageAssociationStandardFlatFieldMetadatas = twentyStandardApplicationId, now, }), - messageChannel: createStandardRelationFieldFlatMetadata({ + messageChannelId: createStandardFieldFlatMetadata({ objectName, workspaceId, context: { - type: FieldMetadataType.RELATION, - morphId: null, - fieldName: 'messageChannel', + fieldName: 'messageChannelId', + type: FieldMetadataType.UUID, label: i18nLabel(msg`Message Channel Id`), description: i18nLabel(msg`Message Channel Id`), icon: 'IconHash', isNullable: true, isUIReadOnly: true, - targetObjectName: 'messageChannel', - targetFieldName: 'messageChannelMessageAssociations', - settings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: RelationOnDeleteAction.CASCADE, - joinColumnName: 'messageChannelId', - }, }, standardObjectMetadataRelatedEntityIds, dependencyFlatEntityMaps, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-standard-flat-field-metadata.util.ts index bdca70cb55..034f74548d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-channel-standard-flat-field-metadata.util.ts @@ -724,50 +724,4 @@ export const buildMessageChannelStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), - messageChannelMessageAssociations: createStandardRelationFieldFlatMetadata({ - objectName, - workspaceId, - context: { - type: FieldMetadataType.RELATION, - morphId: null, - fieldName: 'messageChannelMessageAssociations', - label: i18nLabel(msg`Message Channel Association`), - description: i18nLabel(msg`Messages from the channel.`), - icon: 'IconMessage', - isNullable: true, - isUIReadOnly: true, - targetObjectName: 'messageChannelMessageAssociation', - targetFieldName: 'messageChannel', - settings: { - relationType: RelationType.ONE_TO_MANY, - }, - }, - standardObjectMetadataRelatedEntityIds, - dependencyFlatEntityMaps, - twentyStandardApplicationId, - now, - }), - messageFolders: createStandardRelationFieldFlatMetadata({ - objectName, - workspaceId, - context: { - type: FieldMetadataType.RELATION, - morphId: null, - fieldName: 'messageFolders', - label: i18nLabel(msg`Message Folders`), - description: i18nLabel(msg`Message Folders`), - icon: 'IconFolder', - isNullable: true, - isUIReadOnly: true, - targetObjectName: 'messageFolder', - targetFieldName: 'messageChannel', - settings: { - relationType: RelationType.ONE_TO_MANY, - }, - }, - standardObjectMetadataRelatedEntityIds, - dependencyFlatEntityMaps, - twentyStandardApplicationId, - now, - }), }); diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-folder-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-folder-standard-flat-field-metadata.util.ts index 23dd43f57c..1e3eeb8922 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-folder-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-message-folder-standard-flat-field-metadata.util.ts @@ -1,11 +1,6 @@ import { msg } from '@lingui/core/macro'; import { i18nLabel } from 'src/engine/workspace-manager/twenty-standard-application/utils/i18n-label.util'; -import { - DateDisplayFormat, - FieldMetadataType, - RelationOnDeleteAction, - RelationType, -} from 'twenty-shared/types'; +import { DateDisplayFormat, FieldMetadataType } from 'twenty-shared/types'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type'; @@ -13,7 +8,6 @@ import { type CreateStandardFieldArgs, createStandardFieldFlatMetadata, } from 'src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/create-standard-field-flat-metadata.util'; -import { createStandardRelationFieldFlatMetadata } from 'src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/create-standard-relation-field-flat-metadata.util'; import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/utils/get-ts-vector-column-expression.util'; import { SEARCH_FIELDS_FOR_MESSAGE_FOLDER } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; @@ -333,55 +327,21 @@ export const buildMessageFolderStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), - messageChannel: createStandardRelationFieldFlatMetadata({ + messageChannelId: createStandardFieldFlatMetadata({ objectName, workspaceId, context: { - type: FieldMetadataType.RELATION, - morphId: null, - fieldName: 'messageChannel', + fieldName: 'messageChannelId', + type: FieldMetadataType.UUID, label: i18nLabel(msg`Message Channel`), description: i18nLabel(msg`Message Channel`), icon: 'IconMessage', isNullable: false, isUIReadOnly: true, - targetObjectName: 'messageChannel', - targetFieldName: 'messageFolders', - settings: { - relationType: RelationType.MANY_TO_ONE, - onDelete: RelationOnDeleteAction.CASCADE, - joinColumnName: 'messageChannelId', - }, }, standardObjectMetadataRelatedEntityIds, dependencyFlatEntityMaps, twentyStandardApplicationId, now, }), - messageChannelMessageAssociationMessageFolders: - createStandardRelationFieldFlatMetadata({ - objectName, - workspaceId, - context: { - type: FieldMetadataType.RELATION, - morphId: null, - fieldName: 'messageChannelMessageAssociationMessageFolders', - label: i18nLabel(msg`Message Association Folders`), - description: i18nLabel( - msg`Message Association Folders (supports multiple folders/labels)`, - ), - icon: 'IconFolders', - isNullable: true, - isUIReadOnly: true, - targetObjectName: 'messageChannelMessageAssociationMessageFolder', - targetFieldName: 'messageFolder', - settings: { - relationType: RelationType.ONE_TO_MANY, - }, - }, - standardObjectMetadataRelatedEntityIds, - dependencyFlatEntityMaps, - twentyStandardApplicationId, - now, - }), }); diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-calendar-channel-event-association-standard-flat-index-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-calendar-channel-event-association-standard-flat-index-metadata.util.ts index 4861c9d369..eb1077e875 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-calendar-channel-event-association-standard-flat-index-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-calendar-channel-event-association-standard-flat-index-metadata.util.ts @@ -24,7 +24,7 @@ export const buildCalendarChannelEventAssociationStandardFlatIndexMetadatas = ({ workspaceId, context: { indexName: 'calendarChannelIdIndex', - relatedFieldNames: ['calendarChannel'], + relatedFieldNames: ['calendarChannelId'], }, standardObjectMetadataRelatedEntityIds, dependencyFlatEntityMaps, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-message-channel-message-association-message-folder-standard-flat-index-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-message-channel-message-association-message-folder-standard-flat-index-metadata.util.ts index ec51c4b3d5..abe0d47dda 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-message-channel-message-association-message-folder-standard-flat-index-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-message-channel-message-association-message-folder-standard-flat-index-metadata.util.ts @@ -37,7 +37,7 @@ export const buildMessageChannelMessageAssociationMessageFolderStandardFlatIndex workspaceId, context: { indexName: 'messageFolderIdIndex', - relatedFieldNames: ['messageFolder'], + relatedFieldNames: ['messageFolderId'], }, standardObjectMetadataRelatedEntityIds, dependencyFlatEntityMaps, @@ -53,7 +53,7 @@ export const buildMessageChannelMessageAssociationMessageFolderStandardFlatIndex 'messageChannelMessageAssociationIdMessageFolderIdUniqueIndex', relatedFieldNames: [ 'messageChannelMessageAssociation', - 'messageFolder', + 'messageFolderId', ], isUnique: true, indexWhereClause: '"deletedAt" IS NULL', diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-message-channel-message-association-standard-flat-index-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-message-channel-message-association-standard-flat-index-metadata.util.ts index 3eff55568c..0600c74534 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-message-channel-message-association-standard-flat-index-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-message-channel-message-association-standard-flat-index-metadata.util.ts @@ -25,7 +25,7 @@ export const buildMessageChannelMessageAssociationStandardFlatIndexMetadatas = workspaceId, context: { indexName: 'messageChannelIdIndex', - relatedFieldNames: ['messageChannel'], + relatedFieldNames: ['messageChannelId'], }, standardObjectMetadataRelatedEntityIds, dependencyFlatEntityMaps, @@ -49,7 +49,7 @@ export const buildMessageChannelMessageAssociationStandardFlatIndexMetadatas = workspaceId, context: { indexName: 'messageChannelIdMessageIdUniqueIndex', - relatedFieldNames: ['messageChannel', 'message'], + relatedFieldNames: ['messageChannelId', 'message'], isUnique: true, indexWhereClause: '"deletedAt" IS NULL', }, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-message-folder-standard-flat-index-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-message-folder-standard-flat-index-metadata.util.ts index 2962e8b532..53116dd88d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-message-folder-standard-flat-index-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-message-folder-standard-flat-index-metadata.util.ts @@ -21,7 +21,7 @@ export const buildMessageFolderStandardFlatIndexMetadatas = ({ workspaceId, context: { indexName: 'messageChannelIdIndex', - relatedFieldNames: ['messageChannel'], + relatedFieldNames: ['messageChannelId'], }, standardObjectMetadataRelatedEntityIds, dependencyFlatEntityMaps, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-calendar-channel-event-association-view-fields.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-calendar-channel-event-association-view-fields.util.ts index 4dcb6cf958..99bbb62908 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-calendar-channel-event-association-view-fields.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-calendar-channel-event-association-view-fields.util.ts @@ -17,8 +17,8 @@ export const computeStandardCalendarChannelEventAssociationViewFields = ( objectName: 'calendarChannelEventAssociation', context: { viewName: 'allCalendarChannelEventAssociations', - viewFieldName: 'calendarChannel', - fieldName: 'calendarChannel', + viewFieldName: 'calendarChannelId', + fieldName: 'calendarChannelId', position: 0, isVisible: true, size: 150, @@ -70,8 +70,8 @@ export const computeStandardCalendarChannelEventAssociationViewFields = ( objectName: 'calendarChannelEventAssociation', context: { viewName: 'calendarChannelEventAssociationRecordPageFields', - viewFieldName: 'calendarChannel', - fieldName: 'calendarChannel', + viewFieldName: 'calendarChannelId', + fieldName: 'calendarChannelId', position: 0, isVisible: true, size: 150, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-channel-message-association-message-folder-view-fields.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-channel-message-association-message-folder-view-fields.util.ts index 10ff14a553..634f54ac29 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-channel-message-association-message-folder-view-fields.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-channel-message-association-message-folder-view-fields.util.ts @@ -31,8 +31,8 @@ export const computeStandardMessageChannelMessageAssociationMessageFolderViewFie objectName: 'messageChannelMessageAssociationMessageFolder', context: { viewName: 'allMessageChannelMessageAssociationMessageFolders', - viewFieldName: 'messageFolder', - fieldName: 'messageFolder', + viewFieldName: 'messageFolderId', + fieldName: 'messageFolderId', position: 1, isVisible: true, size: 150, @@ -74,8 +74,8 @@ export const computeStandardMessageChannelMessageAssociationMessageFolderViewFie context: { viewName: 'messageChannelMessageAssociationMessageFolderRecordPageFields', - viewFieldName: 'messageFolder', - fieldName: 'messageFolder', + viewFieldName: 'messageFolderId', + fieldName: 'messageFolderId', position: 1, isVisible: true, size: 150, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-channel-message-association-view-fields.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-channel-message-association-view-fields.util.ts index 3ea71d87ee..a8e5c18c71 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-channel-message-association-view-fields.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-channel-message-association-view-fields.util.ts @@ -17,8 +17,8 @@ export const computeStandardMessageChannelMessageAssociationViewFields = ( objectName: 'messageChannelMessageAssociation', context: { viewName: 'allMessageChannelMessageAssociations', - viewFieldName: 'messageChannel', - fieldName: 'messageChannel', + viewFieldName: 'messageChannelId', + fieldName: 'messageChannelId', position: 0, isVisible: true, size: 150, @@ -83,8 +83,8 @@ export const computeStandardMessageChannelMessageAssociationViewFields = ( objectName: 'messageChannelMessageAssociation', context: { viewName: 'messageChannelMessageAssociationRecordPageFields', - viewFieldName: 'messageChannel', - fieldName: 'messageChannel', + viewFieldName: 'messageChannelId', + fieldName: 'messageChannelId', position: 0, isVisible: true, size: 150, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-folder-view-fields.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-folder-view-fields.util.ts index c5aac4d3a5..5a00060e7e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-folder-view-fields.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/view-field/compute-standard-message-folder-view-fields.util.ts @@ -25,8 +25,8 @@ export const computeStandardMessageFolderViewFields = ( objectName: 'messageFolder', context: { viewName: 'allMessageFolders', - viewFieldName: 'messageChannel', - fieldName: 'messageChannel', + viewFieldName: 'messageChannelId', + fieldName: 'messageChannelId', position: 1, isVisible: true, size: 150, @@ -75,8 +75,8 @@ export const computeStandardMessageFolderViewFields = ( objectName: 'messageFolder', context: { viewName: 'messageFolderRecordPageFields', - viewFieldName: 'messageChannel', - fieldName: 'messageChannel', + viewFieldName: 'messageChannelId', + fieldName: 'messageChannelId', position: 1, isVisible: true, size: 150, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/default-feature-flags.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/default-feature-flags.ts index 21f9f9896f..d5b155544d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/default-feature-flags.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/default-feature-flags.ts @@ -1,6 +1,5 @@ import { FeatureFlagKey } from 'twenty-shared/types'; export const DEFAULT_FEATURE_FLAGS = [ - FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, FeatureFlagKey.IS_DATASOURCE_MIGRATED, ] as const satisfies FeatureFlagKey[]; diff --git a/packages/twenty-server/src/modules/calendar/blocklist-manager/calendar-blocklist-manager.module.ts b/packages/twenty-server/src/modules/calendar/blocklist-manager/calendar-blocklist-manager.module.ts index a58879f7d3..61fe7fbf1c 100644 --- a/packages/twenty-server/src/modules/calendar/blocklist-manager/calendar-blocklist-manager.module.ts +++ b/packages/twenty-server/src/modules/calendar/blocklist-manager/calendar-blocklist-manager.module.ts @@ -1,6 +1,8 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; -import { CalendarChannelDataAccessModule } from 'src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { BlocklistItemDeleteCalendarEventsJob } from 'src/modules/calendar/blocklist-manager/jobs/blocklist-item-delete-calendar-events.job'; import { BlocklistReimportCalendarEventsJob } from 'src/modules/calendar/blocklist-manager/jobs/blocklist-reimport-calendar-events.job'; import { CalendarBlocklistListener } from 'src/modules/calendar/blocklist-manager/listeners/calendar-blocklist.listener'; @@ -11,7 +13,7 @@ import { CalendarCommonModule } from 'src/modules/calendar/common/calendar-commo imports: [ CalendarEventCleanerModule, CalendarCommonModule, - CalendarChannelDataAccessModule, + TypeOrmModule.forFeature([CalendarChannelEntity, UserWorkspaceEntity]), ], providers: [ CalendarBlocklistListener, diff --git a/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-item-delete-calendar-events.job.ts b/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-item-delete-calendar-events.job.ts index f06df455cd..3d19574b04 100644 --- a/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-item-delete-calendar-events.job.ts +++ b/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-item-delete-calendar-events.job.ts @@ -1,13 +1,15 @@ import { Scope } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; -import { And, Any, ILike, In, Not, Or } from 'typeorm'; +import { And, Any, ILike, In, Not, Or, type Repository } from 'typeorm'; import { type ObjectRecordCreateEvent } from 'twenty-shared/database-events'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { type WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; @@ -26,7 +28,10 @@ export type BlocklistItemDeleteCalendarEventsJobData = WorkspaceEventBatch< export class BlocklistItemDeleteCalendarEventsJob { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, private readonly calendarEventCleanerService: CalendarEventCleanerService, ) {} @@ -78,6 +83,12 @@ export class BlocklistItemDeleteCalendarEventsJob { 'calendarChannelEventAssociation', ); + const workspaceMemberRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workspaceMember', + ); + for (const workspaceMemberId of handlesToDeleteByWorkspaceMemberIdMap.keys()) { const handles = handlesToDeleteByWorkspaceMemberIdMap.get(workspaceMemberId); @@ -86,22 +97,37 @@ export class BlocklistItemDeleteCalendarEventsJob { continue; } - const calendarChannels = - await this.calendarChannelDataAccessService.find(workspaceId, { - select: { - id: true, - handle: true, - connectedAccount: { - handleAliases: true, - }, + const workspaceMember = await workspaceMemberRepository.findOne({ + where: { id: workspaceMemberId }, + }); + + if (!workspaceMember) { + continue; + } + + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { userId: workspaceMember.userId, workspaceId }, + select: ['id'], + }); + + if (!userWorkspace) { + continue; + } + + const calendarChannels = await this.calendarChannelRepository.find({ + select: { + id: true, + handle: true, + connectedAccount: { + handleAliases: true, }, - where: { - connectedAccount: { - accountOwnerId: workspaceMemberId, - }, - }, - relations: ['connectedAccount'], - }); + }, + where: { + connectedAccount: { userWorkspaceId: userWorkspace.id }, + workspaceId, + }, + relations: ['connectedAccount'], + }); for (const calendarChannel of calendarChannels) { const calendarChannelHandles = [calendarChannel.handle]; diff --git a/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-reimport-calendar-events.job.ts b/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-reimport-calendar-events.job.ts index a0c81fea2b..05b9c16389 100644 --- a/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-reimport-calendar-events.job.ts +++ b/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-reimport-calendar-events.job.ts @@ -1,18 +1,21 @@ import { Scope } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { Not } from 'typeorm'; +import { Not, type Repository } from 'typeorm'; import { type ObjectRecordDeleteEvent } from 'twenty-shared/database-events'; +import { CalendarChannelSyncStage } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { type WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; import { type BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; -import { CalendarChannelSyncStage } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; export type BlocklistReimportCalendarEventsJobData = WorkspaceEventBatch< ObjectRecordDeleteEvent @@ -25,7 +28,10 @@ export type BlocklistReimportCalendarEventsJobData = WorkspaceEventBatch< export class BlocklistReimportCalendarEventsJob { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService, ) {} @@ -36,22 +42,43 @@ export class BlocklistReimportCalendarEventsJob { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workspaceMemberRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workspaceMember', + ); + for (const eventPayload of data.events) { const workspaceMemberId = eventPayload.properties.before.workspaceMemberId; - const calendarChannels = - await this.calendarChannelDataAccessService.find(workspaceId, { - select: ['id'], - where: { - connectedAccount: { - accountOwnerId: workspaceMemberId, - }, - syncStage: Not( - CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, - ), - }, - }); + const workspaceMember = await workspaceMemberRepository.findOne({ + where: { id: workspaceMemberId }, + }); + + if (!isDefined(workspaceMember)) { + continue; + } + + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { userId: workspaceMember.userId, workspaceId }, + select: ['id'], + }); + + if (!isDefined(userWorkspace)) { + continue; + } + + const calendarChannels = await this.calendarChannelRepository.find({ + select: ['id'], + where: { + connectedAccount: { userWorkspaceId: userWorkspace.id }, + syncStage: Not( + CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, + ), + workspaceId, + }, + }); await this.calendarChannelSyncStatusService.resetAndMarkAsCalendarEventListFetchPending( calendarChannels.map((calendarChannel) => calendarChannel.id), diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/calendar-event-cleaner.module.ts b/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/calendar-event-cleaner.module.ts index bcdd886a3f..25b9c22601 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/calendar-event-cleaner.module.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/calendar-event-cleaner.module.ts @@ -3,6 +3,7 @@ import { Module } from '@nestjs/common'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { CalendarChannelDeletionCleanupJob } from 'src/modules/calendar/calendar-event-cleaner/jobs/calendar-channel-deletion-cleanup.job'; import { DeleteConnectedAccountAssociatedCalendarDataJob } from 'src/modules/calendar/calendar-event-cleaner/jobs/delete-connected-account-associated-calendar-data.job'; + import { CalendarEventCleanerCalendarChannelListener } from 'src/modules/calendar/calendar-event-cleaner/listeners/calendar-event-cleaner-calendar-channel.listener'; import { CalendarEventCleanerConnectedAccountListener } from 'src/modules/calendar/calendar-event-cleaner/listeners/calendar-event-cleaner-connected-account.listener'; import { CalendarEventCleanerService } from 'src/modules/calendar/calendar-event-cleaner/services/calendar-event-cleaner.service'; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/jobs/calendar-channel-deletion-cleanup.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/jobs/calendar-channel-deletion-cleanup.job.ts index d4253802d4..8c2a8bf775 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/jobs/calendar-channel-deletion-cleanup.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/jobs/calendar-channel-deletion-cleanup.job.ts @@ -1,8 +1,5 @@ import { Logger, Scope } from '@nestjs/common'; -import { FeatureFlagKey } from 'twenty-shared/types'; - -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; @@ -22,20 +19,10 @@ export class CalendarChannelDeletionCleanupJob { constructor( private readonly calendarEventCleanerService: CalendarEventCleanerService, - private readonly featureFlagService: FeatureFlagService, ) {} @Process(CalendarChannelDeletionCleanupJob.name) async handle(data: CalendarChannelDeletionCleanupJobData): Promise { - const isMigrated = await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - data.workspaceId, - ); - - if (!isMigrated) { - return; - } - this.logger.debug( `WorkspaceId: ${data.workspaceId} Cleaning up calendar channel event associations for channel ${data.calendarChannelId}`, ); diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/listeners/calendar-event-cleaner-calendar-channel.listener.ts b/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/listeners/calendar-event-cleaner-calendar-channel.listener.ts index 5050c1b5ea..17956af363 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/listeners/calendar-event-cleaner-calendar-channel.listener.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/listeners/calendar-event-cleaner-calendar-channel.listener.ts @@ -6,7 +6,7 @@ import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decora import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; -import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; +import { type CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { CalendarChannelDeletionCleanupJob, type CalendarChannelDeletionCleanupJobData, @@ -24,7 +24,7 @@ export class CalendarEventCleanerCalendarChannelListener { @OnDatabaseBatchEvent('calendarChannel', DatabaseEventAction.DESTROYED) async handleDestroyedEvent( payload: WorkspaceEventBatch< - ObjectRecordDeleteEvent + ObjectRecordDeleteEvent >, ) { await Promise.all( diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/listeners/calendar-event-cleaner-connected-account.listener.ts b/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/listeners/calendar-event-cleaner-connected-account.listener.ts index 01dd38e910..859e685350 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/listeners/calendar-event-cleaner-connected-account.listener.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-cleaner/listeners/calendar-event-cleaner-connected-account.listener.ts @@ -10,7 +10,7 @@ import { DeleteConnectedAccountAssociatedCalendarDataJob, type DeleteConnectedAccountAssociatedCalendarDataJobData, } from 'src/modules/calendar/calendar-event-cleaner/jobs/delete-connected-account-associated-calendar-data.job'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; @@ -24,7 +24,7 @@ export class CalendarEventCleanerConnectedAccountListener { @OnDatabaseBatchEvent('connectedAccount', DatabaseEventAction.DESTROYED) async handleDestroyedEvent( payload: WorkspaceEventBatch< - ObjectRecordDeleteEvent + ObjectRecordDeleteEvent >, ) { await Promise.all( diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts index 973e228b4b..ac8910d1cf 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts @@ -7,8 +7,9 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature- import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; -import { CalendarChannelDataAccessModule } from 'src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module'; -import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity'; @@ -39,6 +40,7 @@ import { CalendarEventParticipantManagerModule } from 'src/modules/calendar/cale import { CalendarCommonModule } from 'src/modules/calendar/common/calendar-common.module'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module'; +import { EmailAliasManagerModule } from 'src/modules/connected-account/email-alias-manager/email-alias-manager.module'; import { RefreshTokensManagerModule } from 'src/modules/connected-account/refresh-tokens-manager/connected-account-refresh-tokens-manager.module'; @Module({ @@ -49,6 +51,9 @@ import { RefreshTokensManagerModule } from 'src/modules/connected-account/refres FeatureFlagEntity, WorkspaceEntity, DataSourceEntity, + CalendarChannelEntity, + ConnectedAccountEntity, + UserWorkspaceEntity, ]), WorkspaceDataSourceModule, CalendarEventCleanerModule, @@ -58,10 +63,9 @@ import { RefreshTokensManagerModule } from 'src/modules/connected-account/refres BillingModule, RefreshTokensManagerModule, ConnectedAccountModule, + EmailAliasManagerModule, CalendarCommonModule, MetricsModule, - CalendarChannelDataAccessModule, - ConnectedAccountDataAccessModule, FeatureFlagModule, ], providers: [ diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/commands/calendar-trigger-event-list-fetch.command.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/commands/calendar-trigger-event-list-fetch.command.ts index 6674b94e1f..e69ca9ade0 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/commands/calendar-trigger-event-list-fetch.command.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/commands/calendar-trigger-event-list-fetch.command.ts @@ -1,18 +1,20 @@ import { Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { Command, CommandRunner, Option } from 'nest-commander'; +import { Repository } from 'typeorm'; +import { CalendarChannelSyncStage } from 'twenty-shared/types'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { CalendarEventListFetchJob, type CalendarEventListFetchJobData, } from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job'; -import { CalendarChannelSyncStage } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; type CalendarTriggerEventListFetchCommandOptions = { workspaceId: string; @@ -31,7 +33,8 @@ export class CalendarTriggerEventListFetchCommand extends CommandRunner { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, @InjectMessageQueue(MessageQueue.calendarQueue) private readonly messageQueueService: MessageQueueService, ) { @@ -51,17 +54,14 @@ export class CalendarTriggerEventListFetchCommand extends CommandRunner { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - const calendarChannels = await this.calendarChannelDataAccessService.find( - workspaceId, - { - where: { - isSyncEnabled: true, - syncStage: - CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, - ...(calendarChannelId ? { id: calendarChannelId } : {}), - }, + const calendarChannels = await this.calendarChannelRepository.find({ + where: { + isSyncEnabled: true, + syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, + ...(calendarChannelId ? { id: calendarChannelId } : {}), + workspaceId, }, - ); + }); if (calendarChannels.length === 0) { this.logger.warn( @@ -76,9 +76,8 @@ export class CalendarTriggerEventListFetchCommand extends CommandRunner { ); for (const calendarChannel of calendarChannels) { - await this.calendarChannelDataAccessService.update( - workspaceId, - { id: calendarChannel.id }, + await this.calendarChannelRepository.update( + { id: calendarChannel.id, workspaceId }, { syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED, diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-event-list-fetch.cron.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-event-list-fetch.cron.job.ts index 1627f3e4ad..890d33af0d 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-event-list-fetch.cron.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-event-list-fetch.cron.job.ts @@ -1,24 +1,21 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; -import { FeatureFlagKey } from 'twenty-shared/types'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { DataSource, Repository } from 'typeorm'; +import { CalendarChannelSyncStage } from 'twenty-shared/types'; import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; import { CalendarEventListFetchJob, type CalendarEventListFetchJobData, } from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job'; -import { CalendarChannelSyncStage } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; export const CALENDAR_EVENT_LIST_FETCH_CRON_PATTERN = '*/5 * * * *'; @@ -34,7 +31,6 @@ export class CalendarEventListFetchCronJob { private readonly exceptionHandlerService: ExceptionHandlerService, @InjectDataSource() private readonly coreDataSource: DataSource, - private readonly featureFlagService: FeatureFlagService, ) {} @Process(CalendarEventListFetchCronJob.name) @@ -53,22 +49,11 @@ export class CalendarEventListFetchCronJob { try { const now = new Date().toISOString(); - // TODO: remove workspace schema branch once IS_CONNECTED_ACCOUNT_MIGRATED feature flag is removed - const isMigrated = await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - activeWorkspace.id, + const [calendarChannels] = await this.coreDataSource.query( + `UPDATE core."calendarChannel" SET "syncStage" = '${CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}') + WHERE "workspaceId" = '${activeWorkspace.id}' AND "isSyncEnabled" = true AND "syncStage" = '${CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING}' RETURNING *`, ); - const [calendarChannels] = isMigrated - ? await this.coreDataSource.query( - `UPDATE core."calendarChannel" SET "syncStage" = '${CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}') - WHERE "workspaceId" = '${activeWorkspace.id}' AND "isSyncEnabled" = true AND "syncStage" = '${CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING}' RETURNING *`, - ) - : await this.coreDataSource.query( - `UPDATE ${getWorkspaceSchemaName(activeWorkspace.id)}."calendarChannel" SET "syncStage" = '${CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}') - WHERE "isSyncEnabled" = true AND "syncStage" = '${CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING}' RETURNING *`, - ); - for (const calendarChannel of calendarChannels) { await this.messageQueueService.add( CalendarEventListFetchJob.name, diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-events-import.cron.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-events-import.cron.job.ts index db39482706..060cfbc074 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-events-import.cron.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-events-import.cron.job.ts @@ -1,22 +1,19 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; -import { FeatureFlagKey } from 'twenty-shared/types'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { DataSource, Repository } from 'typeorm'; +import { CalendarChannelSyncStage } from 'twenty-shared/types'; import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; import { type CalendarEventListFetchJobData } from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job'; import { CalendarEventsImportJob } from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-events-import.job'; -import { CalendarChannelSyncStage } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; export const CALENDAR_EVENTS_IMPORT_CRON_PATTERN = '*/1 * * * *'; @@ -32,7 +29,6 @@ export class CalendarEventsImportCronJob { @InjectDataSource() private readonly coreDataSource: DataSource, private readonly exceptionHandlerService: ExceptionHandlerService, - private readonly featureFlagService: FeatureFlagService, ) {} @Process(CalendarEventsImportCronJob.name) @@ -51,22 +47,11 @@ export class CalendarEventsImportCronJob { try { const now = new Date().toISOString(); - // TODO: remove workspace schema branch once IS_CONNECTED_ACCOUNT_MIGRATED feature flag is removed - const isMigrated = await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - activeWorkspace.id, + const [calendarChannels] = await this.coreDataSource.query( + `UPDATE core."calendarChannel" SET "syncStage" = '${CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}') + WHERE "workspaceId" = '${activeWorkspace.id}' AND "isSyncEnabled" = true AND "syncStage" = '${CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_PENDING}' RETURNING *`, ); - const [calendarChannels] = isMigrated - ? await this.coreDataSource.query( - `UPDATE core."calendarChannel" SET "syncStage" = '${CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}') - WHERE "workspaceId" = '${activeWorkspace.id}' AND "isSyncEnabled" = true AND "syncStage" = '${CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_PENDING}' RETURNING *`, - ) - : await this.coreDataSource.query( - `UPDATE ${getWorkspaceSchemaName(activeWorkspace.id)}."calendarChannel" SET "syncStage" = '${CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}') - WHERE "isSyncEnabled" = true AND "syncStage" = '${CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_PENDING}' RETURNING *`, - ); - for (const calendarChannel of calendarChannels) { await this.messageQueueService.add( CalendarEventsImportJob.name, diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-relaunch-failed-calendar-channels.cron.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-relaunch-failed-calendar-channels.cron.job.ts index e5baed1d24..6f8539cbed 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-relaunch-failed-calendar-channels.cron.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-relaunch-failed-calendar-channels.cron.job.ts @@ -3,6 +3,10 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { DataSource, Repository } from 'typeorm'; +import { + CalendarChannelSyncStage, + CalendarChannelSyncStatus, +} from 'twenty-shared/types'; import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; @@ -16,10 +20,6 @@ import { CalendarRelaunchFailedCalendarChannelJob, type CalendarRelaunchFailedCalendarChannelJobData, } from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-relaunch-failed-calendar-channel.job'; -import { - CalendarChannelSyncStage, - CalendarChannelSyncStatus, -} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; export const CALENDAR_RELAUNCH_FAILED_CALENDAR_CHANNELS_CRON_PATTERN = '*/30 * * * *'; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/providers/caldav.provider.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/providers/caldav.provider.ts index 1e8cd513c1..1e6715745a 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/providers/caldav.provider.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/providers/caldav.provider.ts @@ -4,7 +4,7 @@ import { isDefined } from 'twenty-shared/utils'; import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service'; import { CalDAVClient } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/lib/caldav.client'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; @Injectable() export class CalDavClientProvider { @@ -14,7 +14,7 @@ export class CalDavClientProvider { public async getCalDavCalendarClient( connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, 'id' | 'provider' | 'connectionParameters' | 'handle' >, ): Promise { diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-get-events.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-get-events.service.ts index 8929b4ed81..6a6c0d3e6c 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-get-events.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-get-events.service.ts @@ -3,7 +3,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { CalDavClientProvider } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/providers/caldav.provider'; import { parseCalDAVError } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/utils/parse-caldav-error.util'; import { type GetCalendarEventsResponse } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-get-events.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; @Injectable() export class CalDavGetEventsService { @@ -18,7 +18,7 @@ export class CalDavGetEventsService { public async getCalendarEvents( connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, 'provider' | 'id' | 'connectionParameters' | 'handle' >, syncCursor?: string, diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/services/google-calendar-get-events.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/services/google-calendar-get-events.service.ts index ca1535b1c5..97e862d676 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/services/google-calendar-get-events.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/services/google-calendar-get-events.service.ts @@ -9,7 +9,7 @@ import { parseGaxiosError } from 'src/modules/calendar/calendar-event-import-man import { parseGoogleCalendarError } from 'src/modules/calendar/calendar-event-import-manager/drivers/google-calendar/utils/parse-google-calendar-error.util'; import { type GetCalendarEventsResponse } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-get-events.service'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; @Injectable() export class GoogleCalendarGetEventsService { @@ -21,7 +21,7 @@ export class GoogleCalendarGetEventsService { public async getCalendarEvents( connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, 'provider' | 'refreshToken' | 'id' >, syncCursor?: string, diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/services/microsoft-calendar-get-events.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/services/microsoft-calendar-get-events.service.ts index c10865be7b..b0e548f9ea 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/services/microsoft-calendar-get-events.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/services/microsoft-calendar-get-events.service.ts @@ -9,7 +9,7 @@ import { import { parseMicrosoftCalendarError } from 'src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/utils/parse-microsoft-calendar-error.util'; import { type GetCalendarEventsResponse } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-get-events.service'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; @Injectable() export class MicrosoftCalendarGetEventsService { @@ -19,7 +19,7 @@ export class MicrosoftCalendarGetEventsService { public async getCalendarEvents( connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, 'provider' | 'accessToken' | 'refreshToken' | 'id' >, syncCursor?: string, diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/services/microsoft-calendar-import-events.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/services/microsoft-calendar-import-events.service.ts index 4b526169e4..ae3f300c8b 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/services/microsoft-calendar-import-events.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/services/microsoft-calendar-import-events.service.ts @@ -6,7 +6,7 @@ import { formatMicrosoftCalendarEvents } from 'src/modules/calendar/calendar-eve import { parseMicrosoftCalendarError } from 'src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/utils/parse-microsoft-calendar-error.util'; import { type FetchedCalendarEvent } from 'src/modules/calendar/common/types/fetched-calendar-event'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; @Injectable() export class MicrosoftCalendarImportEventsService { @@ -16,7 +16,7 @@ export class MicrosoftCalendarImportEventsService { public async getCalendarEvents( connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, 'provider' | 'accessToken' | 'refreshToken' | 'id' >, changedEventIds: string[], diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job.ts index c51b0491c1..8d6f03e5a1 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job.ts @@ -1,19 +1,18 @@ import { Scope } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { CalendarChannelSyncStage } from 'twenty-shared/types'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { CalendarFetchEventsService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; -import { - CalendarChannelSyncStage, - type CalendarChannelWorkspaceEntity, -} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { isThrottled } from 'src/modules/connected-account/utils/is-throttled'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; export type CalendarEventListFetchJobData = { calendarChannelId: string; @@ -27,7 +26,8 @@ export type CalendarEventListFetchJobData = { export class CalendarEventListFetchJob { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService, private readonly calendarFetchEventsService: CalendarFetchEventsService, ) {} @@ -39,14 +39,14 @@ export class CalendarEventListFetchJob { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - const calendarChannel = - await this.calendarChannelDataAccessService.findOne(workspaceId, { - where: { - id: calendarChannelId, - isSyncEnabled: true, - }, - relations: ['connectedAccount'], - }); + const calendarChannel = await this.calendarChannelRepository.findOne({ + where: { + id: calendarChannelId, + isSyncEnabled: true, + workspaceId, + }, + relations: ['connectedAccount'], + }); if (!calendarChannel) { return; @@ -62,7 +62,10 @@ export class CalendarEventListFetchJob { const syncStageStartedAt = calendarChannel.syncStageStartedAt; if ( - isThrottled(syncStageStartedAt, calendarChannel.throttleFailureCount) + isThrottled( + syncStageStartedAt?.toISOString() ?? null, + calendarChannel.throttleFailureCount, + ) ) { await this.calendarChannelSyncStatusService.markAsCalendarEventListFetchPending( [calendarChannel.id], @@ -74,8 +77,8 @@ export class CalendarEventListFetchJob { } await this.calendarFetchEventsService.fetchCalendarEvents( - calendarChannel as unknown as CalendarChannelWorkspaceEntity, - calendarChannel.connectedAccount as unknown as ConnectedAccountWorkspaceEntity, + calendarChannel as unknown as CalendarChannelEntity, + calendarChannel.connectedAccount, workspaceId, ); }, authContext); diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-events-import.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-events-import.job.ts index f199309670..f37338388c 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-events-import.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-events-import.job.ts @@ -1,19 +1,18 @@ import { Scope } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { CalendarChannelSyncStage } from 'twenty-shared/types'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { CalendarEventsImportService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; -import { - CalendarChannelSyncStage, - type CalendarChannelWorkspaceEntity, -} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { isThrottled } from 'src/modules/connected-account/utils/is-throttled'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; export type CalendarEventsImportJobData = { calendarChannelId: string; @@ -29,7 +28,8 @@ export class CalendarEventsImportJob { private readonly calendarEventsImportService: CalendarEventsImportService, private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, ) {} @Process(CalendarEventsImportJob.name) @@ -39,14 +39,14 @@ export class CalendarEventsImportJob { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - const calendarChannel = - await this.calendarChannelDataAccessService.findOne(workspaceId, { - where: { - id: calendarChannelId, - isSyncEnabled: true, - }, - relations: ['connectedAccount'], - }); + const calendarChannel = await this.calendarChannelRepository.findOne({ + where: { + id: calendarChannelId, + isSyncEnabled: true, + workspaceId, + }, + relations: ['connectedAccount'], + }); if (!calendarChannel?.isSyncEnabled) { return; @@ -62,7 +62,10 @@ export class CalendarEventsImportJob { const syncStageStartedAt = calendarChannel.syncStageStartedAt; if ( - isThrottled(syncStageStartedAt, calendarChannel.throttleFailureCount) + isThrottled( + syncStageStartedAt?.toISOString() ?? null, + calendarChannel.throttleFailureCount, + ) ) { await this.calendarChannelSyncStatusService.markAsCalendarEventsImportPending( [calendarChannel.id], @@ -74,8 +77,8 @@ export class CalendarEventsImportJob { } await this.calendarEventsImportService.processCalendarEventsImport( - calendarChannel as unknown as CalendarChannelWorkspaceEntity, - calendarChannel.connectedAccount as unknown as ConnectedAccountWorkspaceEntity, + calendarChannel as unknown as CalendarChannelEntity, + calendarChannel.connectedAccount, workspaceId, ); }, authContext); diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-ongoing-stale.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-ongoing-stale.job.ts index 62b6844419..30f11f7f7c 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-ongoing-stale.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-ongoing-stale.job.ts @@ -1,16 +1,17 @@ import { Logger, Scope } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { In } from 'typeorm'; +import { In, Repository } from 'typeorm'; +import { CalendarChannelSyncStage } from 'twenty-shared/types'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { isSyncStale } from 'src/modules/calendar/calendar-event-import-manager/utils/is-sync-stale.util'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; -import { CalendarChannelSyncStage } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; export type CalendarOngoingStaleJobData = { workspaceId: string; @@ -24,7 +25,8 @@ export class CalendarOngoingStaleJob { private readonly logger = new Logger(CalendarOngoingStaleJob.name); constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService, ) {} @@ -35,24 +37,22 @@ export class CalendarOngoingStaleJob { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - const calendarChannels = await this.calendarChannelDataAccessService.find( - workspaceId, - { - where: { - syncStage: In([ - CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING, - CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING, - CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED, - CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED, - ]), - }, + const calendarChannels = await this.calendarChannelRepository.find({ + where: { + syncStage: In([ + CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING, + CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING, + CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_SCHEDULED, + CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED, + ]), + workspaceId, }, - ); + }); for (const calendarChannel of calendarChannels) { const syncStageStartedAt = calendarChannel.syncStageStartedAt; - if (isSyncStale(syncStageStartedAt)) { + if (isSyncStale(syncStageStartedAt?.toISOString() ?? null)) { await this.calendarChannelSyncStatusService.resetSyncStageStartedAt( [calendarChannel.id], workspaceId, diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-relaunch-failed-calendar-channel.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-relaunch-failed-calendar-channel.job.ts index 49a9b828b3..3a110de506 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-relaunch-failed-calendar-channel.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/jobs/calendar-relaunch-failed-calendar-channel.job.ts @@ -1,15 +1,18 @@ import { Scope } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Repository } from 'typeorm'; -import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; -import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; -import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; -import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { CalendarChannelSyncStage, CalendarChannelSyncStatus, -} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; +} from 'twenty-shared/types'; +import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; +import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; +import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; +import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; export type CalendarRelaunchFailedCalendarChannelJobData = { workspaceId: string; @@ -23,7 +26,8 @@ export type CalendarRelaunchFailedCalendarChannelJobData = { export class CalendarRelaunchFailedCalendarChannelJob { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, ) {} @Process(CalendarRelaunchFailedCalendarChannelJob.name) @@ -33,12 +37,12 @@ export class CalendarRelaunchFailedCalendarChannelJob { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - const calendarChannel = - await this.calendarChannelDataAccessService.findOne(workspaceId, { - where: { - id: calendarChannelId, - }, - }); + const calendarChannel = await this.calendarChannelRepository.findOne({ + where: { + id: calendarChannelId, + workspaceId, + }, + }); if ( !calendarChannel || @@ -48,9 +52,8 @@ export class CalendarRelaunchFailedCalendarChannelJob { return; } - await this.calendarChannelDataAccessService.update( - workspaceId, - { id: calendarChannelId }, + await this.calendarChannelRepository.update( + { id: calendarChannelId, workspaceId }, { syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, syncStatus: CalendarChannelSyncStatus.ACTIVE, diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/__tests__/calendar-fetch-events.service.spec.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/__tests__/calendar-fetch-events.service.spec.ts index 0b166ebe42..f1c837cbc9 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/__tests__/calendar-fetch-events.service.spec.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/__tests__/calendar-fetch-events.service.spec.ts @@ -1,5 +1,5 @@ -import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { CalendarFetchEventsService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service'; const mockCalendarChannelSyncStatusService = { @@ -12,7 +12,7 @@ const mockGetCalendarEventsService = { getCalendarEvents: jest.fn(), }; -const mockCalendarChannelDataAccessService = { +const mockCalendarChannelRepository = { update: jest.fn(), }; @@ -51,16 +51,16 @@ const baseConnectedAccount = { refreshToken: 'refresh-token', accessToken: 'access-token', handle: 'test@example.com', -} as unknown as ConnectedAccountWorkspaceEntity; +} as unknown as ConnectedAccountEntity; const createCalendarChannel = ( syncCursor: string | null, -): CalendarChannelWorkspaceEntity => +): CalendarChannelEntity => ({ id: 'channel-123', syncCursor, connectedAccountId: 'account-123', - }) as unknown as CalendarChannelWorkspaceEntity; + }) as unknown as CalendarChannelEntity; describe('CalendarFetchEventsService', () => { let service: CalendarFetchEventsService; @@ -77,7 +77,7 @@ describe('CalendarFetchEventsService', () => { service = new CalendarFetchEventsService( mockCacheStorage as any, mockGlobalWorkspaceOrmManager as any, - mockCalendarChannelDataAccessService as any, + mockCalendarChannelRepository as any, mockCalendarChannelSyncStatusService as any, mockGetCalendarEventsService as any, mockCalendarEventImportErrorHandlerService as any, @@ -147,9 +147,8 @@ describe('CalendarFetchEventsService', () => { workspaceId, ); - expect(mockCalendarChannelDataAccessService.update).toHaveBeenCalledWith( - workspaceId, - { id: 'channel-123' }, + expect(mockCalendarChannelRepository.update).toHaveBeenCalledWith( + { id: 'channel-123', workspaceId }, { syncCursor: 'new-cursor-abc' }, ); }); diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-account-authentication.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-account-authentication.service.ts index b941469f77..108e887670 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-account-authentication.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-account-authentication.service.ts @@ -12,10 +12,10 @@ import { ConnectedAccountRefreshTokensService, type ConnectedAccountTokens, } from 'src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; interface ValidateAndRefreshConnectedAccountAuthenticationParams { - connectedAccount: ConnectedAccountWorkspaceEntity; + connectedAccount: ConnectedAccountEntity; workspaceId: string; calendarChannelId: string; } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts index 311020b4bd..27d459499e 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts @@ -1,7 +1,9 @@ import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Repository } from 'typeorm'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; import { type TwentyORMException, TwentyORMExceptionCode, @@ -16,7 +18,7 @@ import { CalendarEventImportExceptionCode, } from 'src/modules/calendar/calendar-event-import-manager/exceptions/calendar-event-import.exception'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; -import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; export enum CalendarEventImportSyncStep { CALENDAR_EVENT_LIST_FETCH = 'CALENDAR_EVENT_LIST_FETCH', CALENDAR_EVENTS_IMPORT = 'CALENDAR_EVENTS_IMPORT', @@ -28,7 +30,8 @@ export class CalendarEventImportErrorHandlerService { CalendarEventImportErrorHandlerService.name, ); constructor( - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService, private readonly exceptionHandlerService: ExceptionHandlerService, ) {} @@ -36,10 +39,7 @@ export class CalendarEventImportErrorHandlerService { public async handleDriverException( exception: CalendarEventImportDriverException | TwentyORMException, syncStep: CalendarEventImportSyncStep, - calendarChannel: Pick< - CalendarChannelWorkspaceEntity, - 'id' | 'throttleFailureCount' - >, + calendarChannel: Pick, workspaceId: string, ): Promise { switch (exception.code) { @@ -81,7 +81,7 @@ export class CalendarEventImportErrorHandlerService { } private async handleSyncCursorErrorException( - calendarChannel: Pick, + calendarChannel: Pick, workspaceId: string, ): Promise { this.logger.debug( @@ -96,10 +96,7 @@ export class CalendarEventImportErrorHandlerService { private async handleTemporaryException( syncStep: CalendarEventImportSyncStep, - calendarChannel: Pick< - CalendarChannelWorkspaceEntity, - 'id' | 'throttleFailureCount' - >, + calendarChannel: Pick, workspaceId: string, ): Promise { if ( @@ -132,11 +129,8 @@ export class CalendarEventImportErrorHandlerService { throw calendarEventImportException; } - await this.calendarChannelDataAccessService.increment( - workspaceId, - { - id: calendarChannel.id, - }, + await this.calendarChannelRepository.increment( + { id: calendarChannel.id, workspaceId }, 'throttleFailureCount', 1, ); @@ -164,7 +158,7 @@ export class CalendarEventImportErrorHandlerService { } private async handleInsufficientPermissionsException( - calendarChannel: Pick, + calendarChannel: Pick, workspaceId: string, ): Promise { await this.calendarChannelSyncStatusService.markAsFailedInsufficientPermissionsAndFlushCalendarEventsToImport( @@ -175,7 +169,7 @@ export class CalendarEventImportErrorHandlerService { private async handleUnknownException( exception: { message: string }, - calendarChannel: Pick, + calendarChannel: Pick, workspaceId: string, ): Promise { await this.calendarChannelSyncStatusService.markAsFailedUnknownAndFlushCalendarEventsToImport( @@ -207,7 +201,7 @@ export class CalendarEventImportErrorHandlerService { private async handleNotFoundException( syncStep: CalendarEventImportSyncStep, - calendarChannel: Pick, + calendarChannel: Pick, workspaceId: string, ): Promise { if (syncStep === CalendarEventImportSyncStep.CALENDAR_EVENT_LIST_FETCH) { diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service.ts index cc4109ac30..ec82bc12c9 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service.ts @@ -1,11 +1,14 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; -import { Any } from 'typeorm'; +import { Any, Repository } from 'typeorm'; +import { type CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator'; import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; @@ -26,9 +29,10 @@ import { CalendarSaveEventsService } from 'src/modules/calendar/calendar-event-i import { filterEventsAndReturnCancelledEvents } from 'src/modules/calendar/calendar-event-import-manager/utils/filter-events.util'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; import { type CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity'; -import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { type FetchedCalendarEvent } from 'src/modules/calendar/common/types/fetched-calendar-event'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { EmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/services/email-alias-manager.service'; +import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @Injectable() export class CalendarEventsImportService { @@ -43,11 +47,14 @@ export class CalendarEventsImportService { private readonly calendarSaveEventsService: CalendarSaveEventsService, private readonly calendarEventImportErrorHandlerService: CalendarEventImportErrorHandlerService, private readonly microsoftCalendarImportEventService: MicrosoftCalendarImportEventsService, + private readonly emailAliasManagerService: EmailAliasManagerService, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, ) {} public async processCalendarEventsImport( - calendarChannel: CalendarChannelWorkspaceEntity, - connectedAccount: ConnectedAccountWorkspaceEntity, + calendarChannel: CalendarChannelEntity, + connectedAccount: ConnectedAccountEntity, workspaceId: string, fetchedCalendarEvents?: FetchedCalendarEvent[], ): Promise { @@ -99,10 +106,39 @@ export class CalendarEventsImportService { ); } - const blocklist = await this.blocklistRepository.getByWorkspaceMemberId( - connectedAccount.accountOwnerId, - workspaceId, - ); + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { + id: (connectedAccount as unknown as { userWorkspaceId: string }) + .userWorkspaceId, + }, + }); + + const workspaceMemberRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workspaceMember', + ); + + const workspaceMember = userWorkspace + ? await workspaceMemberRepository.findOne({ + where: { userId: userWorkspace.userId }, + }) + : null; + + const blocklist = workspaceMember + ? await this.blocklistRepository.getByWorkspaceMemberId( + workspaceMember.id, + workspaceId, + ) + : []; + + const refreshedHandleAliases = + await this.emailAliasManagerService.refreshHandleAliases( + connectedAccount, + workspaceId, + ); + + connectedAccount.handleAliases = refreshedHandleAliases; if ( !isDefined(connectedAccount.handleAliases) || @@ -116,10 +152,7 @@ export class CalendarEventsImportService { const { filteredEvents, cancelledEvents } = filterEventsAndReturnCancelledEvents( - [ - calendarChannel.handle, - ...connectedAccount.handleAliases.split(','), - ], + [calendarChannel.handle, ...connectedAccount.handleAliases], calendarEvents, blocklist.map((blocklist) => blocklist.handle ?? ''), ); @@ -148,9 +181,7 @@ export class CalendarEventsImportService { await calendarChannelEventAssociationRepository.delete({ eventExternalId: Any(cancelledEventExternalIds), - calendarChannel: { - id: calendarChannel.id, - }, + calendarChannelId: calendarChannel.id, }); await this.calendarEventCleanerService.cleanWorkspaceCalendarEvents( diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service.ts index c29a33ed08..a914333d43 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service.ts @@ -1,9 +1,11 @@ import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Repository } from 'typeorm'; import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator'; import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { @@ -18,8 +20,8 @@ import { import { CalendarEventsImportService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service'; import { CalendarGetCalendarEventsService } from 'src/modules/calendar/calendar-event-import-manager/services/calendar-get-events.service'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; -import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; @Injectable() export class CalendarFetchEventsService { @@ -28,7 +30,8 @@ export class CalendarFetchEventsService { @InjectCacheStorage(CacheStorageNamespace.ModuleCalendar) private readonly cacheStorage: CacheStorageService, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, private readonly calendarChannelSyncStatusService: CalendarChannelSyncStatusService, private readonly getCalendarEventsService: CalendarGetCalendarEventsService, private readonly calendarEventImportErrorHandlerService: CalendarEventImportErrorHandlerService, @@ -37,8 +40,8 @@ export class CalendarFetchEventsService { ) {} public async fetchCalendarEvents( - calendarChannel: CalendarChannelWorkspaceEntity, - connectedAccount: ConnectedAccountWorkspaceEntity, + calendarChannel: CalendarChannelEntity, + connectedAccount: ConnectedAccountEntity, workspaceId: string, ): Promise { this.logger.log( @@ -84,11 +87,8 @@ export class CalendarFetchEventsService { const nextSyncCursor = getCalendarEventsResponse.nextSyncCursor; if (!calendarEvents || calendarEvents?.length === 0) { - await this.calendarChannelDataAccessService.update( - workspaceId, - { - id: calendarChannel.id, - }, + await this.calendarChannelRepository.update( + { id: calendarChannel.id, workspaceId }, { syncCursor: nextSyncCursor, }, @@ -100,11 +100,8 @@ export class CalendarFetchEventsService { ); } - await this.calendarChannelDataAccessService.update( - workspaceId, - { - id: calendarChannel.id, - }, + await this.calendarChannelRepository.update( + { id: calendarChannel.id, workspaceId }, { syncCursor: nextSyncCursor, }, diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-get-events.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-get-events.service.ts index 01eb3fbc24..526493e9aa 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-get-events.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-get-events.service.ts @@ -10,7 +10,7 @@ import { CalendarEventImportExceptionCode, } from 'src/modules/calendar/calendar-event-import-manager/exceptions/calendar-event-import.exception'; import { type FetchedCalendarEvent } from 'src/modules/calendar/common/types/fetched-calendar-event'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; export type GetCalendarEventsResponse = { fullEvents: boolean; @@ -29,7 +29,7 @@ export class CalendarGetCalendarEventsService { public async getCalendarEvents( connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, | 'provider' | 'accessToken' | 'refreshToken' diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts index 67704eb16b..bf8c903cae 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-save-events.service.ts @@ -3,15 +3,15 @@ import { Injectable } from '@nestjs/common'; import { Any } from 'typeorm'; import { v4 as uuid } from 'uuid'; +import { type CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { CalendarEventParticipantService } from 'src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service'; import { type CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity'; -import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { type CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; import { type FetchedCalendarEvent } from 'src/modules/calendar/common/types/fetched-calendar-event'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; type FetchedCalendarEventWithDBEvent = { fetchedCalendarEvent: FetchedCalendarEvent; @@ -28,8 +28,8 @@ export class CalendarSaveEventsService { public async saveCalendarEventsAndEnqueueContactCreationJob( fetchedCalendarEvents: FetchedCalendarEvent[], - calendarChannel: CalendarChannelWorkspaceEntity, - connectedAccount: ConnectedAccountWorkspaceEntity, + calendarChannel: CalendarChannelEntity, + connectedAccount: ConnectedAccountEntity, workspaceId: string, ): Promise { const authContext = buildSystemAuthContext(workspaceId); diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service.ts index 2ab7cebe54..d3e5899f68 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service.ts @@ -6,16 +6,16 @@ import differenceWith from 'lodash.differencewith'; import { FieldActorSource } from 'twenty-shared/types'; import { Any } from 'typeorm'; +import { type CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; -import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { type CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity'; import { type FetchedCalendarEventParticipant } from 'src/modules/calendar/common/types/fetched-calendar-event'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { CreateCompanyAndContactJob, type CreateCompanyAndContactJobData, @@ -52,8 +52,8 @@ export class CalendarEventParticipantService { participantsToCreate: FetchedCalendarEventParticipantWithCalendarEventId[]; participantsToUpdate: FetchedCalendarEventParticipantWithCalendarEventId[]; transactionManager?: WorkspaceEntityManager; - calendarChannel: CalendarChannelWorkspaceEntity; - connectedAccount: ConnectedAccountWorkspaceEntity; + calendarChannel: CalendarChannelEntity; + connectedAccount: ConnectedAccountEntity; workspaceId: string; }): Promise { const authContext = buildSystemAuthContext(workspaceId); diff --git a/packages/twenty-server/src/modules/calendar/common/calendar-common.module.ts b/packages/twenty-server/src/modules/calendar/common/calendar-common.module.ts index 2a1659115b..37f904250b 100644 --- a/packages/twenty-server/src/modules/calendar/common/calendar-common.module.ts +++ b/packages/twenty-server/src/modules/calendar/common/calendar-common.module.ts @@ -1,10 +1,10 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module'; -import { CalendarChannelDataAccessModule } from 'src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module'; -import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service'; import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module'; @@ -12,10 +12,12 @@ import { ConnectedAccountModule } from 'src/modules/connected-account/connected- @Module({ imports: [ WorkspaceDataSourceModule, - TypeOrmModule.forFeature([FeatureFlagEntity]), + TypeOrmModule.forFeature([ + CalendarChannelEntity, + ConnectedAccountEntity, + UserWorkspaceEntity, + ]), ConnectedAccountModule, - CalendarChannelDataAccessModule, - ConnectedAccountDataAccessModule, MetricsModule, ], providers: [CalendarChannelSyncStatusService], diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.spec.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.spec.ts index 61d11dde3d..a680d2c19f 100644 --- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.spec.ts +++ b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.spec.ts @@ -1,10 +1,13 @@ import { Test, type TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; +import { CalendarChannelVisibility } from 'twenty-shared/types'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { type CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; import { ApplyCalendarEventsVisibilityRestrictionsService } from './apply-calendar-events-visibility-restrictions.service'; @@ -49,7 +52,15 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { findOneByOrFail: jest.fn(), }; - const mockConnectedAccountDataAccessService = { + const mockConnectedAccountRepository = { + find: jest.fn(), + }; + + const mockUserWorkspaceRepository = { + findOne: jest.fn(), + }; + + const mockCalendarChannelRepository = { find: jest.fn(), }; @@ -76,8 +87,16 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { useValue: mockGlobalWorkspaceOrmManager, }, { - provide: ConnectedAccountDataAccessService, - useValue: mockConnectedAccountDataAccessService, + provide: getRepositoryToken(ConnectedAccountEntity), + useValue: mockConnectedAccountRepository, + }, + { + provide: getRepositoryToken(UserWorkspaceEntity), + useValue: mockUserWorkspaceRepository, + }, + { + provide: getRepositoryToken(CalendarChannelEntity), + useValue: mockCalendarChannelRepository, }, ], }).compile(); @@ -95,17 +114,17 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { createMockCalendarEvent('1', 'Test Event', 'Test Description'), ]; - mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({ - id: 'workspace-member-id', - }); - mockCalendarEventAssociationRepository.find.mockResolvedValue([ { calendarEventId: '1', - calendarChannel: { - id: '1', - visibility: CalendarChannelVisibility.SHARE_EVERYTHING, - }, + calendarChannelId: '1', + }, + ]); + + mockCalendarChannelRepository.find.mockResolvedValue([ + { + id: '1', + visibility: CalendarChannelVisibility.SHARE_EVERYTHING, }, ]); @@ -123,7 +142,7 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { item.description !== FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED, ), ).toBe(true); - expect(mockConnectedAccountDataAccessService.find).not.toHaveBeenCalled(); + expect(mockConnectedAccountRepository.find).not.toHaveBeenCalled(); }); it('should return calendar event with obfuscated title and description if the visibility is METADATA', async () => { @@ -134,18 +153,22 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { mockCalendarEventAssociationRepository.find.mockResolvedValue([ { calendarEventId: '1', - calendarChannel: { - id: '1', - visibility: CalendarChannelVisibility.METADATA, - }, + calendarChannelId: '1', }, ]); - mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({ - id: 'workspace-member-id', + mockCalendarChannelRepository.find.mockResolvedValue([ + { + id: '1', + visibility: CalendarChannelVisibility.METADATA, + }, + ]); + + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', }); - mockConnectedAccountDataAccessService.find.mockResolvedValue([]); + mockConnectedAccountRepository.find.mockResolvedValue([]); const result = await service.applyCalendarEventsVisibilityRestrictions( calendarEvents, @@ -170,18 +193,22 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { mockCalendarEventAssociationRepository.find.mockResolvedValue([ { calendarEventId: '1', - calendarChannel: { - id: '1', - visibility: CalendarChannelVisibility.METADATA, - }, + calendarChannelId: '1', }, ]); - mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({ - id: 'workspace-member-account-owner-id', + mockCalendarChannelRepository.find.mockResolvedValue([ + { + id: '1', + visibility: CalendarChannelVisibility.METADATA, + }, + ]); + + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', }); - mockConnectedAccountDataAccessService.find.mockResolvedValue([{ id: '1' }]); + mockConnectedAccountRepository.find.mockResolvedValue([{ id: '1' }]); const result = await service.applyCalendarEventsVisibilityRestrictions( calendarEvents, @@ -207,17 +234,21 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { mockCalendarEventAssociationRepository.find.mockResolvedValue([ { calendarEventId: '1', - calendarChannel: { - id: '1', - }, + calendarChannelId: '1', }, ]); - mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({ - id: 'workspace-member-not-account-owner-id', + mockCalendarChannelRepository.find.mockResolvedValue([ + { + id: '1', + }, + ]); + + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', }); - mockConnectedAccountDataAccessService.find.mockResolvedValue([]); + mockConnectedAccountRepository.find.mockResolvedValue([]); const result = await service.applyCalendarEventsVisibilityRestrictions( calendarEvents, @@ -235,35 +266,41 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { createMockCalendarEvent('3', 'Event 3', 'Description 3'), ]; - mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({ - id: 'workspace-member-id', + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', }); mockCalendarEventAssociationRepository.find.mockResolvedValue([ { calendarEventId: '1', - calendarChannel: { - id: '1', - visibility: CalendarChannelVisibility.SHARE_EVERYTHING, - }, + calendarChannelId: '1', }, { calendarEventId: '2', - calendarChannel: { - id: '2', - visibility: CalendarChannelVisibility.METADATA, - }, + calendarChannelId: '2', }, { calendarEventId: '3', - calendarChannel: { - id: '3', - visibility: CalendarChannelVisibility.METADATA, - }, + calendarChannelId: '3', }, ]); - mockConnectedAccountDataAccessService.find + mockCalendarChannelRepository.find.mockResolvedValue([ + { + id: '1', + visibility: CalendarChannelVisibility.SHARE_EVERYTHING, + }, + { + id: '2', + visibility: CalendarChannelVisibility.METADATA, + }, + { + id: '3', + visibility: CalendarChannelVisibility.METADATA, + }, + ]); + + mockConnectedAccountRepository.find .mockResolvedValueOnce([]) // request for calendar event 3 .mockResolvedValueOnce([{ id: '1' }]); // request for calendar event 2 @@ -291,37 +328,38 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { createMockCalendarEvent('3', 'Event 3', 'Description 3'), ]; - mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({ - id: 'workspace-member-id', - }); - mockCalendarEventAssociationRepository.find.mockResolvedValue([ { calendarEventId: '1', - calendarChannel: { - id: '1', - visibility: CalendarChannelVisibility.SHARE_EVERYTHING, - }, + calendarChannelId: '1', }, { calendarEventId: '2', - calendarChannel: { - id: '2', - visibility: CalendarChannelVisibility.METADATA, - }, + calendarChannelId: '2', }, { calendarEventId: '3', - calendarChannel: { - id: '3', - visibility: CalendarChannelVisibility.METADATA, - }, + calendarChannelId: '3', }, ]); - mockConnectedAccountDataAccessService.find - .mockResolvedValueOnce([]) // request for calendar event 3 - .mockResolvedValueOnce([{ id: '1' }]); // request for calendar event 2 + mockCalendarChannelRepository.find.mockResolvedValue([ + { + id: '1', + visibility: CalendarChannelVisibility.SHARE_EVERYTHING, + }, + { + id: '2', + visibility: CalendarChannelVisibility.METADATA, + }, + { + id: '3', + visibility: CalendarChannelVisibility.METADATA, + }, + ]); + + // userId is undefined (api key request), so connected account check is skipped + // METADATA events should be obfuscated const result = await service.applyCalendarEventsVisibilityRestrictions( calendarEvents, @@ -331,12 +369,17 @@ describe('ApplyCalendarEventsVisibilityRestrictionsService', () => { expect(result).toEqual([ calendarEvents[0], - calendarEvents[1], + { + ...calendarEvents[1], + title: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED, + description: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED, + }, { ...calendarEvents[2], title: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED, description: FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED, }, ]); + expect(mockConnectedAccountRepository.find).not.toHaveBeenCalled(); }); }); diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.ts index 4ecbff6bbe..2cf829b802 100644 --- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.ts +++ b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service.ts @@ -1,29 +1,36 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import groupBy from 'lodash.groupby'; import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; +import { CalendarChannelVisibility } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { In } from 'typeorm'; +import { In, Repository } from 'typeorm'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { type CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity'; -import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { type CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; -import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @Injectable() export class ApplyCalendarEventsVisibilityRestrictionsService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, ) {} public async applyCalendarEventsVisibilityRestrictions( calendarEvents: CalendarEventWorkspaceEntity[], workspaceId: string, - userId?: string, // undefined when request is made with api key + userId?: string, ) { const authContext = buildSystemAuthContext(workspaceId); @@ -40,26 +47,42 @@ export class ApplyCalendarEventsVisibilityRestrictionsService { where: { calendarEventId: In(calendarEvents.map((event) => event.id)), }, - relations: ['calendarChannel'], }); - const workspaceMemberRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workspaceMember', - ); + const calendarChannelIds = [ + ...new Set( + calendarChannelCalendarEventsAssociations.map( + (association) => association.calendarChannelId, + ), + ), + ]; + + const calendarChannelsFromCore = + calendarChannelIds.length > 0 + ? await this.calendarChannelRepository.find({ + where: { + id: In(calendarChannelIds), + workspaceId, + }, + }) + : []; + + const calendarChannelMap = new Map( + calendarChannelsFromCore.map((channel) => [channel.id, channel]), + ); for (let i = calendarEvents.length - 1; i >= 0; i--) { - const calendarChannelCalendarEventAssociations = - calendarChannelCalendarEventsAssociations.filter( - (association) => - association.calendarEventId === calendarEvents[i].id, - ); - - const calendarChannels = calendarChannelCalendarEventAssociations.map( - (association) => association.calendarChannel, + const associations = calendarChannelCalendarEventsAssociations.filter( + (association) => + association.calendarEventId === calendarEvents[i].id, ); + const calendarChannels = associations + .map((association) => + calendarChannelMap.get(association.calendarChannelId), + ) + .filter(isDefined); + const calendarChannelsGroupByVisibility = groupBy( calendarChannels, (channel) => channel.visibility, @@ -74,21 +97,26 @@ export class ApplyCalendarEventsVisibilityRestrictionsService { } if (isDefined(userId)) { - const workspaceMember = - await workspaceMemberRepository.findOneByOrFail({ - userId: userId, - }); + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { userId, workspaceId }, + select: ['id'], + }); - const connectedAccounts = - await this.connectedAccountDataAccessService.find(workspaceId, { - calendarChannels: { - id: In(calendarChannels.map((channel) => channel.id)), - }, - accountOwnerId: workspaceMember.id, - }); + if (userWorkspace) { + const connectedAccounts = + await this.connectedAccountRepository.find({ + where: { + calendarChannels: { + id: In(calendarChannels.map((channel) => channel.id)), + }, + userWorkspaceId: userWorkspace.id, + workspaceId, + }, + }); - if (connectedAccounts.length > 0) { - continue; + if (connectedAccounts.length > 0) { + continue; + } } } diff --git a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-query-hook.module.ts b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-query-hook.module.ts index 6890e6ba48..6c65b75a78 100644 --- a/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-query-hook.module.ts +++ b/packages/twenty-server/src/modules/calendar/common/query-hooks/calendar-query-hook.module.ts @@ -1,12 +1,21 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; -import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { CalendarEventFindManyPostQueryHook } from 'src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-many.post-query.hook'; import { CalendarEventFindOnePostQueryHook } from 'src/modules/calendar/common/query-hooks/calendar-event/calendar-event-find-one.post-query.hook'; import { ApplyCalendarEventsVisibilityRestrictionsService } from 'src/modules/calendar/common/query-hooks/calendar-event/services/apply-calendar-events-visibility-restrictions.service'; @Module({ - imports: [ConnectedAccountDataAccessModule], + imports: [ + TypeOrmModule.forFeature([ + CalendarChannelEntity, + ConnectedAccountEntity, + UserWorkspaceEntity, + ]), + ], providers: [ ApplyCalendarEventsVisibilityRestrictionsService, CalendarEventFindOnePostQueryHook, diff --git a/packages/twenty-server/src/modules/calendar/common/services/calendar-channel-sync-status.service.ts b/packages/twenty-server/src/modules/calendar/common/services/calendar-channel-sync-status.service.ts index 90115f9259..a9c08f64d5 100644 --- a/packages/twenty-server/src/modules/calendar/common/services/calendar-channel-sync-status.service.ts +++ b/packages/twenty-server/src/modules/calendar/common/services/calendar-channel-sync-status.service.ts @@ -1,32 +1,36 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { Any, In } from 'typeorm'; +import { Any, In, Repository } from 'typeorm'; +import { + CalendarChannelSyncStage, + CalendarChannelSyncStatus, +} from 'twenty-shared/types'; import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator'; import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; -import { - CalendarChannelSyncStage, - CalendarChannelSyncStatus, -} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service'; import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type'; -import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; - @Injectable() export class CalendarChannelSyncStatusService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, @InjectCacheStorage(CacheStorageNamespace.ModuleCalendar) private readonly cacheStorage: CacheStorageService, - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, private readonly accountsToReconnectService: AccountsToReconnectService, private readonly metricsService: MetricsService, ) {} @@ -43,9 +47,8 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.calendarChannelDataAccessService.update( - workspaceId, - { id: In(calendarChannelIds) }, + await this.calendarChannelRepository.update( + { id: In(calendarChannelIds), workspaceId }, { syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, ...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}), @@ -65,9 +68,8 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.calendarChannelDataAccessService.update( - workspaceId, - { id: In(calendarChannelIds) }, + await this.calendarChannelRepository.update( + { id: In(calendarChannelIds), workspaceId }, { syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_ONGOING, syncStatus: CalendarChannelSyncStatus.ONGOING, @@ -94,9 +96,8 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.calendarChannelDataAccessService.update( - workspaceId, - { id: In(calendarChannelIds) }, + await this.calendarChannelRepository.update( + { id: In(calendarChannelIds), workspaceId }, { syncCursor: '', syncStageStartedAt: null, @@ -122,9 +123,8 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.calendarChannelDataAccessService.update( - workspaceId, - { id: In(calendarChannelIds) }, + await this.calendarChannelRepository.update( + { id: In(calendarChannelIds), workspaceId }, { syncStageStartedAt: null, }, @@ -144,9 +144,8 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.calendarChannelDataAccessService.update( - workspaceId, - { id: In(calendarChannelIds) }, + await this.calendarChannelRepository.update( + { id: In(calendarChannelIds), workspaceId }, { syncStage: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_PENDING, ...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}), @@ -166,9 +165,8 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.calendarChannelDataAccessService.update( - workspaceId, - { id: In(calendarChannelIds) }, + await this.calendarChannelRepository.update( + { id: In(calendarChannelIds), workspaceId }, { syncStage: CalendarChannelSyncStage.CALENDAR_EVENTS_IMPORT_ONGOING, syncStatus: CalendarChannelSyncStatus.ONGOING, @@ -189,9 +187,8 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.calendarChannelDataAccessService.update( - workspaceId, - { id: In(calendarChannelIds) }, + await this.calendarChannelRepository.update( + { id: In(calendarChannelIds), workspaceId }, { syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, syncStatus: CalendarChannelSyncStatus.ACTIVE, @@ -230,9 +227,8 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.calendarChannelDataAccessService.update( - workspaceId, - { id: In(calendarChannelIds) }, + await this.calendarChannelRepository.update( + { id: In(calendarChannelIds), workspaceId }, { syncStatus: CalendarChannelSyncStatus.FAILED_UNKNOWN, syncStage: CalendarChannelSyncStage.FAILED, @@ -263,30 +259,25 @@ export class CalendarChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.calendarChannelDataAccessService.update( - workspaceId, - { id: In(calendarChannelIds) }, + await this.calendarChannelRepository.update( + { id: In(calendarChannelIds), workspaceId }, { syncStatus: CalendarChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS, syncStage: CalendarChannelSyncStage.FAILED, }, ); - const calendarChannels = await this.calendarChannelDataAccessService.find( - workspaceId, - { - select: ['id', 'connectedAccountId'], - where: { id: Any(calendarChannelIds) }, - }, - ); + const calendarChannels = await this.calendarChannelRepository.find({ + select: ['id', 'connectedAccountId'], + where: { id: Any(calendarChannelIds), workspaceId }, + }); const connectedAccountIds = calendarChannels.map( (calendarChannel) => calendarChannel.connectedAccountId, ); - await this.connectedAccountDataAccessService.update( - workspaceId, - { id: Any(connectedAccountIds) }, + await this.connectedAccountRepository.update( + { id: Any(connectedAccountIds), workspaceId }, { authFailedAt: new Date(), }, @@ -312,42 +303,33 @@ export class CalendarChannelSyncStatusService { return; } - const calendarChannels = await this.calendarChannelDataAccessService.find( - workspaceId, - { - select: ['id', 'connectedAccountId'], - where: { - id: Any(calendarChannelIds), - }, - }, - ); - - const workspaceMemberRepository = - await this.globalWorkspaceOrmManager.getRepository( + const calendarChannels = await this.calendarChannelRepository.find({ + select: ['id', 'connectedAccountId'], + where: { + id: Any(calendarChannelIds), workspaceId, - 'workspaceMember', - { shouldBypassPermissionChecks: true }, - ); + }, + }); for (const calendarChannel of calendarChannels) { - const connectedAccount = - await this.connectedAccountDataAccessService.findOne(workspaceId, { - where: { id: calendarChannel.connectedAccountId }, - }); + const connectedAccount = await this.connectedAccountRepository.findOne({ + where: { id: calendarChannel.connectedAccountId, workspaceId }, + }); if (!connectedAccount) { continue; } - const workspaceMember = await workspaceMemberRepository.findOne({ - where: { id: connectedAccount.accountOwnerId }, + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { id: connectedAccount.userWorkspaceId }, + select: ['userId'], }); - if (!workspaceMember) { + if (!userWorkspace) { continue; } - const userId = workspaceMember.userId; + const userId = userWorkspace.userId; const connectedAccountId = connectedAccount.id; await this.accountsToReconnectService.addAccountToReconnectByKey( diff --git a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity.ts b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity.ts index 1b50854b0e..6c8ebf7dd3 100644 --- a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity.ts +++ b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity.ts @@ -3,7 +3,6 @@ import { FieldMetadataType } from 'twenty-shared/types'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { type FieldTypeAndNameMetadata } from 'src/engine/workspace-manager/utils/get-ts-vector-column-expression.util'; import { type EntityRelation } from 'src/engine/workspace-manager/workspace-migration/types/entity-relation.interface'; -import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { type CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; const EVENT_EXTERNAL_ID_FIELD_NAME = 'eventExternalId'; @@ -14,7 +13,6 @@ export const SEARCH_FIELDS_FOR_CALENDAR_CHANNEL_EVENT_ASSOCIATION: FieldTypeAndN export class CalendarChannelEventAssociationWorkspaceEntity extends BaseWorkspaceEntity { eventExternalId: string | null; recurringEventExternalId: string | null; - calendarChannel: EntityRelation; calendarChannelId: string; calendarEvent: EntityRelation; calendarEventId: string; diff --git a/packages/twenty-server/src/modules/connected-account/channel-sync/channel-sync.module.ts b/packages/twenty-server/src/modules/connected-account/channel-sync/channel-sync.module.ts index 59ffabf742..000ab64218 100644 --- a/packages/twenty-server/src/modules/connected-account/channel-sync/channel-sync.module.ts +++ b/packages/twenty-server/src/modules/connected-account/channel-sync/channel-sync.module.ts @@ -1,7 +1,8 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; -import { CalendarChannelDataAccessModule } from 'src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module'; -import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { ChannelSyncResolver } from 'src/modules/connected-account/channel-sync/channel-sync.resolver'; @@ -10,8 +11,7 @@ import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-co @Module({ imports: [ - CalendarChannelDataAccessModule, - MessageChannelDataAccessModule, + TypeOrmModule.forFeature([CalendarChannelEntity, MessageChannelEntity]), PermissionsModule, WorkspaceDataSourceModule, MessagingCommonModule, diff --git a/packages/twenty-server/src/modules/connected-account/channel-sync/services/channel-sync.service.ts b/packages/twenty-server/src/modules/connected-account/channel-sync/services/channel-sync.service.ts index 77bf30c912..3355781fb5 100644 --- a/packages/twenty-server/src/modules/connected-account/channel-sync/services/channel-sync.service.ts +++ b/packages/twenty-server/src/modules/connected-account/channel-sync/services/channel-sync.service.ts @@ -1,22 +1,25 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { + CalendarChannelSyncStage, + CalendarChannelSyncStatus, + MessageChannelSyncStage, +} from 'twenty-shared/types'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { CalendarEventListFetchJob, type CalendarEventListFetchJobData, } from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job'; -import { - CalendarChannelSyncStage, - CalendarChannelSyncStatus, -} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; -import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessagingMessageListFetchJob, type MessagingMessageListFetchJobData, @@ -35,9 +38,11 @@ export class ChannelSyncService { private readonly messageQueueService: MessageQueueService, @InjectMessageQueue(MessageQueue.calendarQueue) private readonly calendarQueueService: MessageQueueService, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService, - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, ) {} async startChannelSync(input: StartChannelSyncInput): Promise { @@ -54,13 +59,13 @@ export class ChannelSyncService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - const messageChannels = await this.messageChannelDataAccessService.find( - workspaceId, - { + const messageChannels = await this.messageChannelRepository.find({ + where: { connectedAccountId, syncStage: MessageChannelSyncStage.PENDING_CONFIGURATION, + workspaceId, }, - ); + }); for (const messageChannel of messageChannels) { await this.messageChannelSyncStatusService.markAsMessagesListFetchScheduled( @@ -86,20 +91,17 @@ export class ChannelSyncService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - const calendarChannels = await this.calendarChannelDataAccessService.find( - workspaceId, - { - where: { - connectedAccountId, - syncStage: CalendarChannelSyncStage.PENDING_CONFIGURATION, - }, + const calendarChannels = await this.calendarChannelRepository.find({ + where: { + connectedAccountId, + syncStage: CalendarChannelSyncStage.PENDING_CONFIGURATION, + workspaceId, }, - ); + }); for (const calendarChannel of calendarChannels) { - await this.calendarChannelDataAccessService.update( - workspaceId, - { id: calendarChannel.id }, + await this.calendarChannelRepository.update( + { id: calendarChannel.id, workspaceId }, { syncStage: CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED, diff --git a/packages/twenty-server/src/modules/connected-account/connected-account.module.ts b/packages/twenty-server/src/modules/connected-account/connected-account.module.ts index 1370fd1344..125d37c891 100644 --- a/packages/twenty-server/src/modules/connected-account/connected-account.module.ts +++ b/packages/twenty-server/src/modules/connected-account/connected-account.module.ts @@ -1,14 +1,19 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserVarsModule } from 'src/engine/core-modules/user/user-vars/user-vars.module'; -import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { DeleteWorkspaceMemberConnectedAccountsCleanupJob } from 'src/modules/connected-account/jobs/delete-workspace-member-connected-accounts.job'; import { ConnectedAccountWorkspaceMemberListener } from 'src/modules/connected-account/listeners/connected-account-workspace-member.listener'; import { ConnectedAccountListener } from 'src/modules/connected-account/listeners/connected-account.listener'; import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service'; @Module({ - imports: [ConnectedAccountDataAccessModule, UserVarsModule], + imports: [ + TypeOrmModule.forFeature([ConnectedAccountEntity, UserWorkspaceEntity]), + UserVarsModule, + ], providers: [ AccountsToReconnectService, ConnectedAccountListener, diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service.ts index fd0a8c9030..f9e30b9c1c 100644 --- a/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service.ts +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service.ts @@ -4,7 +4,7 @@ import { google } from 'googleapis'; import { GmailEmailAliasErrorHandlerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-error-handler.service'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; @Injectable() export class GoogleEmailAliasManagerService { @@ -13,9 +13,7 @@ export class GoogleEmailAliasManagerService { private readonly gmailEmailAliasErrorHandlerService: GmailEmailAliasErrorHandlerService, ) {} - public async getHandleAliases( - connectedAccount: ConnectedAccountWorkspaceEntity, - ) { + public async getHandleAliases(connectedAccount: ConnectedAccountEntity) { const oAuth2Client = await this.oAuth2ClientManagerService.getGoogleOAuth2Client( connectedAccount, diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/microsoft/services/microsoft-email-alias-manager.service.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/microsoft/services/microsoft-email-alias-manager.service.ts index b9126648f9..c466e5df43 100644 --- a/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/microsoft/services/microsoft-email-alias-manager.service.ts +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/microsoft/services/microsoft-email-alias-manager.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common'; import { isNonEmptyString } from '@sniptt/guards'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; @Injectable() export class MicrosoftEmailAliasManagerService { @@ -11,9 +11,7 @@ export class MicrosoftEmailAliasManagerService { private readonly oAuth2ClientManagerService: OAuth2ClientManagerService, ) {} - public async getHandleAliases( - connectedAccount: ConnectedAccountWorkspaceEntity, - ) { + public async getHandleAliases(connectedAccount: ConnectedAccountEntity) { const microsoftClient = await this.oAuth2ClientManagerService.getMicrosoftOAuth2Client( connectedAccount, diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts index e2d17369bd..03ddb6b2b2 100644 --- a/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; -import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GmailEmailAliasErrorHandlerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-error-handler.service'; import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service'; import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/services/microsoft-email-alias-manager.service'; @@ -8,7 +9,10 @@ import { EmailAliasManagerService } from 'src/modules/connected-account/email-al import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-client-manager/oauth2-client-manager.module'; @Module({ - imports: [OAuth2ClientManagerModule, ConnectedAccountDataAccessModule], + imports: [ + OAuth2ClientManagerModule, + TypeOrmModule.forFeature([ConnectedAccountEntity]), + ], providers: [ EmailAliasManagerService, GoogleEmailAliasManagerService, diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts index 85550b2e43..1e25151c05 100644 --- a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts @@ -1,21 +1,21 @@ import { Test, type TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; import { ConnectedAccountProvider } from 'twenty-shared/types'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service'; import { microsoftGraphMeResponseWithProxyAddresses } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/mocks/microsoft-api-examples'; import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/services/microsoft-email-alias-manager.service'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { EmailAliasManagerService } from './email-alias-manager.service'; describe('Email Alias Manager Service', () => { let emailAliasManagerService: EmailAliasManagerService; let microsoftEmailAliasManagerService: MicrosoftEmailAliasManagerService; - const mockConnectedAccountDataAccessService = { + const mockConnectedAccountRepository = { // @ts-expect-error legacy noImplicitAny update: jest.fn().mockResolvedValue((arg) => arg), }; @@ -33,8 +33,8 @@ describe('Email Alias Manager Service', () => { }, EmailAliasManagerService, { - provide: ConnectedAccountDataAccessService, - useValue: mockConnectedAccountDataAccessService, + provide: getRepositoryToken(ConnectedAccountEntity), + useValue: mockConnectedAccountRepository, }, { provide: GoogleEmailAliasManagerService, @@ -73,19 +73,21 @@ describe('Email Alias Manager Service', () => { describe('Refresh handle aliases for Microsoft', () => { it('Should refresh Microsoft handle aliases successfully', async () => { - const mockConnectedAccount: Partial = { + const mockConnectedAccount: Partial = { id: 'test-id', provider: ConnectedAccountProvider.MICROSOFT, refreshToken: 'test-refresh-token', }; - const expectedAliases = - 'bertrand2@domain.onmicrosoft.com,bertrand3@otherdomain.com'; + const expectedAliases = [ + 'bertrand2@domain.onmicrosoft.com', + 'bertrand3@otherdomain.com', + ]; jest.spyOn(microsoftEmailAliasManagerService, 'getHandleAliases'); await emailAliasManagerService.refreshHandleAliases( - mockConnectedAccount as ConnectedAccountWorkspaceEntity, + mockConnectedAccount as ConnectedAccountEntity, 'test-workspace-id', ); @@ -93,9 +95,8 @@ describe('Email Alias Manager Service', () => { microsoftEmailAliasManagerService.getHandleAliases, ).toHaveBeenCalledWith(mockConnectedAccount); - expect(mockConnectedAccountDataAccessService.update).toHaveBeenCalledWith( - 'test-workspace-id', - { id: mockConnectedAccount.id }, + expect(mockConnectedAccountRepository.update).toHaveBeenCalledWith( + { id: mockConnectedAccount.id, workspaceId: 'test-workspace-id' }, { handleAliases: expectedAliases, }, diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts index ba6299b271..4741748859 100644 --- a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts @@ -1,14 +1,15 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { ConnectedAccountProvider } from 'twenty-shared/types'; import { assertUnreachable } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service'; import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/services/microsoft-email-alias-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; @Injectable() export class EmailAliasManagerService { @@ -16,13 +17,14 @@ export class EmailAliasManagerService { private readonly googleEmailAliasManagerService: GoogleEmailAliasManagerService, private readonly microsoftEmailAliasManagerService: MicrosoftEmailAliasManagerService, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, ) {} public async refreshHandleAliases( - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, workspaceId: string, - ) { + ): Promise { let handleAliases: string[]; switch (connectedAccount.provider) { @@ -53,13 +55,14 @@ export class EmailAliasManagerService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.connectedAccountDataAccessService.update( - workspaceId, - { id: connectedAccount.id }, + await this.connectedAccountRepository.update( + { id: connectedAccount.id, workspaceId }, { - handleAliases: handleAliases.join(','), // TODO: modify handleAliases to be of fieldmetadatatype array + handleAliases, }, ); }, authContext); + + return handleAliases; } } diff --git a/packages/twenty-server/src/modules/connected-account/imap-api/imap-apis.module.ts b/packages/twenty-server/src/modules/connected-account/imap-api/imap-apis.module.ts index be029ad60c..89870cb6b9 100644 --- a/packages/twenty-server/src/modules/connected-account/imap-api/imap-apis.module.ts +++ b/packages/twenty-server/src/modules/connected-account/imap-api/imap-apis.module.ts @@ -5,26 +5,32 @@ import { AuthModule } from 'src/engine/core-modules/auth/auth.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { MessageQueueModule } from 'src/engine/core-modules/message-queue/message-queue.module'; import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module'; -import { CalendarChannelDataAccessModule } from 'src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module'; -import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module'; -import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module'; import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module'; import { ImapSmtpCalDavAPIService } from 'src/modules/connected-account/services/imap-smtp-caldav-apis.service'; +import { MessagingFolderSyncManagerModule } from 'src/modules/messaging/message-folder-manager/messaging-folder-sync-manager.module'; @Module({ imports: [ - TypeOrmModule.forFeature([ObjectMetadataEntity]), + TypeOrmModule.forFeature([ + ObjectMetadataEntity, + CalendarChannelEntity, + ConnectedAccountEntity, + MessageChannelEntity, + UserWorkspaceEntity, + ]), MessageQueueModule, WorkspaceEventEmitterModule, TwentyConfigModule, TwentyORMModule, FeatureFlagModule, AuthModule, - CalendarChannelDataAccessModule, - ConnectedAccountDataAccessModule, - MessageChannelDataAccessModule, + MessagingFolderSyncManagerModule, ], providers: [ImapSmtpCalDavAPIService], exports: [ImapSmtpCalDavAPIService], diff --git a/packages/twenty-server/src/modules/connected-account/jobs/delete-workspace-member-connected-accounts.job.ts b/packages/twenty-server/src/modules/connected-account/jobs/delete-workspace-member-connected-accounts.job.ts index d8f24089aa..90c4963cd3 100644 --- a/packages/twenty-server/src/modules/connected-account/jobs/delete-workspace-member-connected-accounts.job.ts +++ b/packages/twenty-server/src/modules/connected-account/jobs/delete-workspace-member-connected-accounts.job.ts @@ -1,9 +1,15 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { type Repository } from 'typeorm'; + import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; +import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; export type DeleteWorkspaceMemberConnectedAccountsCleanupJobData = { workspaceId: string; @@ -14,7 +20,10 @@ export type DeleteWorkspaceMemberConnectedAccountsCleanupJobData = { export class DeleteWorkspaceMemberConnectedAccountsCleanupJob { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, ) {} @Process(DeleteWorkspaceMemberConnectedAccountsCleanupJob.name) @@ -26,8 +35,31 @@ export class DeleteWorkspaceMemberConnectedAccountsCleanupJob { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.connectedAccountDataAccessService.delete(workspaceId, { - accountOwnerId: workspaceMemberId, + const workspaceMemberRepo = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workspaceMember', + ); + + const member = await workspaceMemberRepo.findOne({ + where: { id: workspaceMemberId }, + }); + + if (!member) { + return; + } + + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { userId: member.userId, workspaceId }, + }); + + if (!userWorkspace) { + return; + } + + await this.connectedAccountRepository.delete({ + userWorkspaceId: userWorkspace.id, + workspaceId, }); }, authContext); } diff --git a/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts b/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts index 297c7eedcb..631f0f0e79 100644 --- a/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts +++ b/packages/twenty-server/src/modules/connected-account/listeners/connected-account.listener.ts @@ -1,27 +1,31 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { type ObjectRecordDeleteEvent } from 'twenty-shared/database-events'; +import { type Repository } from 'typeorm'; import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; @Injectable() export class ConnectedAccountListener { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, private readonly accountsToReconnectService: AccountsToReconnectService, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, ) {} @OnDatabaseBatchEvent('connectedAccount', DatabaseEventAction.DESTROYED) async handleDestroyedEvent( payload: WorkspaceEventBatch< - ObjectRecordDeleteEvent + ObjectRecordDeleteEvent >, ) { const workspaceId = payload.workspaceId; @@ -29,19 +33,17 @@ export class ConnectedAccountListener { await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { for (const eventPayload of payload.events) { - const workspaceMemberId = eventPayload.properties.before.accountOwnerId; + const userWorkspaceId = eventPayload.properties.before.userWorkspaceId; - const workspaceMemberRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - 'workspaceMember', - { shouldBypassPermissionChecks: true }, - ); - const workspaceMember = await workspaceMemberRepository.findOneOrFail({ - where: { id: workspaceMemberId }, + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { id: userWorkspaceId }, }); - const userId = workspaceMember.userId; + if (!userWorkspace) { + continue; + } + + const userId = userWorkspace.userId; const connectedAccountId = eventPayload.properties.before.id; diff --git a/packages/twenty-server/src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service.ts b/packages/twenty-server/src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service.ts index 5f85698e07..00c5834a29 100644 --- a/packages/twenty-server/src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service.ts +++ b/packages/twenty-server/src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service.ts @@ -7,7 +7,7 @@ import { CustomError, isDefined } from 'twenty-shared/utils'; import { GoogleOAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/drivers/google/google-oauth2-client-manager.service'; import { MicrosoftOAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/drivers/microsoft/microsoft-oauth2-client-manager.service'; import { OAuth2ClientManagerExceptionCode } from 'src/modules/connected-account/oauth2-client-manager/exceptions/oauth2-client-manager.exceptions'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; @Injectable() export class OAuth2ClientManagerService { @@ -17,10 +17,7 @@ export class OAuth2ClientManagerService { ) {} public async getGoogleOAuth2Client( - connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, - 'provider' | 'refreshToken' - >, + connectedAccount: Pick, ): Promise { if (!isDefined(connectedAccount.refreshToken)) { throw new CustomError( @@ -35,10 +32,7 @@ export class OAuth2ClientManagerService { } public async getMicrosoftOAuth2Client( - connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, - 'provider' | 'accessToken' - >, + connectedAccount: Pick, ): Promise { if (!isDefined(connectedAccount.accessToken)) { throw new CustomError( diff --git a/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts b/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts deleted file mode 100644 index 5eace0312f..0000000000 --- a/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { assertIsDefinedOrThrow } from 'twenty-shared/utils'; -import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; - -import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; -import { type DeleteOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; - -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; -import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; -import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; -import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception'; -import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; -import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; -import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; -@WorkspaceQueryHook(`connectedAccount.destroyOne`) -export class ConnectedAccountDeleteOnePreQueryHook - implements WorkspacePreQueryHookInstance -{ - constructor( - private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, - private readonly workspaceEventEmitter: WorkspaceEventEmitter, - private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, - ) {} - - async execute( - authContext: WorkspaceAuthContext, - _objectName: string, - payload: DeleteOneResolverArgs, - ): Promise { - const connectedAccountId = payload.id; - - const workspace = authContext.workspace; - - assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError); - - const messageChannels = - await this.globalWorkspaceOrmManager.executeInWorkspaceContext( - async () => { - return this.messageChannelDataAccessService.find(workspace.id, { - connectedAccountId, - }); - }, - authContext, - ); - - const { flatObjectMetadataMaps } = - await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId: workspace.id, - flatMapsKeys: ['flatObjectMetadataMaps'], - }, - ); - - const flatObjectMetadata = findFlatEntityByUniversalIdentifierOrThrow({ - flatEntityMaps: flatObjectMetadataMaps, - universalIdentifier: STANDARD_OBJECTS.messageChannel.universalIdentifier, - }); - - // TODO: handle cascade events for delete - this.workspaceEventEmitter.emitDatabaseBatchEvent({ - objectMetadataNameSingular: 'messageChannel', - action: DatabaseEventAction.DESTROYED, - objectMetadata: flatObjectMetadata, - events: messageChannels.map((messageChannel) => ({ - recordId: messageChannel.id, - properties: { - before: messageChannel, - }, - })), - workspaceId: workspace.id, - }); - - return payload; - } -} diff --git a/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-query-hook.module.ts b/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-query-hook.module.ts deleted file mode 100644 index 726adcde16..0000000000 --- a/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-query-hook.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; - -import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; -import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { ConnectedAccountDeleteOnePreQueryHook } from 'src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook'; - -@Module({ - imports: [ - NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity]), - WorkspaceManyOrAllFlatEntityMapsCacheModule, - MessageChannelDataAccessModule, - ], - providers: [ConnectedAccountDeleteOnePreQueryHook], -}) -export class ConnectedAccountQueryHookModule {} diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/connected-account-refresh-tokens-manager.module.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/connected-account-refresh-tokens-manager.module.ts index ea2ed3ec9c..dacea180e0 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/connected-account-refresh-tokens-manager.module.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/connected-account-refresh-tokens-manager.module.ts @@ -1,7 +1,8 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { JwtModule } from 'src/engine/core-modules/jwt/jwt.module'; -import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GoogleAPIRefreshAccessTokenModule } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/google-api-refresh-access-token.module'; import { MicrosoftAPIRefreshAccessTokenModule } from 'src/modules/connected-account/refresh-tokens-manager/drivers/microsoft/microsoft-api-refresh-access-token.module'; import { ConnectedAccountRefreshTokensService } from 'src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service'; @@ -9,7 +10,7 @@ import { ConnectedAccountRefreshTokensService } from 'src/modules/connected-acco @Module({ imports: [ JwtModule, - ConnectedAccountDataAccessModule, + TypeOrmModule.forFeature([ConnectedAccountEntity]), GoogleAPIRefreshAccessTokenModule, MicrosoftAPIRefreshAccessTokenModule, ], diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.spec.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.spec.ts index 04d94bf998..f4055943a0 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.spec.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.spec.ts @@ -1,8 +1,9 @@ import { Test, type TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; import { ConnectedAccountProvider } from 'twenty-shared/types'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service'; import { MicrosoftAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/microsoft/services/microsoft-api-refresh-tokens.service'; @@ -10,7 +11,6 @@ import { ConnectedAccountRefreshAccessTokenException, ConnectedAccountRefreshAccessTokenExceptionCode, } from 'src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { ConnectedAccountRefreshTokensService } from './connected-account-refresh-tokens.service'; @@ -18,7 +18,7 @@ describe('ConnectedAccountRefreshTokensService', () => { let service: ConnectedAccountRefreshTokensService; let googleAPIRefreshAccessTokenService: GoogleAPIRefreshAccessTokenService; let microsoftAPIRefreshAccessTokenService: MicrosoftAPIRefreshAccessTokenService; - let connectedAccountDataAccessService: ConnectedAccountDataAccessService; + let connectedAccountRepository: { update: jest.Mock }; const mockWorkspaceId = 'workspace-123'; const mockConnectedAccountId = 'account-456'; @@ -52,7 +52,7 @@ describe('ConnectedAccountRefreshTokensService', () => { }, }, { - provide: ConnectedAccountDataAccessService, + provide: getRepositoryToken(ConnectedAccountEntity), useValue: { update: jest.fn(), }, @@ -71,10 +71,9 @@ describe('ConnectedAccountRefreshTokensService', () => { module.get( MicrosoftAPIRefreshAccessTokenService, ); - connectedAccountDataAccessService = - module.get( - ConnectedAccountDataAccessService, - ); + connectedAccountRepository = module.get( + getRepositoryToken(ConnectedAccountEntity), + ); }); afterEach(() => { @@ -89,7 +88,7 @@ describe('ConnectedAccountRefreshTokensService', () => { accessToken: mockAccessToken, refreshToken: mockRefreshToken, lastCredentialsRefreshedAt: new Date(Date.now() - 30 * 60 * 1000), // 30 minutes ago - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; const result = await service.refreshAndSaveTokens( connectedAccount, @@ -103,7 +102,7 @@ describe('ConnectedAccountRefreshTokensService', () => { expect( microsoftAPIRefreshAccessTokenService.refreshTokens, ).not.toHaveBeenCalled(); - expect(connectedAccountDataAccessService.update).not.toHaveBeenCalled(); + expect(connectedAccountRepository.update).not.toHaveBeenCalled(); }); it('should refresh and save new Microsoft token when expired (lastCredentialsRefreshedAt is old)', async () => { @@ -113,7 +112,7 @@ describe('ConnectedAccountRefreshTokensService', () => { accessToken: mockAccessToken, refreshToken: mockRefreshToken, lastCredentialsRefreshedAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; const newTokens = { accessToken: mockNewAccessToken, @@ -133,9 +132,8 @@ describe('ConnectedAccountRefreshTokensService', () => { expect( microsoftAPIRefreshAccessTokenService.refreshTokens, ).toHaveBeenCalledWith(mockRefreshToken); - expect(connectedAccountDataAccessService.update).toHaveBeenCalledWith( - mockWorkspaceId, - { id: mockConnectedAccountId }, + expect(connectedAccountRepository.update).toHaveBeenCalledWith( + { id: mockConnectedAccountId, workspaceId: mockWorkspaceId }, expect.objectContaining({ ...newTokens, lastCredentialsRefreshedAt: expect.any(Date), @@ -150,7 +148,7 @@ describe('ConnectedAccountRefreshTokensService', () => { accessToken: mockAccessToken, refreshToken: mockRefreshToken, lastCredentialsRefreshedAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; const newTokens = { accessToken: mockNewAccessToken, @@ -170,9 +168,8 @@ describe('ConnectedAccountRefreshTokensService', () => { expect( googleAPIRefreshAccessTokenService.refreshTokens, ).toHaveBeenCalledWith(mockRefreshToken); - expect(connectedAccountDataAccessService.update).toHaveBeenCalledWith( - mockWorkspaceId, - { id: mockConnectedAccountId }, + expect(connectedAccountRepository.update).toHaveBeenCalledWith( + { id: mockConnectedAccountId, workspaceId: mockWorkspaceId }, expect.objectContaining({ ...newTokens, lastCredentialsRefreshedAt: expect.any(Date), @@ -187,7 +184,7 @@ describe('ConnectedAccountRefreshTokensService', () => { accessToken: mockAccessToken, refreshToken: mockRefreshToken, lastCredentialsRefreshedAt: null, - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; const newTokens = { accessToken: mockNewAccessToken, @@ -207,9 +204,8 @@ describe('ConnectedAccountRefreshTokensService', () => { expect( microsoftAPIRefreshAccessTokenService.refreshTokens, ).toHaveBeenCalledWith(mockRefreshToken); - expect(connectedAccountDataAccessService.update).toHaveBeenCalledWith( - mockWorkspaceId, - { id: mockConnectedAccountId }, + expect(connectedAccountRepository.update).toHaveBeenCalledWith( + { id: mockConnectedAccountId, workspaceId: mockWorkspaceId }, expect.objectContaining({ ...newTokens, lastCredentialsRefreshedAt: expect.any(Date), @@ -224,7 +220,7 @@ describe('ConnectedAccountRefreshTokensService', () => { accessToken: mockAccessToken, refreshToken: null, lastCredentialsRefreshedAt: new Date(Date.now() - 2 * 60 * 60 * 1000), - } as unknown as ConnectedAccountWorkspaceEntity; + } as unknown as ConnectedAccountEntity; await expect( service.refreshAndSaveTokens(connectedAccount, mockWorkspaceId), @@ -243,7 +239,7 @@ describe('ConnectedAccountRefreshTokensService', () => { accessToken: mockAccessToken, refreshToken: mockRefreshToken, lastCredentialsRefreshedAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; const invalidGrantError = new ConnectedAccountRefreshAccessTokenException( 'Microsoft OAuth error: invalid_grant - Token has been revoked', @@ -271,7 +267,7 @@ describe('ConnectedAccountRefreshTokensService', () => { accessToken: mockAccessToken, refreshToken: mockRefreshToken, lastCredentialsRefreshedAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; const networkError = new ConnectedAccountRefreshAccessTokenException( 'Google refresh token network error: ECONNRESET - Network error', @@ -296,7 +292,7 @@ describe('ConnectedAccountRefreshTokensService', () => { id: mockConnectedAccountId, provider: ConnectedAccountProvider.MICROSOFT, lastCredentialsRefreshedAt: new Date(Date.now() - 30 * 60 * 1000), // 30 minutes ago - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; const result = await service.isAccessTokenStillValid(connectedAccount); @@ -308,7 +304,7 @@ describe('ConnectedAccountRefreshTokensService', () => { id: mockConnectedAccountId, provider: ConnectedAccountProvider.GOOGLE, lastCredentialsRefreshedAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; const result = await service.isAccessTokenStillValid(connectedAccount); @@ -320,7 +316,7 @@ describe('ConnectedAccountRefreshTokensService', () => { id: mockConnectedAccountId, provider: ConnectedAccountProvider.MICROSOFT, lastCredentialsRefreshedAt: null, - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; const result = await service.isAccessTokenStillValid(connectedAccount); @@ -332,7 +328,7 @@ describe('ConnectedAccountRefreshTokensService', () => { id: mockConnectedAccountId, provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV, lastCredentialsRefreshedAt: null, - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; const result = await service.isAccessTokenStillValid(connectedAccount); @@ -344,7 +340,7 @@ describe('ConnectedAccountRefreshTokensService', () => { id: mockConnectedAccountId, provider: ConnectedAccountProvider.OIDC, lastCredentialsRefreshedAt: null, - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; const result = await service.isAccessTokenStillValid(connectedAccount); @@ -356,7 +352,7 @@ describe('ConnectedAccountRefreshTokensService', () => { id: mockConnectedAccountId, provider: ConnectedAccountProvider.SAML, lastCredentialsRefreshedAt: null, - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; const result = await service.isAccessTokenStillValid(connectedAccount); @@ -372,7 +368,7 @@ describe('ConnectedAccountRefreshTokensService', () => { accessToken: mockAccessToken, refreshToken: mockRefreshToken, lastCredentialsRefreshedAt: null, - } as unknown as ConnectedAccountWorkspaceEntity; + } as unknown as ConnectedAccountEntity; const result = await service.refreshAndSaveTokens( connectedAccount, @@ -389,7 +385,7 @@ describe('ConnectedAccountRefreshTokensService', () => { expect( microsoftAPIRefreshAccessTokenService.refreshTokens, ).not.toHaveBeenCalled(); - expect(connectedAccountDataAccessService.update).not.toHaveBeenCalled(); + expect(connectedAccountRepository.update).not.toHaveBeenCalled(); }); it('should reuse existing tokens for SAML without attempting a refresh', async () => { @@ -399,7 +395,7 @@ describe('ConnectedAccountRefreshTokensService', () => { accessToken: mockAccessToken, refreshToken: mockRefreshToken, lastCredentialsRefreshedAt: null, - } as unknown as ConnectedAccountWorkspaceEntity; + } as unknown as ConnectedAccountEntity; const result = await service.refreshAndSaveTokens( connectedAccount, @@ -416,7 +412,7 @@ describe('ConnectedAccountRefreshTokensService', () => { expect( microsoftAPIRefreshAccessTokenService.refreshTokens, ).not.toHaveBeenCalled(); - expect(connectedAccountDataAccessService.update).not.toHaveBeenCalled(); + expect(connectedAccountRepository.update).not.toHaveBeenCalled(); }); }); }); diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts index 35849aa3a4..2f39f496ad 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts @@ -1,9 +1,11 @@ import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { ConnectedAccountProvider } from 'twenty-shared/types'; import { assertUnreachable, isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service'; @@ -12,7 +14,6 @@ import { ConnectedAccountRefreshAccessTokenException, ConnectedAccountRefreshAccessTokenExceptionCode, } from 'src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; export type ConnectedAccountTokens = { accessToken: string; @@ -31,11 +32,12 @@ export class ConnectedAccountRefreshTokensService { private readonly googleAPIRefreshAccessTokenService: GoogleAPIRefreshAccessTokenService, private readonly microsoftAPIRefreshAccessTokenService: MicrosoftAPIRefreshAccessTokenService, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, ) {} async refreshAndSaveTokens( - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, workspaceId: string, ): Promise { const { refreshToken, accessToken } = connectedAccount; @@ -80,9 +82,8 @@ export class ConnectedAccountRefreshTokensService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.connectedAccountDataAccessService.update( - workspaceId, - { id: connectedAccount.id }, + await this.connectedAccountRepository.update( + { id: connectedAccount.id, workspaceId }, { ...connectedAccountTokens, lastCredentialsRefreshedAt: new Date(), @@ -94,7 +95,7 @@ export class ConnectedAccountRefreshTokensService { } async isAccessTokenStillValid( - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, ): Promise { switch (connectedAccount.provider) { case ConnectedAccountProvider.GOOGLE: @@ -126,7 +127,7 @@ export class ConnectedAccountRefreshTokensService { } async refreshTokens( - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, refreshToken: string, workspaceId: string, ): Promise { diff --git a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.spec.ts b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.spec.ts index 8e100b547b..015f5a65ea 100644 --- a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.spec.ts +++ b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.spec.ts @@ -1,18 +1,18 @@ import { Test, type 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 { CreateMessageChannelService } from 'src/engine/core-modules/auth/services/create-message-channel.service'; import { type EmailAccountConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { ImapSmtpCalDavAPIService } from 'src/modules/connected-account/services/imap-smtp-caldav-apis.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service'; jest.mock('uuid', () => ({ v4: jest.fn(() => 'mocked-uuid'), @@ -21,21 +21,38 @@ jest.mock('uuid', () => ({ describe('ImapSmtpCalDavAPIService', () => { let service: ImapSmtpCalDavAPIService; - const mockConnectedAccountDataAccessService = { + const mockTransactionManagerSave = jest.fn(); + const mockTransactionManager = { + getRepository: jest + .fn() + .mockReturnValue({ save: mockTransactionManagerSave }), + }; + + const mockConnectedAccountRepository = { findOne: jest.fn(), save: jest.fn(), + manager: { + transaction: jest.fn((callback) => callback(mockTransactionManager)), + }, }; - const mockMessageChannelDataAccessService = { + const mockMessageChannelRepository = { findOne: jest.fn(), }; - const mockCalendarChannelDataAccessService = { + const mockCalendarChannelRepository = { findOne: jest.fn(), }; - const mockWorkspaceDataSource = { - transaction: jest.fn((callback) => callback({})), + const mockUserWorkspaceRepository = { + findOne: jest.fn().mockResolvedValue({ id: 'user-workspace-id' }), + }; + + const mockWorkspaceMemberRepository = { + findOne: jest.fn().mockResolvedValue({ + id: 'workspace-member-id', + userId: 'user-id', + }), }; const mockCreateMessageChannelService = { @@ -53,9 +70,14 @@ describe('ImapSmtpCalDavAPIService', () => { { provide: GlobalWorkspaceOrmManager, useValue: { - getGlobalWorkspaceDataSource: jest + getRepository: jest .fn() - .mockResolvedValue(mockWorkspaceDataSource), + .mockImplementation((_workspaceId, entity) => { + if (entity === 'workspaceMember') + return mockWorkspaceMemberRepository; + + return {}; + }), executeInWorkspaceContext: jest .fn() @@ -71,16 +93,26 @@ describe('ImapSmtpCalDavAPIService', () => { useValue: mockCreateCalendarChannelService, }, { - provide: ConnectedAccountDataAccessService, - useValue: mockConnectedAccountDataAccessService, + provide: getRepositoryToken(ConnectedAccountEntity), + useValue: mockConnectedAccountRepository, }, { - provide: MessageChannelDataAccessService, - useValue: mockMessageChannelDataAccessService, + provide: getRepositoryToken(MessageChannelEntity), + useValue: mockMessageChannelRepository, }, { - provide: CalendarChannelDataAccessService, - useValue: mockCalendarChannelDataAccessService, + provide: getRepositoryToken(CalendarChannelEntity), + useValue: mockCalendarChannelRepository, + }, + { + provide: getRepositoryToken(UserWorkspaceEntity), + useValue: mockUserWorkspaceRepository, + }, + { + provide: SyncMessageFoldersService, + useValue: { + syncMessageFolders: jest.fn().mockResolvedValue([]), + }, }, ], }).compile(); @@ -113,23 +145,27 @@ describe('ImapSmtpCalDavAPIService', () => { }; it('should create new account with message channel when account does not exist and IMAP is configured', async () => { - mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null); - mockMessageChannelDataAccessService.findOne.mockResolvedValue(null); - mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null); + mockConnectedAccountRepository.findOne.mockResolvedValue(null); + mockMessageChannelRepository.findOne.mockResolvedValue(null); + mockCalendarChannelRepository.findOne.mockResolvedValue(null); + mockWorkspaceMemberRepository.findOne.mockResolvedValue({ + id: 'workspace-member-id', + userId: 'user-id', + }); + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', + }); await service.processAccount(baseInput); - expect(mockConnectedAccountDataAccessService.save).toHaveBeenCalledWith( - 'workspace-id', - { - id: 'mocked-uuid', - handle: 'test@example.com', - provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV, - connectionParameters: baseInput.connectionParameters, - accountOwnerId: 'workspace-member-id', - }, - {}, - ); + expect(mockTransactionManagerSave).toHaveBeenCalledWith({ + id: 'mocked-uuid', + handle: 'test@example.com', + provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV, + connectionParameters: baseInput.connectionParameters, + userWorkspaceId: 'user-workspace-id', + workspaceId: 'workspace-id', + }); expect( mockCreateMessageChannelService.createMessageChannel, @@ -137,7 +173,7 @@ describe('ImapSmtpCalDavAPIService', () => { workspaceId: 'workspace-id', connectedAccountId: 'mocked-uuid', handle: 'test@example.com', - manager: {}, + transactionManager: mockTransactionManager, }); expect( @@ -149,29 +185,34 @@ describe('ImapSmtpCalDavAPIService', () => { const existingAccount = { id: 'existing-account-id', handle: 'test@example.com', - accountOwnerId: 'workspace-member-id', + userWorkspaceId: 'user-workspace-id', provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV, - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; const existingMessageChannel = { id: 'existing-message-channel-id', connectedAccountId: 'existing-account-id', - } as MessageChannelWorkspaceEntity; + } as MessageChannelEntity; const existingCalendarChannel = { id: 'existing-calendar-channel-id', connectedAccountId: 'existing-account-id', - } as CalendarChannelWorkspaceEntity; + } as CalendarChannelEntity; - mockConnectedAccountDataAccessService.findOne.mockResolvedValue( - existingAccount, - ); - mockMessageChannelDataAccessService.findOne.mockResolvedValue( + mockConnectedAccountRepository.findOne.mockResolvedValue(existingAccount); + mockMessageChannelRepository.findOne.mockResolvedValue( existingMessageChannel, ); - mockCalendarChannelDataAccessService.findOne.mockResolvedValue( + mockCalendarChannelRepository.findOne.mockResolvedValue( existingCalendarChannel, ); + mockWorkspaceMemberRepository.findOne.mockResolvedValue({ + id: 'workspace-member-id', + userId: 'user-id', + }); + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', + }); const inputWithConnectedAccountId = { ...baseInput, @@ -180,17 +221,14 @@ describe('ImapSmtpCalDavAPIService', () => { await service.processAccount(inputWithConnectedAccountId); - expect(mockConnectedAccountDataAccessService.save).toHaveBeenCalledWith( - 'workspace-id', - { - id: 'existing-account-id', - handle: 'test@example.com', - provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV, - connectionParameters: baseInput.connectionParameters, - accountOwnerId: 'workspace-member-id', - }, - {}, - ); + expect(mockTransactionManagerSave).toHaveBeenCalledWith({ + id: 'existing-account-id', + handle: 'test@example.com', + provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV, + connectionParameters: baseInput.connectionParameters, + userWorkspaceId: 'user-workspace-id', + workspaceId: 'workspace-id', + }); expect( mockCreateMessageChannelService.createMessageChannel, @@ -213,9 +251,16 @@ describe('ImapSmtpCalDavAPIService', () => { } as EmailAccountConnectionParameters, }; - mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null); - mockMessageChannelDataAccessService.findOne.mockResolvedValue(null); - mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null); + mockConnectedAccountRepository.findOne.mockResolvedValue(null); + mockMessageChannelRepository.findOne.mockResolvedValue(null); + mockCalendarChannelRepository.findOne.mockResolvedValue(null); + mockWorkspaceMemberRepository.findOne.mockResolvedValue({ + id: 'workspace-member-id', + userId: 'user-id', + }); + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', + }); await service.processAccount(imapOnlyInput); @@ -241,9 +286,16 @@ describe('ImapSmtpCalDavAPIService', () => { } as EmailAccountConnectionParameters, }; - mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null); - mockMessageChannelDataAccessService.findOne.mockResolvedValue(null); - mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null); + mockConnectedAccountRepository.findOne.mockResolvedValue(null); + mockMessageChannelRepository.findOne.mockResolvedValue(null); + mockCalendarChannelRepository.findOne.mockResolvedValue(null); + mockWorkspaceMemberRepository.findOne.mockResolvedValue({ + id: 'workspace-member-id', + userId: 'user-id', + }); + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', + }); await service.processAccount(caldavOnlyInput); @@ -275,9 +327,16 @@ describe('ImapSmtpCalDavAPIService', () => { } as EmailAccountConnectionParameters, }; - mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null); - mockMessageChannelDataAccessService.findOne.mockResolvedValue(null); - mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null); + mockConnectedAccountRepository.findOne.mockResolvedValue(null); + mockMessageChannelRepository.findOne.mockResolvedValue(null); + mockCalendarChannelRepository.findOne.mockResolvedValue(null); + mockWorkspaceMemberRepository.findOne.mockResolvedValue({ + id: 'workspace-member-id', + userId: 'user-id', + }); + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', + }); await service.processAccount(imapSmtpInput); @@ -316,9 +375,16 @@ describe('ImapSmtpCalDavAPIService', () => { } as EmailAccountConnectionParameters, }; - mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null); - mockMessageChannelDataAccessService.findOne.mockResolvedValue(null); - mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null); + mockConnectedAccountRepository.findOne.mockResolvedValue(null); + mockMessageChannelRepository.findOne.mockResolvedValue(null); + mockCalendarChannelRepository.findOne.mockResolvedValue(null); + mockWorkspaceMemberRepository.findOne.mockResolvedValue({ + id: 'workspace-member-id', + userId: 'user-id', + }); + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', + }); await service.processAccount(fullConfigInput); @@ -334,39 +400,42 @@ describe('ImapSmtpCalDavAPIService', () => { const existingAccount = { id: 'existing-account-id', handle: 'test@example.com', - accountOwnerId: 'workspace-member-id', + userWorkspaceId: 'user-workspace-id', provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV, - } as ConnectedAccountWorkspaceEntity; + } as ConnectedAccountEntity; - mockConnectedAccountDataAccessService.findOne.mockResolvedValueOnce( + mockConnectedAccountRepository.findOne.mockResolvedValueOnce( existingAccount, ); - mockMessageChannelDataAccessService.findOne.mockResolvedValue(null); - mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null); + mockMessageChannelRepository.findOne.mockResolvedValue(null); + mockCalendarChannelRepository.findOne.mockResolvedValue(null); + mockWorkspaceMemberRepository.findOne.mockResolvedValue({ + id: 'workspace-member-id', + userId: 'user-id', + }); + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', + }); await service.processAccount(baseInput); - expect( - mockConnectedAccountDataAccessService.findOne, - ).toHaveBeenCalledWith('workspace-id', { + expect(mockConnectedAccountRepository.findOne).toHaveBeenCalledWith({ where: { handle: 'test@example.com', - accountOwnerId: 'workspace-member-id', + userWorkspaceId: 'user-workspace-id', + workspaceId: 'workspace-id', }, }); - expect(mockConnectedAccountDataAccessService.save).toHaveBeenCalledWith( - 'workspace-id', - { - id: 'existing-account-id', - handle: 'test@example.com', - provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV, - connectionParameters: baseInput.connectionParameters, - accountOwnerId: 'workspace-member-id', - }, - {}, - ); + expect(mockTransactionManagerSave).toHaveBeenCalledWith({ + id: 'existing-account-id', + handle: 'test@example.com', + provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV, + connectionParameters: baseInput.connectionParameters, + userWorkspaceId: 'user-workspace-id', + workspaceId: 'workspace-id', + }); }); it('should not create channels when neither IMAP nor CALDAV is configured', async () => { @@ -383,9 +452,16 @@ describe('ImapSmtpCalDavAPIService', () => { } as EmailAccountConnectionParameters, }; - mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null); - mockMessageChannelDataAccessService.findOne.mockResolvedValue(null); - mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null); + mockConnectedAccountRepository.findOne.mockResolvedValue(null); + mockMessageChannelRepository.findOne.mockResolvedValue(null); + mockCalendarChannelRepository.findOne.mockResolvedValue(null); + mockWorkspaceMemberRepository.findOne.mockResolvedValue({ + id: 'workspace-member-id', + userId: 'user-id', + }); + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', + }); await service.processAccount(smtpOnlyInput); @@ -398,15 +474,22 @@ describe('ImapSmtpCalDavAPIService', () => { }); it('should handle transaction correctly', async () => { - mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null); - mockMessageChannelDataAccessService.findOne.mockResolvedValue(null); - mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null); + mockConnectedAccountRepository.findOne.mockResolvedValue(null); + mockMessageChannelRepository.findOne.mockResolvedValue(null); + mockCalendarChannelRepository.findOne.mockResolvedValue(null); + mockWorkspaceMemberRepository.findOne.mockResolvedValue({ + id: 'workspace-member-id', + userId: 'user-id', + }); + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', + }); await service.processAccount(baseInput); - expect(mockWorkspaceDataSource.transaction).toHaveBeenCalledWith( - expect.any(Function), - ); + expect( + mockConnectedAccountRepository.manager.transaction, + ).toHaveBeenCalledWith(expect.any(Function)); }); }); }); diff --git a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts index 1907a96c4b..f9e4ec8f2f 100644 --- a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts +++ b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts @@ -1,45 +1,58 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { ConnectedAccountProvider } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; +import { EntityManager, Repository } from 'typeorm'; +import { NotFoundError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { CreateCalendarChannelService } from 'src/engine/core-modules/auth/services/create-calendar-channel.service'; import { CreateMessageChannelService } from 'src/engine/core-modules/auth/services/create-message-channel.service'; import { type EmailAccountConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto'; -import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; -import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; +import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service'; @Injectable() export class ImapSmtpCalDavAPIService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, + @InjectRepository(CalendarChannelEntity) + private readonly calendarChannelRepository: Repository, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, private readonly createMessageChannelService: CreateMessageChannelService, private readonly createCalendarChannelService: CreateCalendarChannelService, + private readonly syncMessageFoldersService: SyncMessageFoldersService, ) {} async getImapSmtpCaldavConnectedAccount( workspaceId: string, id: string, - ): Promise { + ): Promise { const authContext = buildSystemAuthContext(workspaceId); return this.globalWorkspaceOrmManager.executeInWorkspaceContext( async () => { - const connectedAccount = - await this.connectedAccountDataAccessService.findOne(workspaceId, { - where: { id, provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV }, - }); + const connectedAccount = await this.connectedAccountRepository.findOne({ + where: { + id, + provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV, + workspaceId, + }, + }); - return connectedAccount as ConnectedAccountWorkspaceEntity | null; + return connectedAccount; }, authContext, ); @@ -59,29 +72,59 @@ export class ImapSmtpCalDavAPIService { return this.globalWorkspaceOrmManager.executeInWorkspaceContext( async () => { + // Resolve userWorkspaceId from workspaceMemberId + const workspaceMemberRepo = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workspaceMember', + ); + + const member = await workspaceMemberRepo.findOne({ + where: { id: workspaceMemberId }, + }); + + if (!member) { + throw new NotFoundError( + `Workspace member with id ${workspaceMemberId} not found`, + ); + } + + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { userId: member.userId, workspaceId }, + }); + + if (!userWorkspace) { + throw new NotFoundError( + `UserWorkspace not found for userId ${member.userId} in workspace ${workspaceId}`, + ); + } + + const userWorkspaceId = userWorkspace.id; + const existingAccount = connectedAccountId - ? await this.connectedAccountDataAccessService.findOne(workspaceId, { - where: { id: connectedAccountId }, + ? await this.connectedAccountRepository.findOne({ + where: { id: connectedAccountId, workspaceId }, }) - : await this.connectedAccountDataAccessService.findOne(workspaceId, { - where: { handle, accountOwnerId: workspaceMemberId }, + : await this.connectedAccountRepository.findOne({ + where: { + handle, + userWorkspaceId, + workspaceId, + }, }); const newOrExistingAccountId = existingAccount?.id ?? connectedAccountId ?? v4(); - const workspaceDataSource = - await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource(); - const existingMessageChannel = existingAccount - ? await this.messageChannelDataAccessService.findOne(workspaceId, { - where: { connectedAccountId: existingAccount.id }, + ? await this.messageChannelRepository.findOne({ + where: { connectedAccountId: existingAccount.id, workspaceId }, }) : null; const existingCalendarChannel = existingAccount - ? await this.calendarChannelDataAccessService.findOne(workspaceId, { - where: { connectedAccountId: existingAccount.id }, + ? await this.calendarChannelRepository.findOne({ + where: { connectedAccountId: existingAccount.id, workspaceId }, }) : null; @@ -93,26 +136,25 @@ export class ImapSmtpCalDavAPIService { !isDefined(existingCalendarChannel) && Boolean(input.connectionParameters.CALDAV); - await workspaceDataSource.transaction( - async (manager: WorkspaceEntityManager) => { - await this.connectedAccountDataAccessService.save( - workspaceId, - { + await this.connectedAccountRepository.manager.transaction( + async (transactionManager: EntityManager) => { + await transactionManager + .getRepository(ConnectedAccountEntity) + .save({ id: newOrExistingAccountId, handle, provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV, connectionParameters: input.connectionParameters, - accountOwnerId: workspaceMemberId, - }, - manager, - ); + userWorkspaceId, + workspaceId, + }); if (shouldCreateMessageChannel) { await this.createMessageChannelService.createMessageChannel({ workspaceId, connectedAccountId: newOrExistingAccountId, handle, - manager, + transactionManager, }); } @@ -121,12 +163,31 @@ export class ImapSmtpCalDavAPIService { workspaceId, connectedAccountId: newOrExistingAccountId, handle, - manager, + transactionManager, }); } }, ); + if (shouldCreateMessageChannel) { + const newMessageChannel = await this.messageChannelRepository.findOne( + { + where: { + connectedAccountId: newOrExistingAccountId, + workspaceId, + }, + relations: ['connectedAccount', 'messageFolders'], + }, + ); + + if (isDefined(newMessageChannel)) { + await this.syncMessageFoldersService.syncMessageFolders({ + messageChannel: newMessageChannel, + workspaceId, + }); + } + } + return newOrExistingAccountId; }, authContext, diff --git a/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts b/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts index 811b8f9714..17dcfd0f82 100644 --- a/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts +++ b/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts @@ -7,8 +7,6 @@ import { type ImapSmtpCaldavParams } from 'src/engine/core-modules/imap-smtp-cal import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { type FieldTypeAndNameMetadata } from 'src/engine/workspace-manager/utils/get-ts-vector-column-expression.util'; import { type EntityRelation } from 'src/engine/workspace-manager/workspace-migration/types/entity-relation.interface'; -import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; const HANDLE_FIELD_NAME = 'handle'; @@ -25,11 +23,9 @@ export class ConnectedAccountWorkspaceEntity extends BaseWorkspaceEntity { lastCredentialsRefreshedAt: Date | null; lastSyncHistoryId: string | null; authFailedAt: Date | null; - handleAliases: string | null; + handleAliases: string[] | null; scopes: string[] | null; connectionParameters: ImapSmtpCaldavParams | null; accountOwner: EntityRelation; accountOwnerId: string; - messageChannels: EntityRelation; - calendarChannels: EntityRelation; } diff --git a/packages/twenty-server/src/modules/contact-creation-manager/contact-creation-manager.module.ts b/packages/twenty-server/src/modules/contact-creation-manager/contact-creation-manager.module.ts index 852b36f5b3..456080f93f 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/contact-creation-manager.module.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/contact-creation-manager.module.ts @@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { SecureHttpClientModule } from 'src/engine/core-modules/secure-http-client/secure-http-client.module'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; @@ -13,7 +14,7 @@ import { CreatePersonService } from 'src/modules/contact-creation-manager/servic @Module({ imports: [ WorkspaceDataSourceModule, - TypeOrmModule.forFeature([FeatureFlagEntity]), + TypeOrmModule.forFeature([FeatureFlagEntity, UserWorkspaceEntity]), TypeOrmModule.forFeature([ObjectMetadataEntity, FieldMetadataEntity]), SecureHttpClientModule, ], diff --git a/packages/twenty-server/src/modules/contact-creation-manager/jobs/create-company-and-contact.job.ts b/packages/twenty-server/src/modules/contact-creation-manager/jobs/create-company-and-contact.job.ts index 15ba9e84cf..3310b3730f 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/jobs/create-company-and-contact.job.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/jobs/create-company-and-contact.job.ts @@ -3,12 +3,12 @@ import { type FieldActorSource } from 'twenty-shared/types'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { CreateCompanyAndPersonService } from 'src/modules/contact-creation-manager/services/create-company-and-contact.service'; export type CreateCompanyAndContactJobData = { workspaceId: string; - connectedAccount: ConnectedAccountWorkspaceEntity; + connectedAccount: ConnectedAccountEntity; contactsToCreate: { displayName: string; handle: string; diff --git a/packages/twenty-server/src/modules/contact-creation-manager/services/__tests__/create-company-and-contact.service.spec.ts b/packages/twenty-server/src/modules/contact-creation-manager/services/__tests__/create-company-and-contact.service.spec.ts index 48d8c5a921..748b316f47 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/services/__tests__/create-company-and-contact.service.spec.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/services/__tests__/create-company-and-contact.service.spec.ts @@ -1,10 +1,12 @@ import { Test, type TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; import { FieldActorSource } from 'twenty-shared/types'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { CreateCompanyAndPersonService } from 'src/modules/contact-creation-manager/services/create-company-and-contact.service'; import { CreateCompanyService } from 'src/modules/contact-creation-manager/services/create-company.service'; import { CreatePersonService } from 'src/modules/contact-creation-manager/services/create-person.service'; @@ -19,7 +21,7 @@ describe('CreateCompanyAndPersonService', () => { accountOwner: { id: 'workspace-member-1', }, - } as unknown as ConnectedAccountWorkspaceEntity; + } as unknown as ConnectedAccountEntity; beforeEach(async () => { const mockCreateCompaniesService = { @@ -48,6 +50,12 @@ describe('CreateCompanyAndPersonService', () => { provide: ExceptionHandlerService, useValue: {}, }, + { + provide: getRepositoryToken(UserWorkspaceEntity), + useValue: { + findOne: jest.fn(), + }, + }, ], }).compile(); @@ -106,6 +114,7 @@ describe('CreateCompanyAndPersonService', () => { mockExistingPeople, FieldActorSource.CALENDAR, mockConnectedAccount, + null, ); expect(result.contactsThatNeedPersonCreate).toHaveLength(1); @@ -121,6 +130,7 @@ describe('CreateCompanyAndPersonService', () => { mockExistingPeople, FieldActorSource.CALENDAR, mockConnectedAccount, + null, ); expect(result.contactsThatNeedPersonRestore).toHaveLength(2); diff --git a/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts b/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts index e14e8982ca..3dd2fd89bc 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { isNonEmptyString, isNull } from '@sniptt/guards'; import chunk from 'lodash.chunk'; @@ -8,13 +9,14 @@ import { type FieldActorSource, } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { type DeepPartial } from 'typeorm'; +import { type DeepPartial, type Repository } from 'typeorm'; import { v4 } from 'uuid'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { CONTACTS_CREATION_BATCH_SIZE } from 'src/modules/contact-creation-manager/constants/contacts-creation-batch-size.constant'; import { CreateCompanyService } from 'src/modules/contact-creation-manager/services/create-company.service'; import { CreatePersonService } from 'src/modules/contact-creation-manager/services/create-person.service'; @@ -36,13 +38,16 @@ export class CreateCompanyAndPersonService { private readonly createCompaniesService: CreateCompanyService, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, private readonly exceptionHandlerService: ExceptionHandlerService, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, ) {} async createCompaniesAndPeople( - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, contactsToCreate: Contact[], workspaceId: string, source: FieldActorSource, + accountOwner: WorkspaceMemberWorkspaceEntity | null, ): Promise[]> { if (!contactsToCreate || contactsToCreate.length === 0) { return []; @@ -105,6 +110,7 @@ export class CreateCompanyAndPersonService { alreadyCreatedPeople, source, connectedAccount, + accountOwner, ); const companiesMap = @@ -117,7 +123,7 @@ export class CreateCompanyAndPersonService { contactsToCreate: contactsThatNeedPersonCreate, createdBy: { source: source, - workspaceMember: connectedAccount.accountOwner, + workspaceMember: accountOwner, context: { provider: connectedAccount.provider, }, @@ -148,7 +154,7 @@ export class CreateCompanyAndPersonService { } async createCompaniesAndPeopleAndUpdateParticipants( - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, contactsToCreate: Contact[], workspaceId: string, source: FieldActorSource, @@ -160,29 +166,31 @@ export class CreateCompanyAndPersonService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - if (!connectedAccount.accountOwner) { - const workspaceMemberRepository = - await this.globalWorkspaceOrmManager.getRepository( - workspaceId, - WorkspaceMemberWorkspaceEntity, - ); + const accountOwner = + await this.globalWorkspaceOrmManager.executeInWorkspaceContext( + async () => { + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { id: connectedAccount.userWorkspaceId }, + }); - const workspaceMember = await workspaceMemberRepository.findOne({ - where: { - id: connectedAccount.accountOwnerId, - }, - }); + if (!userWorkspace) { + throw new Error( + `UserWorkspace with id ${connectedAccount.userWorkspaceId} not found`, + ); + } - if (!workspaceMember) { - throw new Error( - `Workspace member with id ${connectedAccount.accountOwnerId} not found in workspace ${workspaceId}`, - ); - } + const workspaceMemberRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + WorkspaceMemberWorkspaceEntity, + ); - connectedAccount.accountOwner = workspaceMember; - } - }, authContext); + return workspaceMemberRepository.findOne({ + where: { userId: userWorkspace.userId }, + }); + }, + authContext, + ); for (const contactsBatch of contactsBatches) { try { @@ -191,6 +199,7 @@ export class CreateCompanyAndPersonService { contactsBatch, workspaceId, source, + accountOwner, ); } catch (error) { this.exceptionHandlerService.captureExceptions([error], { @@ -206,7 +215,8 @@ export class CreateCompanyAndPersonService { uniqueContacts: Contact[], alreadyCreatedPeople: PersonWorkspaceEntity[], source: FieldActorSource, - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, + accountOwner: WorkspaceMemberWorkspaceEntity | null, ) { const shouldCreateOrRestorePeopleByHandleMap = new Map< string, @@ -288,7 +298,7 @@ export class CreateCompanyAndPersonService { return { domainName: companyDomainName, createdBySource: source, - createdByWorkspaceMember: connectedAccount.accountOwner, + createdByWorkspaceMember: accountOwner, createdByContext: { provider: connectedAccount.provider, }, diff --git a/packages/twenty-server/src/modules/contact-creation-manager/utils/filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts b/packages/twenty-server/src/modules/contact-creation-manager/utils/filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts index 58ab555016..40f6e2769c 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/utils/filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/utils/filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts @@ -1,6 +1,6 @@ import { isDefined } from 'twenty-shared/utils'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { type Contact } from 'src/modules/contact-creation-manager/types/contact.type'; import { getDomainNameFromHandle } from 'src/modules/contact-creation-manager/utils/get-domain-name-from-handle.util'; import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @@ -8,7 +8,7 @@ import { isWorkDomain } from 'src/utils/is-work-email'; export function filterOutContactsThatBelongToSelfOrWorkspaceMembers( contacts: Contact[], - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, workspaceMembers: WorkspaceMemberWorkspaceEntity[], ): Contact[] { if (!isDefined(connectedAccount.handle)) { @@ -20,7 +20,7 @@ export function filterOutContactsThatBelongToSelfOrWorkspaceMembers( const allHandles = [ connectedAccount.handle.toLowerCase(), - ...(connectedAccount.handleAliases?.split(',') || []).map((handle) => + ...(connectedAccount.handleAliases || []).map((handle) => handle.toLowerCase(), ), ]; diff --git a/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts b/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts index 3b62cd1244..e17c84f86f 100644 --- a/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts +++ b/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts @@ -1,31 +1,24 @@ import { Scope } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { type ObjectRecordCreateEvent } from 'twenty-shared/database-events'; import { MessageParticipantRole } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { - And, - Any, - type FindManyOptions, - ILike, - In, - IsNull, - Not, - Or, -} from 'typeorm'; - -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { And, Any, ILike, In, Not, Or, Repository } from 'typeorm'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { type WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; import { type BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity'; import { type MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity'; import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service'; +import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; export type BlocklistItemDeleteMessagesJobData = WorkspaceEventBatch< ObjectRecordCreateEvent @@ -39,7 +32,12 @@ export class BlocklistItemDeleteMessagesJob { constructor( private readonly threadCleanerService: MessagingMessageCleanerService, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, ) {} @Process(BlocklistItemDeleteMessagesJob.name) @@ -90,6 +88,12 @@ export class BlocklistItemDeleteMessagesJob { 'messageChannelMessageAssociation', ); + const workspaceMemberRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workspaceMember', + ); + for (const workspaceMemberId of handlesToDeleteByWorkspaceMemberIdMap.keys()) { const handles = handlesToDeleteByWorkspaceMemberIdMap.get(workspaceMemberId); @@ -103,23 +107,46 @@ export class BlocklistItemDeleteMessagesJob { MessageParticipantRole.TO, ] as const; - const messageChannels = - await this.messageChannelDataAccessService.findMany(workspaceId, { - select: { - id: true, - handle: true, - connectedAccount: { - handleAliases: true, - }, + const workspaceMember = await workspaceMemberRepository.findOne({ + where: { id: workspaceMemberId }, + }); + + if (!workspaceMember) { + continue; + } + + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { userId: workspaceMember.userId, workspaceId }, + }); + + if (!userWorkspace) { + continue; + } + + const connectedAccounts = await this.connectedAccountRepository.find({ + where: { userWorkspaceId: userWorkspace.id, workspaceId }, + }); + + const connectedAccountIds = connectedAccounts.map((ca) => ca.id); + + if (connectedAccountIds.length === 0) { + continue; + } + + const messageChannels = await this.messageChannelRepository.find({ + select: { + id: true, + handle: true, + connectedAccount: { + handleAliases: true, }, - where: { - connectedAccount: { - accountOwnerId: workspaceMemberId, - deletedAt: IsNull(), - }, - }, - relations: ['connectedAccount'], - } as FindManyOptions); + }, + where: { + connectedAccountId: In(connectedAccountIds), + workspaceId, + }, + relations: { connectedAccount: true }, + }); for (const messageChannel of messageChannels) { const messageChannelHandles = [messageChannel.handle]; diff --git a/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-reimport-messages.job.ts b/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-reimport-messages.job.ts index 2033adf94a..1ebdf8f0e6 100644 --- a/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-reimport-messages.job.ts +++ b/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-reimport-messages.job.ts @@ -1,18 +1,22 @@ import { Scope } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { Not } from 'typeorm'; +import { In, Not, Repository } from 'typeorm'; import { type ObjectRecordDeleteEvent } from 'twenty-shared/database-events'; +import { MessageChannelSyncStage } from 'twenty-shared/types'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { type WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; import { type BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; -import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; export type BlocklistReimportMessagesJobData = WorkspaceEventBatch< ObjectRecordDeleteEvent @@ -25,7 +29,12 @@ export type BlocklistReimportMessagesJobData = WorkspaceEventBatch< export class BlocklistReimportMessagesJob { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, private readonly messagingChannelSyncStatusService: MessageChannelSyncStatusService, ) {} @@ -36,19 +45,49 @@ export class BlocklistReimportMessagesJob { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { + const workspaceMemberRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workspaceMember', + ); + for (const eventPayload of data.events) { const workspaceMemberId = eventPayload.properties.before.workspaceMemberId; - const messageChannels = await this.messageChannelDataAccessService.find( - workspaceId, - { - connectedAccount: { - accountOwnerId: workspaceMemberId, - }, + const workspaceMember = await workspaceMemberRepository.findOne({ + where: { id: workspaceMemberId }, + }); + + if (!workspaceMember) { + continue; + } + + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { userId: workspaceMember.userId, workspaceId }, + }); + + if (!userWorkspace) { + continue; + } + + const connectedAccounts = await this.connectedAccountRepository.find({ + where: { userWorkspaceId: userWorkspace.id, workspaceId }, + }); + + const connectedAccountIds = connectedAccounts.map((ca) => ca.id); + + if (connectedAccountIds.length === 0) { + continue; + } + + const messageChannels = await this.messageChannelRepository.find({ + where: { + connectedAccountId: In(connectedAccountIds), syncStage: Not(MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING), + workspaceId, }, - ); + }); await this.messagingChannelSyncStatusService.resetAndMarkAsMessagesListFetchPending( messageChannels.map((messageChannel) => messageChannel.id), diff --git a/packages/twenty-server/src/modules/messaging/blocklist-manager/messaging-blocklist-manager.module.ts b/packages/twenty-server/src/modules/messaging/blocklist-manager/messaging-blocklist-manager.module.ts index 54ffeadfbe..3c034f016c 100644 --- a/packages/twenty-server/src/modules/messaging/blocklist-manager/messaging-blocklist-manager.module.ts +++ b/packages/twenty-server/src/modules/messaging/blocklist-manager/messaging-blocklist-manager.module.ts @@ -1,6 +1,9 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; -import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { BlocklistItemDeleteMessagesJob } from 'src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job'; import { BlocklistReimportMessagesJob } from 'src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-reimport-messages.job'; import { MessagingBlocklistListener } from 'src/modules/messaging/blocklist-manager/listeners/messaging-blocklist.listener'; @@ -11,7 +14,11 @@ import { MessagingMessageCleanerModule } from 'src/modules/messaging/message-cle imports: [ MessagingCommonModule, MessagingMessageCleanerModule, - MessageChannelDataAccessModule, + TypeOrmModule.forFeature([ + MessageChannelEntity, + ConnectedAccountEntity, + UserWorkspaceEntity, + ]), ], providers: [ MessagingBlocklistListener, diff --git a/packages/twenty-server/src/modules/messaging/common/messaging-common.module.ts b/packages/twenty-server/src/modules/messaging/common/messaging-common.module.ts index 5c131af353..b24207e2f3 100644 --- a/packages/twenty-server/src/modules/messaging/common/messaging-common.module.ts +++ b/packages/twenty-server/src/modules/messaging/common/messaging-common.module.ts @@ -1,11 +1,11 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module'; -import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module'; -import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module'; -import { MessageFolderDataAccessModule } from 'src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; @@ -13,11 +13,13 @@ import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/se @Module({ imports: [ WorkspaceDataSourceModule, - TypeOrmModule.forFeature([FeatureFlagEntity]), + TypeOrmModule.forFeature([ + MessageChannelEntity, + MessageFolderEntity, + ConnectedAccountEntity, + UserWorkspaceEntity, + ]), ConnectedAccountModule, - ConnectedAccountDataAccessModule, - MessageChannelDataAccessModule, - MessageFolderDataAccessModule, MetricsModule, ], providers: [MessageChannelSyncStatusService], diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts index 86fdef25b2..ac718c9b47 100644 --- a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.spec.ts @@ -1,10 +1,13 @@ import { Test, type TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; +import { MessageChannelVisibility } from 'twenty-shared/types'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { type MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity'; import { ApplyMessagesVisibilityRestrictionsService } from './apply-messages-visibility-restrictions.service'; @@ -39,7 +42,15 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { findOneByOrFail: jest.fn(), }; - const mockConnectedAccountDataAccessService = { + const mockConnectedAccountRepository = { + find: jest.fn(), + }; + + const mockUserWorkspaceRepository = { + findOne: jest.fn(), + }; + + const mockMessageChannelRepository = { find: jest.fn(), }; @@ -66,8 +77,16 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { useValue: mockGlobalWorkspaceOrmManager, }, { - provide: ConnectedAccountDataAccessService, - useValue: mockConnectedAccountDataAccessService, + provide: getRepositoryToken(ConnectedAccountEntity), + useValue: mockConnectedAccountRepository, + }, + { + provide: getRepositoryToken(UserWorkspaceEntity), + useValue: mockUserWorkspaceRepository, + }, + { + provide: getRepositoryToken(MessageChannelEntity), + useValue: mockMessageChannelRepository, }, ], }).compile(); @@ -87,10 +106,14 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([ { messageId: 'messageId', - messageChannel: { - id: 'messageChannelId', - visibility: MessageChannelVisibility.SHARE_EVERYTHING, - }, + messageChannelId: 'messageChannelId', + }, + ]); + + mockMessageChannelRepository.find.mockResolvedValue([ + { + id: 'messageChannelId', + visibility: MessageChannelVisibility.SHARE_EVERYTHING, }, ]); @@ -107,7 +130,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { item.subject === 'Test Subject' && item.text === 'Test Message', ), ).toBe(true); - expect(mockConnectedAccountDataAccessService.find).not.toHaveBeenCalled(); + expect(mockConnectedAccountRepository.find).not.toHaveBeenCalled(); }); it('should return message without obfuscated subject and with obfuscated text if the visibility is SUBJECT', async () => { @@ -118,17 +141,27 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([ { messageId: 'messageId', - messageChannel: { - id: 'messageChannelId', - visibility: MessageChannelVisibility.SUBJECT, - }, + messageChannelId: 'messageChannelId', }, ]); - mockConnectedAccountDataAccessService.find.mockResolvedValue([]); + mockMessageChannelRepository.find.mockResolvedValue([ + { + id: 'messageChannelId', + visibility: MessageChannelVisibility.SUBJECT, + }, + ]); + + mockConnectedAccountRepository.find.mockResolvedValue([]); mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({ id: 'workspace-member-id', + userId: 'user-id', + }); + + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', + userId: 'user-id', }); const result = await service.applyMessagesVisibilityRestrictions( @@ -153,17 +186,27 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([ { messageId: 'messageId', - messageChannel: { - id: 'messageChannelId', - visibility: MessageChannelVisibility.METADATA, - }, + messageChannelId: 'messageChannelId', }, ]); - mockConnectedAccountDataAccessService.find.mockResolvedValue([]); + mockMessageChannelRepository.find.mockResolvedValue([ + { + id: 'messageChannelId', + visibility: MessageChannelVisibility.METADATA, + }, + ]); + + mockConnectedAccountRepository.find.mockResolvedValue([]); mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({ id: 'workspace-member-id', + userId: 'user-id', + }); + + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', + userId: 'user-id', }); const result = await service.applyMessagesVisibilityRestrictions( @@ -189,18 +232,28 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([ { messageId: 'messageId', - messageChannel: { - id: 'messageChannelId', - visibility: MessageChannelVisibility.METADATA, - }, + messageChannelId: 'messageChannelId', + }, + ]); + + mockMessageChannelRepository.find.mockResolvedValue([ + { + id: 'messageChannelId', + visibility: MessageChannelVisibility.METADATA, }, ]); mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({ id: 'workspace-member-account-owner-id', + userId: 'user-id', }); - mockConnectedAccountDataAccessService.find.mockResolvedValue([{ id: '1' }]); + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', + userId: 'user-id', + }); + + mockConnectedAccountRepository.find.mockResolvedValue([{ id: '1' }]); const result = await service.applyMessagesVisibilityRestrictions( messages, @@ -225,17 +278,27 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([ { messageId: 'messageId', - messageChannel: { - id: 'messageChannelId', - }, + messageChannelId: 'messageChannelId', + }, + ]); + + mockMessageChannelRepository.find.mockResolvedValue([ + { + id: 'messageChannelId', }, ]); mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({ id: 'workspace-member-not-account-owner-id', + userId: 'user-id', }); - mockConnectedAccountDataAccessService.find.mockResolvedValue([]); + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', + userId: 'user-id', + }); + + mockConnectedAccountRepository.find.mockResolvedValue([]); const result = await service.applyMessagesVisibilityRestrictions( messages, @@ -254,34 +317,28 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { ]; mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([ - { - messageId: '1', - messageChannel: { - id: '1', - visibility: MessageChannelVisibility.SHARE_EVERYTHING, - }, - }, - { - messageId: '2', - messageChannel: { - id: '2', - visibility: MessageChannelVisibility.SUBJECT, - }, - }, - { - messageId: '3', - messageChannel: { - id: '3', - visibility: MessageChannelVisibility.METADATA, - }, - }, + { messageId: '1', messageChannelId: '1' }, + { messageId: '2', messageChannelId: '2' }, + { messageId: '3', messageChannelId: '3' }, + ]); + + mockMessageChannelRepository.find.mockResolvedValue([ + { id: '1', visibility: MessageChannelVisibility.SHARE_EVERYTHING }, + { id: '2', visibility: MessageChannelVisibility.SUBJECT }, + { id: '3', visibility: MessageChannelVisibility.METADATA }, ]); mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({ id: 'workspace-member-id', + userId: 'user-id', }); - mockConnectedAccountDataAccessService.find + mockUserWorkspaceRepository.findOne.mockResolvedValue({ + id: 'user-workspace-id', + userId: 'user-id', + }); + + mockConnectedAccountRepository.find .mockResolvedValueOnce([]) // request for message 3 .mockResolvedValueOnce([]); // request for message 2 @@ -313,34 +370,22 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => { ]; mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([ - { - messageId: '1', - messageChannel: { - id: '1', - visibility: MessageChannelVisibility.SHARE_EVERYTHING, - }, - }, - { - messageId: '2', - messageChannel: { - id: '2', - visibility: MessageChannelVisibility.SUBJECT, - }, - }, - { - messageId: '3', - messageChannel: { - id: '3', - visibility: MessageChannelVisibility.METADATA, - }, - }, + { messageId: '1', messageChannelId: '1' }, + { messageId: '2', messageChannelId: '2' }, + { messageId: '3', messageChannelId: '3' }, + ]); + + mockMessageChannelRepository.find.mockResolvedValue([ + { id: '1', visibility: MessageChannelVisibility.SHARE_EVERYTHING }, + { id: '2', visibility: MessageChannelVisibility.SUBJECT }, + { id: '3', visibility: MessageChannelVisibility.METADATA }, ]); mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({ id: 'workspace-member-id', }); - mockConnectedAccountDataAccessService.find + mockConnectedAccountRepository.find .mockResolvedValueOnce([]) // request for message 3 .mockResolvedValueOnce([]); // request for message 2 diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.ts index b20ff2017b..63d8c5f2ee 100644 --- a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.ts +++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service.ts @@ -1,16 +1,19 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import groupBy from 'lodash.groupby'; import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants'; +import { MessageChannelVisibility } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { In } from 'typeorm'; +import { In, Repository } from 'typeorm'; import { NotFoundError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { type MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity'; -import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { type MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @@ -18,7 +21,12 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta export class ApplyMessagesVisibilityRestrictionsService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, ) {} public async applyMessagesVisibilityRestrictions( @@ -41,9 +49,28 @@ export class ApplyMessagesVisibilityRestrictionsService { where: { messageId: In(messages.map((message) => message.id)), }, - relations: ['messageChannel'], }); + const messageChannelIds = [ + ...new Set( + messageChannelMessagesAssociations.map((a) => a.messageChannelId), + ), + ]; + + const messageChannelsFromCore = + messageChannelIds.length > 0 + ? await this.messageChannelRepository.find({ + where: { + id: In(messageChannelIds), + workspaceId, + }, + }) + : []; + + const messageChannelMap = new Map( + messageChannelsFromCore.map((ch) => [ch.id, ch]), + ); + const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( workspaceId, @@ -51,17 +78,15 @@ export class ApplyMessagesVisibilityRestrictionsService { ); for (let i = messages.length - 1; i >= 0; i--) { - const messageChannelMessageAssociations = - messageChannelMessagesAssociations.filter( - (association) => association.messageId === messages[i].id, - ); + const associations = messageChannelMessagesAssociations.filter( + (association) => association.messageId === messages[i].id, + ); - const messageChannels = messageChannelMessageAssociations - .map((association) => association.messageChannel) - .filter( - (channel): channel is NonNullable => - channel !== null, - ); + const messageChannels = associations + .map((association) => + messageChannelMap.get(association.messageChannelId), + ) + .filter(isDefined); if (messageChannels.length === 0) { throw new NotFoundError('Associated message channels not found'); @@ -86,16 +111,26 @@ export class ApplyMessagesVisibilityRestrictionsService { userId, }); - const connectedAccounts = - await this.connectedAccountDataAccessService.find(workspaceId, { - messageChannels: { - id: In(messageChannels.map((channel) => channel.id)), - }, - accountOwnerId: workspaceMember.id, - }); + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { userId: workspaceMember.userId, workspaceId }, + }); - if (connectedAccounts.length > 0) { - continue; + if (userWorkspace) { + const connectedAccounts = + await this.connectedAccountRepository.find({ + where: { + userWorkspaceId: userWorkspace.id, + workspaceId, + messageChannels: { + id: In(messageChannels.map((channel) => channel.id)), + }, + }, + relations: { messageChannels: true }, + }); + + if (connectedAccounts.length > 0) { + continue; + } } } diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-channel-update-one.pre-query.hook.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-channel-update-one.pre-query.hook.ts deleted file mode 100644 index 912638ec42..0000000000 --- a/packages/twenty-server/src/modules/messaging/common/query-hooks/message/message-channel-update-one.pre-query.hook.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { Logger } from '@nestjs/common'; - -import { msg } from '@lingui/core/macro'; -import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils'; -import { Not } from 'typeorm'; - -import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; -import { type UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; - -import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; -import { - WorkspaceQueryRunnerException, - WorkspaceQueryRunnerExceptionCode, -} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception'; -import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; -import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; -import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service'; -import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; -import { - MessageChannelPendingGroupEmailsAction, - MessageChannelSyncStage, - type MessageChannelWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; -import { MessagingProcessGroupEmailActionsService } from 'src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service'; - -const ONGOING_SYNC_STAGES = [ - MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING, -]; - -@WorkspaceQueryHook(`messageChannel.updateOne`) -export class MessageChannelUpdateOnePreQueryHook - implements WorkspacePreQueryHookInstance -{ - private readonly logger = new Logger( - MessageChannelUpdateOnePreQueryHook.name, - ); - - constructor( - private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, - private readonly messageFolderDataAccessService: MessageFolderDataAccessService, - private readonly messagingProcessGroupEmailActionsService: MessagingProcessGroupEmailActionsService, - ) {} - - async execute( - authContext: WorkspaceAuthContext, - _objectName: string, - payload: UpdateOneResolverArgs, - ): Promise> { - const workspace = authContext.workspace; - - assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError); - - const systemAuthContext = buildSystemAuthContext(workspace.id); - - return this.globalWorkspaceOrmManager.executeInWorkspaceContext( - async () => { - const messageChannel = - await this.messageChannelDataAccessService.findOne(workspace.id, { - where: { id: payload.id }, - }); - - if (!isDefined(messageChannel)) { - throw new WorkspaceQueryRunnerException( - 'Message channel not found', - WorkspaceQueryRunnerExceptionCode.DATA_NOT_FOUND, - { - userFriendlyMessage: msg`Message channel not found`, - }, - ); - } - - const messageChannelWorkspace = - messageChannel as unknown as MessageChannelWorkspaceEntity; - - const isSyncOngoing = ONGOING_SYNC_STAGES.includes( - messageChannelWorkspace.syncStage, - ); - - const messageFoldersWithPendingAction = - await this.messageFolderDataAccessService.find(workspace.id, { - messageChannelId: messageChannel.id, - pendingSyncAction: Not(MessageFolderPendingSyncAction.NONE), - }); - - const messageFoldersWithPendingActionCount = - messageFoldersWithPendingAction.length; - - const hasPendingFolderActions = - messageFoldersWithPendingActionCount > 0; - - const hasPendingGroupEmailsAction = - messageChannelWorkspace.pendingGroupEmailsAction !== - MessageChannelPendingGroupEmailsAction.NONE; - - if ( - isSyncOngoing && - (hasPendingFolderActions || hasPendingGroupEmailsAction) - ) { - throw new WorkspaceQueryRunnerException( - 'Cannot update message channel while sync is ongoing with pending actions', - WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT, - { - userFriendlyMessage: msg`Cannot update message channel while sync is ongoing. Please wait for the sync to complete.`, - }, - ); - } - - const hasCompletedConfiguration = - messageChannelWorkspace.syncStage !== - MessageChannelSyncStage.PENDING_CONFIGURATION; - - if (!hasCompletedConfiguration) { - this.logger.log( - `MessageChannelId: ${messageChannelWorkspace.id} - Skipping pending action for message channel in PENDING_CONFIGURATION state`, - ); - - return payload; - } - - const excludeGroupEmailsChanged = - isDefined(payload.data.excludeGroupEmails) && - payload.data.excludeGroupEmails !== - messageChannelWorkspace.excludeGroupEmails; - - if (excludeGroupEmailsChanged) { - await this.messagingProcessGroupEmailActionsService.markMessageChannelAsPendingGroupEmailsAction( - messageChannelWorkspace, - workspace.id, - payload.data.excludeGroupEmails - ? MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_DELETION - : MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_IMPORT, - ); - } - - return payload; - }, - systemAuthContext, - ); - } -} diff --git a/packages/twenty-server/src/modules/messaging/common/query-hooks/messaging-query-hook.module.ts b/packages/twenty-server/src/modules/messaging/common/query-hooks/messaging-query-hook.module.ts index 3372d6b8d9..47bc6b6aac 100644 --- a/packages/twenty-server/src/modules/messaging/common/query-hooks/messaging-query-hook.module.ts +++ b/packages/twenty-server/src/modules/messaging/common/query-hooks/messaging-query-hook.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; -import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module'; -import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module'; -import { MessageFolderDataAccessModule } from 'src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { ApplyMessagesVisibilityRestrictionsService } from 'src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service'; -import { MessageChannelUpdateOnePreQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-channel-update-one.pre-query.hook'; import { MessageFindManyPostQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-find-many.post-query.hook'; import { MessageFindOnePostQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-find-one.post-query.hook'; import { MessagingImportManagerModule } from 'src/modules/messaging/message-import-manager/messaging-import-manager.module'; @@ -12,15 +13,17 @@ import { MessagingImportManagerModule } from 'src/modules/messaging/message-impo @Module({ imports: [ MessagingImportManagerModule, - ConnectedAccountDataAccessModule, - MessageChannelDataAccessModule, - MessageFolderDataAccessModule, + TypeOrmModule.forFeature([ + ConnectedAccountEntity, + MessageChannelEntity, + MessageFolderEntity, + UserWorkspaceEntity, + ]), ], providers: [ ApplyMessagesVisibilityRestrictionsService, MessageFindOnePostQueryHook, MessageFindManyPostQueryHook, - MessageChannelUpdateOnePreQueryHook, ], }) export class MessagingQueryHookModule {} diff --git a/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts b/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts index 47e882149a..d1ae372c56 100644 --- a/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts +++ b/packages/twenty-server/src/modules/messaging/common/services/message-channel-sync-status.service.ts @@ -1,25 +1,27 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { Any, In } from 'typeorm'; +import { Any, In, Repository } from 'typeorm'; +import { + MessageChannelPendingGroupEmailsAction, + MessageChannelSyncStage, + MessageChannelSyncStatus, + MessageFolderPendingSyncAction, +} from 'twenty-shared/types'; import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator'; import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type'; -import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; -import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service'; import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type'; -import { - MessageChannelPendingGroupEmailsAction, - MessageChannelSyncStage, - MessageChannelSyncStatus, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @Injectable() @@ -28,9 +30,14 @@ export class MessageChannelSyncStatusService { @InjectCacheStorage(CacheStorageNamespace.ModuleMessaging) private readonly cacheStorage: CacheStorageService, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, - private readonly messageFolderDataAccessService: MessageFolderDataAccessService, - private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, + @InjectRepository(MessageFolderEntity) + private readonly messageFolderRepository: Repository, + @InjectRepository(ConnectedAccountEntity) + private readonly connectedAccountRepository: Repository, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, private readonly accountsToReconnectService: AccountsToReconnectService, private readonly metricsService: MetricsService, ) {} @@ -47,9 +54,8 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.messageChannelDataAccessService.update( - workspaceId, - { id: In(messageChannelIds) }, + await this.messageChannelRepository.update( + { id: In(messageChannelIds), workspaceId }, { syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, ...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}), @@ -70,9 +76,8 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.messageChannelDataAccessService.update( - workspaceId, - { id: In(messageChannelIds) }, + await this.messageChannelRepository.update( + { id: In(messageChannelIds), workspaceId }, { syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_PENDING, ...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}), @@ -98,9 +103,8 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.messageChannelDataAccessService.update( - workspaceId, - { id: In(messageChannelIds) }, + await this.messageChannelRepository.update( + { id: In(messageChannelIds), workspaceId }, { syncCursor: '', syncStageStartedAt: null, @@ -110,9 +114,8 @@ export class MessageChannelSyncStatusService { }, ); - await this.messageFolderDataAccessService.update( - workspaceId, - { messageChannelId: In(messageChannelIds) }, + await this.messageFolderRepository.update( + { messageChannelId: In(messageChannelIds), workspaceId }, { syncCursor: '', pendingSyncAction: MessageFolderPendingSyncAction.NONE, @@ -134,9 +137,8 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.messageChannelDataAccessService.update( - workspaceId, - { id: In(messageChannelIds) }, + await this.messageChannelRepository.update( + { id: In(messageChannelIds), workspaceId }, { syncStageStartedAt: null }, ); }, authContext); @@ -153,9 +155,8 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.messageChannelDataAccessService.update( - workspaceId, - { id: In(messageChannelIds) }, + await this.messageChannelRepository.update( + { id: In(messageChannelIds), workspaceId }, { syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED, syncStatus: MessageChannelSyncStatus.ONGOING, @@ -176,9 +177,8 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.messageChannelDataAccessService.update( - workspaceId, - { id: In(messageChannelIds) }, + await this.messageChannelRepository.update( + { id: In(messageChannelIds), workspaceId }, { syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING, syncStatus: MessageChannelSyncStatus.ONGOING, @@ -199,9 +199,8 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.messageChannelDataAccessService.update( - workspaceId, - { id: In(messageChannelIds) }, + await this.messageChannelRepository.update( + { id: In(messageChannelIds), workspaceId }, { syncStatus: MessageChannelSyncStatus.ACTIVE, syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, @@ -230,9 +229,8 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.messageChannelDataAccessService.update( - workspaceId, - { id: In(messageChannelIds) }, + await this.messageChannelRepository.update( + { id: In(messageChannelIds), workspaceId }, { syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED, }, @@ -251,9 +249,8 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.messageChannelDataAccessService.update( - workspaceId, - { id: In(messageChannelIds) }, + await this.messageChannelRepository.update( + { id: In(messageChannelIds), workspaceId }, { syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING, syncStatus: MessageChannelSyncStatus.ONGOING, @@ -277,9 +274,8 @@ export class MessageChannelSyncStatusService { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.messageChannelDataAccessService.update( - workspaceId, - { id: In(messageChannelIds) }, + await this.messageChannelRepository.update( + { id: In(messageChannelIds), workspaceId }, { syncStage: MessageChannelSyncStage.FAILED, syncStatus: syncStatus, @@ -300,18 +296,16 @@ export class MessageChannelSyncStatusService { if ( syncStatus === MessageChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS ) { - const messageChannels = await this.messageChannelDataAccessService.find( - workspaceId, - { id: In(messageChannelIds) }, - ); + const messageChannels = await this.messageChannelRepository.find({ + where: { id: In(messageChannelIds), workspaceId }, + }); const connectedAccountIds = messageChannels.map( (messageChannel) => messageChannel.connectedAccountId, ); - await this.connectedAccountDataAccessService.update( - workspaceId, - { id: Any(connectedAccountIds) }, + await this.connectedAccountRepository.update( + { id: Any(connectedAccountIds), workspaceId }, { authFailedAt: new Date(), }, @@ -333,12 +327,9 @@ export class MessageChannelSyncStatusService { return; } - const messageChannels = await this.messageChannelDataAccessService.findMany( - workspaceId, - { - where: { id: In(messageChannelIds) }, - }, - ); + const messageChannels = await this.messageChannelRepository.find({ + where: { id: In(messageChannelIds), workspaceId }, + }); const workspaceMemberRepository = await this.globalWorkspaceOrmManager.getRepository( @@ -348,17 +339,24 @@ export class MessageChannelSyncStatusService { ); for (const messageChannel of messageChannels) { - const connectedAccount = - await this.connectedAccountDataAccessService.findOne(workspaceId, { - where: { id: messageChannel.connectedAccountId }, - }); + const connectedAccount = await this.connectedAccountRepository.findOne({ + where: { id: messageChannel.connectedAccountId, workspaceId }, + }); if (!connectedAccount) { continue; } + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { id: connectedAccount.userWorkspaceId }, + }); + + if (!userWorkspace) { + continue; + } + const workspaceMember = await workspaceMemberRepository.findOne({ - where: { id: connectedAccount.accountOwnerId }, + where: { userId: userWorkspace.userId }, }); if (!workspaceMember) { diff --git a/packages/twenty-server/src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity.ts b/packages/twenty-server/src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity.ts index 4fa19313fb..f47322f23c 100644 --- a/packages/twenty-server/src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity.ts +++ b/packages/twenty-server/src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity.ts @@ -5,7 +5,6 @@ import { type FieldTypeAndNameMetadata } from 'src/engine/workspace-manager/util import { type EntityRelation } from 'src/engine/workspace-manager/workspace-migration/types/entity-relation.interface'; import { type MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum'; import { type MessageChannelMessageAssociationMessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association-message-folder.workspace-entity'; -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { type MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity'; const MESSAGE_EXTERNAL_ID_FIELD_NAME = 'messageExternalId'; @@ -17,7 +16,6 @@ export class MessageChannelMessageAssociationWorkspaceEntity extends BaseWorkspa messageExternalId: string | null; messageThreadExternalId: string | null; direction: MessageDirection; - messageChannel: EntityRelation | null; messageChannelId: string; message: EntityRelation | null; messageId: string; diff --git a/packages/twenty-server/src/modules/messaging/common/standard-objects/message-folder.workspace-entity.ts b/packages/twenty-server/src/modules/messaging/common/standard-objects/message-folder.workspace-entity.ts index d49af4721b..0179e06a75 100644 --- a/packages/twenty-server/src/modules/messaging/common/standard-objects/message-folder.workspace-entity.ts +++ b/packages/twenty-server/src/modules/messaging/common/standard-objects/message-folder.workspace-entity.ts @@ -9,7 +9,6 @@ import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity import { type FieldTypeAndNameMetadata } from 'src/engine/workspace-manager/utils/get-ts-vector-column-expression.util'; import { type EntityRelation } from 'src/engine/workspace-manager/workspace-migration/types/entity-relation.interface'; import { type MessageChannelMessageAssociationMessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association-message-folder.workspace-entity'; -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; export { MessageFolderPendingSyncAction }; @@ -25,7 +24,6 @@ export const SEARCH_FIELDS_FOR_MESSAGE_FOLDER: FieldTypeAndNameMetadata[] = [ export class MessageFolderWorkspaceEntity extends BaseWorkspaceEntity { name: string | null; - messageChannel: EntityRelation; syncCursor: string | null; isSentFolder: boolean; isSynced: boolean; diff --git a/packages/twenty-server/src/modules/messaging/message-cleaner/commands/messaging-reset-channel.command.ts b/packages/twenty-server/src/modules/messaging/message-cleaner/commands/messaging-reset-channel.command.ts index 298b06285c..5263fbbf1d 100644 --- a/packages/twenty-server/src/modules/messaging/message-cleaner/commands/messaging-reset-channel.command.ts +++ b/packages/twenty-server/src/modules/messaging/message-cleaner/commands/messaging-reset-channel.command.ts @@ -1,9 +1,11 @@ import { Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { Command, CommandRunner, Option } from 'nest-commander'; import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; @@ -24,7 +26,8 @@ export class MessagingResetChannelCommand extends CommandRunner { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, private readonly messagingChannelSyncStatusService: MessageChannelSyncStatusService, private readonly messagingMessageCleanerService: MessagingMessageCleanerService, ) { @@ -44,10 +47,12 @@ export class MessagingResetChannelCommand extends CommandRunner { `No message channel ID provided, resetting all message channels in workspace ${workspaceId}`, ); - const messageChannels = await this.messageChannelDataAccessService.find( - workspaceId, - isDefined(messageChannelId) ? { id: messageChannelId } : {}, - ); + const messageChannels = await this.messageChannelRepository.find({ + where: { + ...(isDefined(messageChannelId) ? { id: messageChannelId } : {}), + workspaceId, + }, + }); if (messageChannels.length === 0) { this.logger.log( diff --git a/packages/twenty-server/src/modules/messaging/message-cleaner/jobs/messaging-message-channel-deletion-cleanup.job.ts b/packages/twenty-server/src/modules/messaging/message-cleaner/jobs/messaging-message-channel-deletion-cleanup.job.ts index dde8c1bd14..1e9f3b7be2 100644 --- a/packages/twenty-server/src/modules/messaging/message-cleaner/jobs/messaging-message-channel-deletion-cleanup.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-cleaner/jobs/messaging-message-channel-deletion-cleanup.job.ts @@ -1,8 +1,5 @@ import { Logger, Scope } from '@nestjs/common'; -import { FeatureFlagKey } from 'twenty-shared/types'; - -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; @@ -24,22 +21,12 @@ export class MessagingMessageChannelDeletionCleanupJob { constructor( private readonly messageCleanerService: MessagingMessageCleanerService, - private readonly featureFlagService: FeatureFlagService, ) {} @Process(MessagingMessageChannelDeletionCleanupJob.name) async handle( data: MessagingMessageChannelDeletionCleanupJobData, ): Promise { - const isMigrated = await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - data.workspaceId, - ); - - if (!isMigrated) { - return; - } - this.logger.debug( `WorkspaceId: ${data.workspaceId} Cleaning up message channel message associations for channel ${data.messageChannelId}`, ); diff --git a/packages/twenty-server/src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-connected-account.listener.ts b/packages/twenty-server/src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-connected-account.listener.ts index 03a5a5c976..7267d9b72e 100644 --- a/packages/twenty-server/src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-connected-account.listener.ts +++ b/packages/twenty-server/src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-connected-account.listener.ts @@ -6,7 +6,7 @@ import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decora import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { MessagingConnectedAccountDeletionCleanupJob, type MessagingConnectedAccountDeletionCleanupJobData, @@ -24,7 +24,7 @@ export class MessagingMessageCleanerConnectedAccountListener { @OnDatabaseBatchEvent('connectedAccount', DatabaseEventAction.DESTROYED) async handleDestroyedEvent( payload: WorkspaceEventBatch< - ObjectRecordDeleteEvent + ObjectRecordDeleteEvent >, ) { await Promise.all( diff --git a/packages/twenty-server/src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-message-channel.listener.ts b/packages/twenty-server/src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-message-channel.listener.ts index 77261e71ae..534326a9cf 100644 --- a/packages/twenty-server/src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-message-channel.listener.ts +++ b/packages/twenty-server/src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-message-channel.listener.ts @@ -6,7 +6,7 @@ import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decora import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { MessagingMessageChannelDeletionCleanupJob, type MessagingMessageChannelDeletionCleanupJobData, @@ -23,9 +23,7 @@ export class MessagingMessageCleanerMessageChannelListener { @OnDatabaseBatchEvent('messageChannel', DatabaseEventAction.DESTROYED) async handleDestroyedEvent( - payload: WorkspaceEventBatch< - ObjectRecordDeleteEvent - >, + payload: WorkspaceEventBatch>, ) { await Promise.all( payload.events.map((eventPayload) => diff --git a/packages/twenty-server/src/modules/messaging/message-cleaner/messaging-message-cleaner.module.ts b/packages/twenty-server/src/modules/messaging/message-cleaner/messaging-message-cleaner.module.ts index 638a6d5b5d..2c6505bb85 100644 --- a/packages/twenty-server/src/modules/messaging/message-cleaner/messaging-message-cleaner.module.ts +++ b/packages/twenty-server/src/modules/messaging/message-cleaner/messaging-message-cleaner.module.ts @@ -5,7 +5,7 @@ import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/w import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; -import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module'; import { MessagingMessageCleanerRemoveOrphansCommand } from 'src/modules/messaging/message-cleaner/commands/messaging-message-clearner-remove-orphans.command'; import { MessagingResetChannelCommand } from 'src/modules/messaging/message-cleaner/commands/messaging-reset-channel.command'; @@ -13,6 +13,7 @@ import { MessagingConnectedAccountDeletionCleanupJob } from 'src/modules/messagi import { MessagingMessageChannelDeletionCleanupJob } from 'src/modules/messaging/message-cleaner/jobs/messaging-message-channel-deletion-cleanup.job'; import { MessagingMessageCleanerConnectedAccountListener } from 'src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-connected-account.listener'; import { MessagingMessageCleanerMessageChannelListener } from 'src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-message-channel.listener'; + import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service'; @Module({ @@ -21,7 +22,7 @@ import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cl DataSourceModule, FeatureFlagModule, MessagingCommonModule, - MessageChannelDataAccessModule, + TypeOrmModule.forFeature([MessageChannelEntity]), WorkspaceIteratorModule, ], providers: [ diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-get-all-folders.service.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-get-all-folders.service.ts index faf1ec0d0b..e025095d10 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-get-all-folders.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-get-all-folders.service.ts @@ -7,9 +7,9 @@ import { MessageFolderDriver, } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type 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 { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GmailFoldersErrorHandlerService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-folders-error-handler.service'; import { extractGmailFolderName } from 'src/modules/messaging/message-folder-manager/drivers/gmail/utils/extract-gmail-folder-name.util'; import { getGmailFolderParentId } from 'src/modules/messaging/message-folder-manager/drivers/gmail/utils/get-gmail-folder-parent-id.util'; @@ -27,13 +27,10 @@ export class GmailGetAllFoldersService implements MessageFolderDriver { async getAllMessageFolders( connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, 'provider' | 'refreshToken' | 'accessToken' | 'id' | 'handle' >, - messageChannel: Pick< - MessageChannelWorkspaceEntity, - 'messageFolderImportPolicy' - >, + messageChannel: Pick, ): Promise { try { const oAuth2Client = diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service.ts index f2a0c1a374..4c3f1803b5 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service.ts @@ -8,8 +8,8 @@ import { MessageFolderDriver, } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface'; -import { type 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 { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { shouldCreateFolderByDefault } from 'src/modules/messaging/message-folder-manager/utils/should-create-folder-by-default.util'; import { shouldSyncFolderByDefault } from 'src/modules/messaging/message-folder-manager/utils/should-sync-folder-by-default.util'; import { ImapClientProvider } from 'src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider'; @@ -27,13 +27,10 @@ export class ImapGetAllFoldersService implements MessageFolderDriver { public async getAllMessageFolders( connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, 'id' | 'provider' | 'connectionParameters' | 'handle' >, - messageChannel: Pick< - MessageChannelWorkspaceEntity, - 'messageFolderImportPolicy' - >, + messageChannel: Pick, ): Promise { try { const client = await this.imapClientProvider.getClient(connectedAccount); @@ -62,10 +59,7 @@ export class ImapGetAllFoldersService implements MessageFolderDriver { private async filterAndMapFolders( client: ImapFlow, mailboxList: ListResponse[], - messageChannel: Pick< - MessageChannelWorkspaceEntity, - 'messageFolderImportPolicy' - >, + messageChannel: Pick, ): Promise { const folders: DiscoveredMessageFolder[] = []; const pathToExternalIdMap = new Map(); diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/microsoft/services/microsoft-get-all-folders.service.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/microsoft/services/microsoft-get-all-folders.service.ts index cd64d0ee67..a3d128a6ea 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/microsoft/services/microsoft-get-all-folders.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/microsoft/services/microsoft-get-all-folders.service.ts @@ -7,9 +7,9 @@ import { MessageFolderDriver, } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type 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 { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { shouldCreateFolderByDefault } from 'src/modules/messaging/message-folder-manager/utils/should-create-folder-by-default.util'; import { shouldSyncFolderByDefault } from 'src/modules/messaging/message-folder-manager/utils/should-sync-folder-by-default.util'; import { MicrosoftMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-message-list-fetch-error-handler.service'; @@ -37,13 +37,10 @@ export class MicrosoftGetAllFoldersService implements MessageFolderDriver { async getAllMessageFolders( connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, 'accessToken' | 'refreshToken' | 'id' | 'handle' | 'provider' >, - messageChannel: Pick< - MessageChannelWorkspaceEntity, - 'messageFolderImportPolicy' - >, + messageChannel: Pick, ): Promise { try { const microsoftClient = diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface.ts index 706d601426..17537ebd69 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface.ts @@ -1,14 +1,14 @@ -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; export type DiscoveredMessageFolder = Pick< - MessageFolderWorkspaceEntity, + MessageFolderEntity, 'name' | 'isSynced' | 'isSentFolder' | 'externalId' | 'parentFolderId' >; export type MessageFolder = Pick< - MessageFolderWorkspaceEntity, + MessageFolderEntity, | 'name' | 'isSynced' | 'isSentFolder' @@ -22,7 +22,7 @@ export type MessageFolder = Pick< export type MessageFolderDriver = { getAllMessageFolders( connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, | 'provider' | 'accessToken' | 'refreshToken' @@ -30,9 +30,6 @@ export type MessageFolderDriver = { | 'handle' | 'connectionParameters' >, - messageChannel: Pick< - MessageChannelWorkspaceEntity, - 'messageFolderImportPolicy' - >, + messageChannel: Pick, ): Promise; }; diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/messaging-folder-sync-manager.module.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/messaging-folder-sync-manager.module.ts index ba02fff116..11238af4c4 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/messaging-folder-sync-manager.module.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/messaging-folder-sync-manager.module.ts @@ -4,7 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; -import { MessageFolderDataAccessModule } from 'src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module'; +import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-client-manager/oauth2-client-manager.module'; import { GmailFoldersErrorHandlerService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-folders-error-handler.service'; @@ -21,8 +21,7 @@ import { MessagingMicrosoftDriverModule } from 'src/modules/messaging/message-im FeatureFlagModule, WorkspaceDataSourceModule, DataSourceModule, - MessageFolderDataAccessModule, - TypeOrmModule.forFeature([WorkspaceEntity]), + TypeOrmModule.forFeature([WorkspaceEntity, MessageFolderEntity]), OAuth2ClientManagerModule, MessagingGmailDriverModule, MessagingMicrosoftDriverModule, diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.spec.ts index f3145b5456..a5bbc680c3 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.spec.ts @@ -1,29 +1,27 @@ import { Test, type TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; -import { ConnectedAccountProvider } from 'twenty-shared/types'; -import { In } from 'typeorm'; - -import { type DiscoveredMessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface'; - -import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service'; -import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { + ConnectedAccountProvider, MessageChannelContactAutoCreationPolicy, MessageChannelType, MessageChannelVisibility, MessageFolderImportPolicy, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { MessageFolderPendingSyncAction, - type MessageFolderWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +} from 'twenty-shared/types'; +import { In } from 'typeorm'; + +import { type DiscoveredMessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface'; + +import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; +import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { GmailGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-get-all-folders.service'; import { ImapGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service'; import { MicrosoftGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/microsoft/services/microsoft-get-all-folders.service'; import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service'; type SyncedMessageFolder = Pick< - MessageFolderWorkspaceEntity, + MessageFolderEntity, | 'id' | 'name' | 'isSynced' @@ -111,7 +109,7 @@ describe('SyncMessageFoldersService', () => { let service: SyncMessageFoldersService; let gmailGetAllFoldersService: jest.Mocked; - let mockMessageFolderDataAccessService: { + let mockMessageFolderRepository: { delete: jest.Mock; update: jest.Mock; save: jest.Mock; @@ -119,7 +117,7 @@ describe('SyncMessageFoldersService', () => { }; let createdFolderRecords: Array< - Partial & { + Partial & { id: string; externalId: string; } @@ -128,10 +126,10 @@ describe('SyncMessageFoldersService', () => { beforeEach(async () => { createdFolderRecords = []; - mockMessageFolderDataAccessService = { + mockMessageFolderRepository = { delete: jest.fn(), update: jest.fn().mockResolvedValue(undefined), - save: jest.fn().mockImplementation(async (_workspaceId, folder) => { + save: jest.fn().mockImplementation(async (folder) => { createdFolderRecords.push({ ...folder, id: `new-folder-${createdFolderRecords.length}-${Math.random().toString(36).substring(7)}`, @@ -141,7 +139,7 @@ describe('SyncMessageFoldersService', () => { externalId: folder.externalId as string, }); }), - find: jest.fn().mockImplementation(async (_workspaceId, where) => { + find: jest.fn().mockImplementation(async ({ where }) => { if (!where?.externalId) { return []; } @@ -171,8 +169,8 @@ describe('SyncMessageFoldersService', () => { }, }, { - provide: MessageFolderDataAccessService, - useValue: mockMessageFolderDataAccessService, + provide: getRepositoryToken(MessageFolderEntity), + useValue: mockMessageFolderRepository, }, { provide: GmailGetAllFoldersService, @@ -228,18 +226,18 @@ describe('SyncMessageFoldersService', () => { workspaceId, }); - expect(mockMessageFolderDataAccessService.save).toHaveBeenCalledWith( - workspaceId, + expect(mockMessageFolderRepository.save).toHaveBeenCalledWith( expect.objectContaining({ + workspaceId, name: 'INBOX', externalId: 'inbox-ext', messageChannelId: 'channel-123', isSentFolder: false, }), ); - expect(mockMessageFolderDataAccessService.save).toHaveBeenCalledWith( - workspaceId, + expect(mockMessageFolderRepository.save).toHaveBeenCalledWith( expect.objectContaining({ + workspaceId, name: 'Sent', externalId: 'sent-ext', messageChannelId: 'channel-123', @@ -275,9 +273,9 @@ describe('SyncMessageFoldersService', () => { workspaceId, }); - expect(mockMessageFolderDataAccessService.save).toHaveBeenCalledWith( - workspaceId, + expect(mockMessageFolderRepository.save).toHaveBeenCalledWith( expect.objectContaining({ + workspaceId, name: 'Projects', parentFolderId: 'parent-folder-id', }), @@ -311,9 +309,8 @@ describe('SyncMessageFoldersService', () => { workspaceId, }); - expect(mockMessageFolderDataAccessService.update).toHaveBeenCalledWith( - workspaceId, - { id: 'folder-1', messageChannelId: 'channel-123' }, + expect(mockMessageFolderRepository.update).toHaveBeenCalledWith( + { id: 'folder-1', messageChannelId: 'channel-123', workspaceId }, expect.objectContaining({ name: 'Primary Inbox' }), ); expect(result).toContainEqual( @@ -325,11 +322,16 @@ describe('SyncMessageFoldersService', () => { }); it('should update folder when parent folder changes', async () => { + const newParentFolder = createMockExistingFolder({ + id: 'new-parent-uuid', + externalId: 'new-parent-id', + name: 'New Parent', + }); const existingFolder = createMockExistingFolder({ id: 'folder-1', externalId: 'child-ext', name: 'Projects', - parentFolderId: 'old-parent-id', + parentFolderId: 'old-parent-uuid', }); const discoveredFolders = [ createMockDiscoveredFolder({ @@ -337,9 +339,13 @@ describe('SyncMessageFoldersService', () => { name: 'Projects', parentFolderId: 'new-parent-id', }), + createMockDiscoveredFolder({ + externalId: 'new-parent-id', + name: 'New Parent', + }), ]; const messageChannel = createMockMessageChannel({ - messageFolders: [existingFolder], + messageFolders: [existingFolder, newParentFolder], }); gmailGetAllFoldersService.getAllMessageFolders.mockResolvedValue( @@ -351,11 +357,10 @@ describe('SyncMessageFoldersService', () => { workspaceId, }); - expect(mockMessageFolderDataAccessService.update).toHaveBeenCalledWith( - workspaceId, - { id: 'folder-1', messageChannelId: 'channel-123' }, + expect(mockMessageFolderRepository.update).toHaveBeenCalledWith( + { id: 'folder-1', messageChannelId: 'channel-123', workspaceId }, expect.objectContaining({ - parentFolderId: 'new-parent-id', + parentFolderId: 'new-parent-uuid', }), ); }); @@ -389,9 +394,7 @@ describe('SyncMessageFoldersService', () => { workspaceId, }); - expect( - mockMessageFolderDataAccessService.update, - ).not.toHaveBeenCalled(); + expect(mockMessageFolderRepository.update).not.toHaveBeenCalled(); }); }); @@ -428,9 +431,8 @@ describe('SyncMessageFoldersService', () => { workspaceId, }); - expect(mockMessageFolderDataAccessService.update).toHaveBeenCalledWith( - workspaceId, - { id: In(['folder-2']) }, + expect(mockMessageFolderRepository.update).toHaveBeenCalledWith( + { id: In(['folder-2']), workspaceId }, expect.objectContaining({ pendingSyncAction: 'FOLDER_DELETION', }), @@ -491,21 +493,25 @@ describe('SyncMessageFoldersService', () => { workspaceId, }); - expect(mockMessageFolderDataAccessService.update).toHaveBeenCalledWith( - workspaceId, - { id: In(['folder-to-delete']) }, + expect(mockMessageFolderRepository.update).toHaveBeenCalledWith( + { id: In(['folder-to-delete']), workspaceId }, expect.objectContaining({ pendingSyncAction: 'FOLDER_DELETION', }), ); - expect(mockMessageFolderDataAccessService.update).toHaveBeenCalledWith( - workspaceId, - { id: 'folder-to-update', messageChannelId: 'channel-123' }, + expect(mockMessageFolderRepository.update).toHaveBeenCalledWith( + { + id: 'folder-to-update', + messageChannelId: 'channel-123', + workspaceId, + }, expect.objectContaining({ name: 'New Name' }), ); - expect(mockMessageFolderDataAccessService.save).toHaveBeenCalledWith( - workspaceId, - expect.objectContaining({ externalId: 'new-ext' }), + expect(mockMessageFolderRepository.save).toHaveBeenCalledWith( + expect.objectContaining({ + workspaceId, + externalId: 'new-ext', + }), ); expect(result).toHaveLength(4); expect(result).toContainEqual( diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts index 6d7589351f..16a050552a 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts @@ -1,20 +1,23 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { ConnectedAccountProvider } from 'twenty-shared/types'; +import { + ConnectedAccountProvider, + MessageFolderPendingSyncAction, +} from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { In } from 'typeorm'; +import { In, Repository } from 'typeorm'; import { DiscoveredMessageFolder, MessageFolder, } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface'; -import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service'; +import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; -import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GmailGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-get-all-folders.service'; import { ImapGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service'; import { MicrosoftGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/microsoft/services/microsoft-get-all-folders.service'; @@ -27,7 +30,8 @@ import { computeUpdatedFolders } from 'src/modules/messaging/message-folder-mana export class SyncMessageFoldersService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageFolderDataAccessService: MessageFolderDataAccessService, + @InjectRepository(MessageFolderEntity) + private readonly messageFolderRepository: Repository, private readonly gmailGetAllFoldersService: GmailGetAllFoldersService, private readonly microsoftGetAllFoldersService: MicrosoftGetAllFoldersService, private readonly imapGetAllFoldersService: ImapGetAllFoldersService, @@ -38,11 +42,11 @@ export class SyncMessageFoldersService { workspaceId, }: { messageChannel: Pick< - MessageChannelWorkspaceEntity, + MessageChannelEntity, 'id' | 'messageFolderImportPolicy' > & { connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, | 'provider' | 'accessToken' | 'refreshToken' @@ -72,7 +76,7 @@ export class SyncMessageFoldersService { async discoverAllFolders( connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, | 'accessToken' | 'refreshToken' | 'id' @@ -80,10 +84,7 @@ export class SyncMessageFoldersService { | 'provider' | 'connectionParameters' >, - messageChannel: Pick< - MessageChannelWorkspaceEntity, - 'messageFolderImportPolicy' - >, + messageChannel: Pick, ): Promise { switch (connectedAccount.provider) { case ConnectedAccountProvider.GOOGLE: @@ -114,6 +115,12 @@ export class SyncMessageFoldersService { messageChannelId: string, workspaceId: string, ): Promise { + const externalIdToUuidMap = new Map( + existingFolders + .filter((folder) => isDefined(folder.externalId)) + .map((folder) => [folder.externalId as string, folder.id]), + ); + const foldersToCreate = computeFoldersToCreate({ discoveredFolders, existingFolders, @@ -123,6 +130,7 @@ export class SyncMessageFoldersService { const foldersToUpdate = computeFoldersToUpdate({ discoveredFolders, existingFolders, + externalIdToUuidMap, }); const folderIdsToDelete = computeFolderIdsToDelete({ @@ -132,16 +140,11 @@ export class SyncMessageFoldersService { const authContext = buildSystemAuthContext(workspaceId); - // TODO: Restore transaction wrapper once migration is complete — folder - // sync operations (create/update/delete) are no longer atomic since - // the data access layer routes writes across workspace and core schemas. - // Acceptable during transition as sync is idempotent and self-corrects. return this.globalWorkspaceOrmManager.executeInWorkspaceContext( async () => { if (folderIdsToDelete.length > 0) { - await this.messageFolderDataAccessService.update( - workspaceId, - { id: In(folderIdsToDelete) }, + await this.messageFolderRepository.update( + { id: In(folderIdsToDelete), workspaceId }, { pendingSyncAction: MessageFolderPendingSyncAction.FOLDER_DELETION, }, @@ -150,32 +153,34 @@ export class SyncMessageFoldersService { if (foldersToUpdate.size > 0) { for (const [id, data] of foldersToUpdate.entries()) { - await this.messageFolderDataAccessService.update( - workspaceId, - { id, messageChannelId }, - data, + await this.messageFolderRepository.update( + { id, messageChannelId, workspaceId }, + data as Record, ); } } if (foldersToCreate.length > 0) { for (const folderToCreate of foldersToCreate) { - await this.messageFolderDataAccessService.save( + await this.messageFolderRepository.save({ + ...folderToCreate, workspaceId, - folderToCreate, - ); + }); } } const createdFolders = foldersToCreate.length > 0 - ? await this.messageFolderDataAccessService.find(workspaceId, { - messageChannelId, - externalId: In( - foldersToCreate - .map((folder) => folder.externalId) - .filter(isDefined), - ), + ? await this.messageFolderRepository.find({ + where: { + messageChannelId, + externalId: In( + foldersToCreate + .map((folder) => folder.externalId) + .filter(isDefined), + ), + workspaceId, + }, }) : []; @@ -185,10 +190,7 @@ export class SyncMessageFoldersService { folderIdsToDelete, }); - return [ - ...updatedExistingFolders, - ...(createdFolders as MessageFolder[]), - ]; + return [...updatedExistingFolders, ...createdFolders]; }, authContext, ); diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-folder-ids-to-delete.util.spec.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-folder-ids-to-delete.util.spec.ts index be882130fa..8e07d3db5e 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-folder-ids-to-delete.util.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-folder-ids-to-delete.util.spec.ts @@ -1,4 +1,4 @@ -import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { MessageFolderPendingSyncAction } from 'twenty-shared/types'; import { computeFolderIdsToDelete } from 'src/modules/messaging/message-folder-manager/utils/compute-folder-ids-to-delete.util'; describe('computeFolderIdsToDelete', () => { diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-folders-to-create.util.spec.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-folders-to-create.util.spec.ts index 9bc9f33f53..13f6dbf944 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-folders-to-create.util.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-folders-to-create.util.spec.ts @@ -1,4 +1,4 @@ -import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { MessageFolderPendingSyncAction } from 'twenty-shared/types'; import { computeFoldersToCreate } from 'src/modules/messaging/message-folder-manager/utils/compute-folders-to-create.util'; describe('computeFoldersToCreate', () => { diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-folders-to-update.util.spec.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-folders-to-update.util.spec.ts index 304b8c2ec7..f68c1d1860 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-folders-to-update.util.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-folders-to-update.util.spec.ts @@ -1,7 +1,9 @@ -import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { MessageFolderPendingSyncAction } from 'twenty-shared/types'; import { computeFoldersToUpdate } from 'src/modules/messaging/message-folder-manager/utils/compute-folders-to-update.util'; describe('computeFoldersToUpdate', () => { + const emptyMap = new Map(); + it('should detect folder rename from provider', () => { const discoveredFolders = [ { @@ -29,19 +31,22 @@ describe('computeFoldersToUpdate', () => { const result = computeFoldersToUpdate({ discoveredFolders, existingFolders, + externalIdToUuidMap: emptyMap, }); expect(result.get('folder-id')?.name).toBe('Work Emails'); }); it('should detect folder moved to different parent', () => { + const existingParentUuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'; + const discoveredFolders = [ { name: 'Subfolder', externalId: 'sub-1', isSynced: true, isSentFolder: false, - parentFolderId: 'new-parent-id', + parentFolderId: 'new-parent-ext', }, ]; @@ -52,18 +57,23 @@ describe('computeFoldersToUpdate', () => { externalId: 'sub-1', isSynced: true, isSentFolder: false, - parentFolderId: 'old-parent-id', + parentFolderId: 'old-parent-uuid', syncCursor: 'cursor', pendingSyncAction: MessageFolderPendingSyncAction.NONE, }, ]; + const externalIdToUuidMap = new Map([ + ['new-parent-ext', existingParentUuid], + ]); + const result = computeFoldersToUpdate({ discoveredFolders, existingFolders, + externalIdToUuidMap, }); - expect(result.get('folder-id')?.parentFolderId).toBe('new-parent-id'); + expect(result.get('folder-id')?.parentFolderId).toBe(existingParentUuid); }); it('should not flag unchanged folders for update', () => { @@ -88,6 +98,7 @@ describe('computeFoldersToUpdate', () => { const result = computeFoldersToUpdate({ discoveredFolders, existingFolders, + externalIdToUuidMap: emptyMap, }); expect(result.size).toBe(0); @@ -120,6 +131,7 @@ describe('computeFoldersToUpdate', () => { const result = computeFoldersToUpdate({ discoveredFolders, existingFolders, + externalIdToUuidMap: emptyMap, }); expect(result.size).toBe(0); diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-updated-folders.util.spec.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-updated-folders.util.spec.ts index d7c8813367..6c839700bb 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-updated-folders.util.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/compute-updated-folders.util.spec.ts @@ -1,4 +1,4 @@ -import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { MessageFolderPendingSyncAction } from 'twenty-shared/types'; import { computeUpdatedFolders } from 'src/modules/messaging/message-folder-manager/utils/compute-updated-folders.util'; describe('computeUpdatedFolders', () => { diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/should-sync-folder-by-default.util.spec.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/should-sync-folder-by-default.util.spec.ts index bb783720ed..c6e9c26446 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/should-sync-folder-by-default.util.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/__tests__/should-sync-folder-by-default.util.spec.ts @@ -1,4 +1,4 @@ -import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { MessageFolderImportPolicy } from 'twenty-shared/types'; import { shouldSyncFolderByDefault } from 'src/modules/messaging/message-folder-manager/utils/should-sync-folder-by-default.util'; describe('shouldSyncFolderByDefault', () => { diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/compute-folders-to-create.util.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/compute-folders-to-create.util.ts index 2e45c4e1ac..8bf1d27256 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/compute-folders-to-create.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/compute-folders-to-create.util.ts @@ -5,7 +5,7 @@ import { type MessageFolder, } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface'; -import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; export const computeFoldersToCreate = ({ discoveredFolders, @@ -15,7 +15,7 @@ export const computeFoldersToCreate = ({ discoveredFolders: DiscoveredMessageFolder[]; existingFolders: MessageFolder[]; messageChannelId: string; -}): Partial[] => { +}): Partial[] => { const existingFoldersByExternalId = new Map( existingFolders.map((folder) => [folder.externalId, folder]), ); diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/compute-folders-to-update.util.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/compute-folders-to-update.util.ts index af16be4021..49b7595d79 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/compute-folders-to-update.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/compute-folders-to-update.util.ts @@ -6,23 +6,22 @@ import { type MessageFolder, } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface'; -import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; export const computeFoldersToUpdate = ({ discoveredFolders, existingFolders, + externalIdToUuidMap, }: { discoveredFolders: DiscoveredMessageFolder[]; existingFolders: MessageFolder[]; -}): Map> => { + externalIdToUuidMap: Map; +}): Map> => { const existingFoldersByExternalId = new Map( existingFolders.map((folder) => [folder.externalId, folder]), ); - const foldersToUpdate = new Map< - string, - Partial - >(); + const foldersToUpdate = new Map>(); for (const discoveredFolder of discoveredFolders) { const existingFolder = existingFoldersByExternalId.get( @@ -33,12 +32,16 @@ export const computeFoldersToUpdate = ({ continue; } + const resolvedParentFolderId = isNonEmptyString( + discoveredFolder.parentFolderId, + ) + ? (externalIdToUuidMap.get(discoveredFolder.parentFolderId) ?? null) + : null; + const discoveredFolderData = { name: discoveredFolder.name, isSentFolder: discoveredFolder.isSentFolder, - parentFolderId: isNonEmptyString(discoveredFolder.parentFolderId) - ? discoveredFolder.parentFolderId - : null, + parentFolderId: resolvedParentFolderId, }; const existingFolderData = { diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/compute-updated-folders.util.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/compute-updated-folders.util.ts index 7387a4631d..31bf86b8d5 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/compute-updated-folders.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/compute-updated-folders.util.ts @@ -1,9 +1,8 @@ import { type MessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface'; -import { - MessageFolderPendingSyncAction, - type MessageFolderWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { MessageFolderPendingSyncAction } from 'twenty-shared/types'; + +import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; export const computeUpdatedFolders = ({ existingFolders, @@ -11,7 +10,7 @@ export const computeUpdatedFolders = ({ folderIdsToDelete, }: { existingFolders: MessageFolder[]; - foldersToUpdate: Map>; + foldersToUpdate: Map>; folderIdsToDelete: string[]; }): MessageFolder[] => { return existingFolders.map((existingFolder) => { diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/should-sync-folder-by-default.util.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/should-sync-folder-by-default.util.ts index 99b61431e6..9dae21612d 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/should-sync-folder-by-default.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/utils/should-sync-folder-by-default.util.ts @@ -1,4 +1,4 @@ -import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { MessageFolderImportPolicy } from 'twenty-shared/types'; export const shouldSyncFolderByDefault = ( messageFolderImportPolicy: MessageFolderImportPolicy, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/commands/messaging-trigger-message-list-fetch.command.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/commands/messaging-trigger-message-list-fetch.command.ts index f71b6e307b..442df878e5 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/commands/messaging-trigger-message-list-fetch.command.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/commands/messaging-trigger-message-list-fetch.command.ts @@ -1,18 +1,20 @@ import { Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { Command, CommandRunner, Option } from 'nest-commander'; +import { Repository } from 'typeorm'; +import { MessageChannelSyncStage } from 'twenty-shared/types'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { MessagingMessageListFetchJob, type MessagingMessageListFetchJobData, } from 'src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job'; -import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; type MessagingTriggerMessageListFetchCommandOptions = { workspaceId: string; @@ -31,7 +33,8 @@ export class MessagingTriggerMessageListFetchCommand extends CommandRunner { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, @InjectMessageQueue(MessageQueue.messagingQueue) private readonly messageQueueService: MessageQueueService, ) { @@ -51,14 +54,14 @@ export class MessagingTriggerMessageListFetchCommand extends CommandRunner { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - const messageChannels = await this.messageChannelDataAccessService.find( - workspaceId, - { + const messageChannels = await this.messageChannelRepository.find({ + where: { isSyncEnabled: true, syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, ...(messageChannelId ? { id: messageChannelId } : {}), + workspaceId, }, - ); + }); if (messageChannels.length === 0) { this.logger.warn( @@ -73,9 +76,8 @@ export class MessagingTriggerMessageListFetchCommand extends CommandRunner { ); for (const messageChannel of messageChannels) { - await this.messageChannelDataAccessService.update( - workspaceId, - { id: messageChannel.id }, + await this.messageChannelRepository.update( + { id: messageChannel.id, workspaceId }, { syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED, syncStageStartedAt: new Date().toISOString(), diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-message-list-fetch.cron.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-message-list-fetch.cron.job.ts index 06ed4b6fd2..ac945ccbc3 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-message-list-fetch.cron.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-message-list-fetch.cron.job.ts @@ -1,20 +1,17 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; -import { FeatureFlagKey } from 'twenty-shared/types'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { DataSource, Repository } from 'typeorm'; +import { MessageChannelSyncStage } from 'twenty-shared/types'; import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; -import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessagingMessageListFetchJob, type MessagingMessageListFetchJobData, @@ -32,7 +29,6 @@ export class MessagingMessageListFetchCronJob { @InjectDataSource() private readonly coreDataSource: DataSource, private readonly exceptionHandlerService: ExceptionHandlerService, - private readonly featureFlagService: FeatureFlagService, ) {} @Process(MessagingMessageListFetchCronJob.name) @@ -51,22 +47,11 @@ export class MessagingMessageListFetchCronJob { try { const now = new Date().toISOString(); - // TODO: remove workspace schema branch once IS_CONNECTED_ACCOUNT_MIGRATED feature flag is removed - const isMigrated = await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - activeWorkspace.id, + const [messageChannels] = await this.coreDataSource.query( + `UPDATE core."messageChannel" SET "syncStage" = '${MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}') + WHERE "workspaceId" = '${activeWorkspace.id}' AND "isSyncEnabled" = true AND "syncStage" = '${MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING}' RETURNING *`, ); - const [messageChannels] = isMigrated - ? await this.coreDataSource.query( - `UPDATE core."messageChannel" SET "syncStage" = '${MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}') - WHERE "workspaceId" = '${activeWorkspace.id}' AND "isSyncEnabled" = true AND "syncStage" = '${MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING}' RETURNING *`, - ) - : await this.coreDataSource.query( - `UPDATE ${getWorkspaceSchemaName(activeWorkspace.id)}."messageChannel" SET "syncStage" = '${MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}') - WHERE "isSyncEnabled" = true AND "syncStage" = '${MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING}' RETURNING *`, - ); - for (const messageChannel of messageChannels) { await this.messageQueueService.add( MessagingMessageListFetchJob.name, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-messages-import.cron.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-messages-import.cron.job.ts index 44917ba1b9..f657b6fd52 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-messages-import.cron.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-messages-import.cron.job.ts @@ -1,13 +1,12 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; -import { FeatureFlagKey } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { DataSource, Repository } from 'typeorm'; +import { MessageChannelSyncStage } from 'twenty-shared/types'; import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; @@ -18,8 +17,6 @@ import { DataSourceException, DataSourceExceptionCode, } from 'src/engine/metadata-modules/data-source/data-source.exception'; -import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; -import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessagingMessagesImportJob, type MessagingMessagesImportJobData, @@ -37,7 +34,6 @@ export class MessagingMessagesImportCronJob { private readonly exceptionHandlerService: ExceptionHandlerService, @InjectDataSource() private readonly coreDataSource: DataSource, - private readonly featureFlagService: FeatureFlagService, ) {} @Process(MessagingMessagesImportCronJob.name) @@ -56,22 +52,11 @@ export class MessagingMessagesImportCronJob { try { const now = new Date().toISOString(); - // TODO: remove workspace schema branch once IS_CONNECTED_ACCOUNT_MIGRATED feature flag is removed - const isMigrated = await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - activeWorkspace.id, + const [messageChannels] = await this.coreDataSource.query( + `UPDATE core."messageChannel" SET "syncStage" = '${MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}') + WHERE "workspaceId" = '${activeWorkspace.id}' AND "isSyncEnabled" = true AND "syncStage" = '${MessageChannelSyncStage.MESSAGES_IMPORT_PENDING}' RETURNING *`, ); - const [messageChannels] = isMigrated - ? await this.coreDataSource.query( - `UPDATE core."messageChannel" SET "syncStage" = '${MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}') - WHERE "workspaceId" = '${activeWorkspace.id}' AND "isSyncEnabled" = true AND "syncStage" = '${MessageChannelSyncStage.MESSAGES_IMPORT_PENDING}' RETURNING *`, - ) - : await this.coreDataSource.query( - `UPDATE ${getWorkspaceSchemaName(activeWorkspace.id)}."messageChannel" SET "syncStage" = '${MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}') - WHERE "isSyncEnabled" = true AND "syncStage" = '${MessageChannelSyncStage.MESSAGES_IMPORT_PENDING}' RETURNING *`, - ); - for (const messageChannel of messageChannels) { await this.messageQueueService.add( MessagingMessagesImportJob.name, @@ -82,9 +67,6 @@ export class MessagingMessagesImportCronJob { ); } } catch (error) { - // We had issues with the workspace schema not being found, due - // to users deleting their workspaces in the middle of the cron job - // We only throw an error when the workspace is found & schema not found if ( error.code === '42P01' && error.message.includes('messageChannel" does not exist') diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-relaunch-failed-message-channels.cron.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-relaunch-failed-message-channels.cron.job.ts index 8513f100e7..62dce272bd 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-relaunch-failed-message-channels.cron.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-relaunch-failed-message-channels.cron.job.ts @@ -3,6 +3,10 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { DataSource, Repository } from 'typeorm'; +import { + MessageChannelSyncStage, + MessageChannelSyncStatus, +} from 'twenty-shared/types'; import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; @@ -12,10 +16,6 @@ import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queu import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; -import { - MessageChannelSyncStage, - MessageChannelSyncStatus, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessagingRelaunchFailedMessageChannelJob, type MessagingRelaunchFailedMessageChannelJobData, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service.spec.ts index 94cd428bf1..ecd7508946 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service.spec.ts @@ -1,14 +1,16 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { google } from 'googleapis'; -import { ConnectedAccountProvider } from 'twenty-shared/types'; +import { + ConnectedAccountProvider, + MessageFolderImportPolicy, + MessageFolderPendingSyncAction, +} from 'twenty-shared/types'; import { type MessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GmailGetHistoryService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-history.service'; import { GmailGetMessageListService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service'; import { GmailMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-message-list-fetch-error-handler.service'; @@ -31,7 +33,7 @@ describe('GmailGetMessageListService', () => { let oAuth2ClientManagerService: OAuth2ClientManagerService; const mockConnectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, | 'provider' | 'accessToken' | 'refreshToken' diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service.ts index 7324058980..5e47acfe7f 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service.ts @@ -3,13 +3,11 @@ import { Injectable, Logger } from '@nestjs/common'; import { isNonEmptyString } from '@sniptt/guards'; import { google } from 'googleapis'; +import { MessageFolderImportPolicy } from 'twenty-shared/types'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { - MessageChannelWorkspaceEntity, - MessageFolderImportPolicy, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { MessageImportDriverException, MessageImportDriverExceptionCode, @@ -32,17 +30,14 @@ export class GmailGetMessageListService { private async getMessageListWithoutCursor( connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, 'provider' | 'accessToken' | 'refreshToken' | 'id' | 'handle' >, messageFolders: Pick< - MessageFolderWorkspaceEntity, + MessageFolderEntity, 'name' | 'externalId' | 'isSynced' | 'parentFolderId' >[], - messageChannel: Pick< - MessageChannelWorkspaceEntity, - 'messageFolderImportPolicy' - >, + messageChannel: Pick, ): Promise { const oAuth2Client = await this.oAuth2ClientManagerService.getGoogleOAuth2Client( diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-messages.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-messages.service.ts index 76a070b9e0..77e41e6ba8 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-messages.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-messages.service.ts @@ -5,12 +5,10 @@ import { isNonEmptyString } from '@sniptt/guards'; import { type gmail_v1 as gmailV1, google } from 'googleapis'; import { isDefined } from 'twenty-shared/utils'; +import { MessageFolderImportPolicy } from 'twenty-shared/types'; +import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { - type MessageChannelWorkspaceEntity, - MessageFolderImportPolicy, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GmailMessagesImportErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-messages-import-error-handler.service'; import { filterGmailMessagesByFolderPolicy } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/filter-gmail-messages-by-folder-policy.util'; import { parseAndFormatGmailMessage } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util'; @@ -28,7 +26,7 @@ export class GmailGetMessagesService { async getMessages( messageIds: string[], connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, | 'provider' | 'accessToken' | 'refreshToken' @@ -37,7 +35,7 @@ export class GmailGetMessagesService { | 'handleAliases' >, messageChannel: Pick< - MessageChannelWorkspaceEntity, + MessageChannelEntity, 'messageFolders' | 'messageFolderImportPolicy' >, ): Promise { @@ -169,10 +167,7 @@ export class GmailGetMessagesService { private async fetchMessages( gmailClient: gmailV1.Gmail, messageIds: string[], - connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, - 'handle' | 'handleAliases' - >, + connectedAccount: Pick, ): Promise { const results = await Promise.all( messageIds.map((messageId) => diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/compute-gmail-exclude-search-filter.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/compute-gmail-exclude-search-filter.spec.ts index 44c38bf5fe..a9ab3768a0 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/compute-gmail-exclude-search-filter.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/compute-gmail-exclude-search-filter.spec.ts @@ -1,4 +1,4 @@ -import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { MessageFolderImportPolicy } from 'twenty-shared/types'; import { computeGmailExcludeSearchFilter } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-gmail-exclude-search-filter.util'; describe('computeGmailExcludeSearchFilter', () => { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/filter-out-gmail-excluded-folders.util.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/filter-out-gmail-excluded-folders.util.spec.ts index 7cc9706b2c..36d886e643 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/filter-out-gmail-excluded-folders.util.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/filter-out-gmail-excluded-folders.util.spec.ts @@ -1,5 +1,5 @@ -import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { MessageFolderImportPolicy } from 'twenty-shared/types'; +import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { filterGmailMessagesByFolderPolicy } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/filter-gmail-messages-by-folder-policy.util'; import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message'; @@ -12,8 +12,8 @@ const createMessage = ( const createFolder = ( externalId: string, isSynced: boolean, -): MessageFolderWorkspaceEntity => - ({ externalId, isSynced }) as MessageFolderWorkspaceEntity; +): MessageFolderEntity => + ({ externalId, isSynced }) as unknown as MessageFolderEntity; describe('filterGmailMessagesByFolderPolicy', () => { describe('ALL_FOLDERS policy', () => { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/build-gmail-label-search-name.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/build-gmail-label-search-name.util.ts index 10535d1cc4..c660e2c14b 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/build-gmail-label-search-name.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/build-gmail-label-search-name.util.ts @@ -1,9 +1,9 @@ -import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; const MAXIMUM_GMAIL_FOLDER_DEPTH = 50; type FolderInput = Pick< - MessageFolderWorkspaceEntity, + MessageFolderEntity, 'externalId' | 'name' | 'parentFolderId' >; diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-gmail-exclude-search-filter.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-gmail-exclude-search-filter.util.ts index 35d6e61ba5..0fa4b7de59 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-gmail-exclude-search-filter.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-gmail-exclude-search-filter.util.ts @@ -1,7 +1,7 @@ import { isDefined } from 'twenty-shared/utils'; -import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { MessageFolderImportPolicy } from 'twenty-shared/types'; +import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { MESSAGING_GMAIL_DEFAULT_EXCLUDED_LABELS } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-default-excluded-labels.constant'; import { MESSAGING_GMAIL_EXCLUDED_SYSTEM_LABELS } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-excluded-system-labels.constant'; import { MESSAGING_GMAIL_FOLDERS_WITH_CATEGORY_EXCLUSIONS } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-folders-with-category-exclusions.constant'; @@ -10,7 +10,7 @@ import { computeGmailDefaultNotSyncedLabelsSearchFilter } from 'src/modules/mess export const computeGmailExcludeSearchFilter = ( messageFolders: Pick< - MessageFolderWorkspaceEntity, + MessageFolderEntity, 'externalId' | 'isSynced' | 'name' | 'parentFolderId' >[], messageFolderImportPolicy: MessageFolderImportPolicy, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util.ts index 53341e6f21..397ba21666 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util.ts @@ -1,12 +1,9 @@ -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum'; export const computeMessageDirection = ( fromHandle: string, - connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, - 'handle' | 'handleAliases' - >, + connectedAccount: Pick, ): MessageDirection => connectedAccount.handle === fromHandle || connectedAccount.handleAliases?.includes(fromHandle) diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/filter-gmail-messages-by-folder-policy.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/filter-gmail-messages-by-folder-policy.util.ts index 3408fe73f9..4f7aa057b6 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/filter-gmail-messages-by-folder-policy.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/filter-gmail-messages-by-folder-policy.util.ts @@ -1,7 +1,5 @@ -import { - type MessageChannelWorkspaceEntity, - MessageFolderImportPolicy, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { MessageFolderImportPolicy } from 'twenty-shared/types'; +import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { MESSAGING_GMAIL_EXCLUDED_CATEGORY_LABELS } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-excluded-category-labels.constant'; import { MESSAGING_GMAIL_FOLDERS_WITH_CATEGORY_EXCLUSIONS } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-folders-with-category-exclusions.constant'; import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message'; @@ -9,7 +7,7 @@ import { type MessageWithParticipants } from 'src/modules/messaging/message-impo export const filterGmailMessagesByFolderPolicy = ( messages: MessageWithParticipants[], messageChannel: Pick< - MessageChannelWorkspaceEntity, + MessageChannelEntity, 'messageFolders' | 'messageFolderImportPolicy' >, ): MessageWithParticipants[] => { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util.ts index df0242871d..dcc014bc19 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util.ts @@ -2,7 +2,7 @@ import { type gmail_v1 as gmailV1 } from 'googleapis'; import planer from 'planer'; import { MessageParticipantRole } from 'twenty-shared/types'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { computeMessageDirection } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util'; import { parseGmailMessage } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-message.util'; import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message'; @@ -11,10 +11,7 @@ import { sanitizeString } from 'src/modules/messaging/message-import-manager/uti export const parseAndFormatGmailMessage = ( message: gmailV1.Schema$Message, - connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, - 'handle' | 'handleAliases' - >, + connectedAccount: Pick, ): MessageWithParticipants | null => { const { id, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider.ts index 562178bbab..6574759348 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider.ts @@ -6,12 +6,12 @@ import { CustomError, isDefined } from 'twenty-shared/utils'; import { type ImapSmtpCaldavParams } from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type'; import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { MessageImportDriverExceptionCode } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception'; import { parseImapAuthenticationError } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/parse-imap-authentication-error.util'; type ConnectedAccountIdentifier = Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, 'id' | 'provider' | 'connectionParameters' | 'handle' >; diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.spec.ts index a2d89963b6..82a0a96cfa 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.spec.ts @@ -1,12 +1,14 @@ import { Test, type TestingModule } from '@nestjs/testing'; -import { ConnectedAccountProvider } from 'twenty-shared/types'; +import { + ConnectedAccountProvider, + MessageFolderImportPolicy, + MessageFolderPendingSyncAction, +} from 'twenty-shared/types'; import { type MessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { ImapClientProvider } from 'src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider'; import { ImapGetMessageListService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service'; import { ImapMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-list-fetch-error-handler.service'; @@ -29,7 +31,7 @@ describe('ImapGetMessageListService', () => { let imapClientProvider: ImapClientProvider; const mockConnectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, | 'provider' | 'accessToken' | 'refreshToken' diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.ts index 51ebd36883..74faf29bb5 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.ts @@ -3,9 +3,9 @@ import { Injectable, Logger } from '@nestjs/common'; import { type ImapFlow } from 'imapflow'; import { isDefined } from 'twenty-shared/utils'; +import { MessageFolderImportPolicy } from 'twenty-shared/types'; import { MessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface'; -import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessageImportDriverException, MessageImportDriverExceptionCode, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts index 0583215d37..b61d16b36b 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-messages.service.ts @@ -4,7 +4,7 @@ import { type ImapFlow } from 'imapflow'; import { Address, type Email as ParsedMail } from 'postal-mime'; import { MessageParticipantRole } from 'twenty-shared/types'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { computeMessageDirection } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util'; import { ImapClientProvider } from 'src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider'; import { ImapMessageParserService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-parser.service'; @@ -17,7 +17,7 @@ import { formatAddressObjectAsParticipants } from 'src/modules/messaging/message import { sanitizeString } from 'src/modules/messaging/message-import-manager/utils/sanitize-string.util'; type ConnectedAccount = Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, 'id' | 'provider' | 'handle' | 'handleAliases' | 'connectionParameters' >; @@ -162,10 +162,7 @@ export class ImapGetMessagesService { uid: number, folderPath: string, folderExternalId: string, - connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, - 'handle' | 'handleAliases' - >, + connectedAccount: Pick, ): MessageWithParticipants { const fromAddresses = this.extractAddresses(parsed.from); const senderAddress = fromAddresses[0]?.address ?? ''; diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-fetch-by-batch.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-fetch-by-batch.service.ts index 06418dfa62..5326b463b5 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-fetch-by-batch.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-fetch-by-batch.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { type MicrosoftGraphBatchResponse } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.interface'; @Injectable() @@ -13,7 +13,7 @@ export class MicrosoftFetchByBatchService { async fetchAllByBatches( messageIds: string[], connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, 'accessToken' | 'refreshToken' | 'id' | 'provider' >, ): Promise<{ diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.dev.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.dev.spec.ts index e857f35b03..3ddcd55e1a 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.dev.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.dev.spec.ts @@ -1,20 +1,18 @@ import { ConfigService } from '@nestjs/config'; import { Test, type TestingModule } from '@nestjs/testing'; -import { ConnectedAccountProvider } from 'twenty-shared/types'; +import { + ConnectedAccountProvider, + MessageFolderImportPolicy, + MessageFolderPendingSyncAction, +} from 'twenty-shared/types'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module'; import { MicrosoftOAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/drivers/microsoft/microsoft-oauth2-client-manager.service'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { - MessageChannelWorkspaceEntity, - MessageFolderImportPolicy, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { - MessageFolderPendingSyncAction, - MessageFolderWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { microsoftGraphWithMessagesDeltaLink } from 'src/modules/messaging/message-import-manager/drivers/microsoft/mocks/microsoft-api-examples'; import { MessageFolderName } from 'src/modules/messaging/message-import-manager/drivers/microsoft/types/folders'; @@ -26,7 +24,7 @@ const accessToken = 'replace-with-your-access-token'; const refreshToken = 'replace-with-your-refresh-token'; const syncCursor = `replace-with-your-sync-cursor`; const mockConnectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, | 'provider' | 'accessToken' | 'refreshToken' @@ -43,7 +41,7 @@ const mockConnectedAccount: Pick< }; const mockMessageChannel: Pick< - MessageChannelWorkspaceEntity, + MessageChannelEntity, 'id' | 'syncCursor' | 'messageFolderImportPolicy' > = { id: 'message-channel-id', @@ -178,7 +176,7 @@ xdescribe('Microsoft dev tests : get message list service', () => { xdescribe('Microsoft dev tests : get message list service for folders', () => { let service: MicrosoftGetMessageListService; - const inboxFolder = new MessageFolderWorkspaceEntity(); + const inboxFolder = new MessageFolderEntity(); inboxFolder.id = 'inbox-folder-id'; inboxFolder.name = MessageFolderName.INBOX; @@ -186,7 +184,7 @@ xdescribe('Microsoft dev tests : get message list service for folders', () => { inboxFolder.messageChannelId = 'message-channel-1'; inboxFolder.parentFolderId = null; - const sentFolder = new MessageFolderWorkspaceEntity(); + const sentFolder = new MessageFolderEntity(); sentFolder.id = 'sent-folder-id'; sentFolder.name = MessageFolderName.SENT_ITEMS; @@ -194,7 +192,7 @@ xdescribe('Microsoft dev tests : get message list service for folders', () => { sentFolder.messageChannelId = 'message-channel-1'; sentFolder.parentFolderId = null; - const otherFolder = new MessageFolderWorkspaceEntity(); + const otherFolder = new MessageFolderEntity(); otherFolder.id = 'other-folder-id'; otherFolder.name = 'other'; @@ -202,7 +200,7 @@ xdescribe('Microsoft dev tests : get message list service for folders', () => { otherFolder.messageChannelId = 'message-channel-2'; otherFolder.parentFolderId = null; - const messageChannelNoFolders = new MessageChannelWorkspaceEntity(); + const messageChannelNoFolders = new MessageChannelEntity(); messageChannelNoFolders.id = 'message-channel-0'; messageChannelNoFolders.messageFolders = []; @@ -210,7 +208,7 @@ xdescribe('Microsoft dev tests : get message list service for folders', () => { messageChannelNoFolders.messageFolderImportPolicy = MessageFolderImportPolicy.SELECTED_FOLDERS; - const messageChannelMicrosoftOneFolder = new MessageChannelWorkspaceEntity(); + const messageChannelMicrosoftOneFolder = new MessageChannelEntity(); messageChannelMicrosoftOneFolder.id = 'message-channel-1'; messageChannelMicrosoftOneFolder.messageFolders = [inboxFolder]; @@ -218,7 +216,7 @@ xdescribe('Microsoft dev tests : get message list service for folders', () => { messageChannelMicrosoftOneFolder.messageFolderImportPolicy = MessageFolderImportPolicy.SELECTED_FOLDERS; - const messageChannelMicrosoft = new MessageChannelWorkspaceEntity(); + const messageChannelMicrosoft = new MessageChannelEntity(); messageChannelMicrosoft.id = 'message-channel-2'; messageChannelMicrosoft.messageFolders = [inboxFolder, sentFolder]; diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.spec.ts index ec52e19be9..9c3d916721 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.spec.ts @@ -1,13 +1,15 @@ import { Test, type TestingModule } from '@nestjs/testing'; -import { ConnectedAccountProvider } from 'twenty-shared/types'; +import { + ConnectedAccountProvider, + MessageFolderImportPolicy, + MessageFolderPendingSyncAction, +} from 'twenty-shared/types'; import { type MessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { MicrosoftGetMessageListService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service'; import { MicrosoftMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-message-list-fetch-error-handler.service'; @@ -28,7 +30,7 @@ describe('MicrosoftGetMessageListService', () => { let oAuth2ClientManagerService: OAuth2ClientManagerService; const mockConnectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, | 'provider' | 'accessToken' | 'refreshToken' diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts index 6840c341ee..854e00fa56 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts @@ -8,10 +8,10 @@ import { import { isNonEmptyString } from '@sniptt/guards'; import pLimit from 'p-limit'; +import { MessageFolderImportPolicy } from 'twenty-shared/types'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { MicrosoftMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-message-list-fetch-error-handler.service'; import { type GetMessageListsArgs } from 'src/modules/messaging/message-import-manager/types/get-message-lists-args.type'; import { @@ -72,11 +72,11 @@ export class MicrosoftGetMessageListService { public async getMessageList( connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, 'provider' | 'accessToken' | 'id' >, messageFolder: Pick< - MessageFolderWorkspaceEntity, + MessageFolderEntity, 'name' | 'syncCursor' | 'externalId' >, ): Promise { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.dev.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.dev.spec.ts index e30c33e063..ed1b1a4775 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.dev.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.dev.spec.ts @@ -47,7 +47,7 @@ xdescribe('Microsoft dev tests : get messages service', () => { id: 'connected-account-id', provider: ConnectedAccountProvider.MICROSOFT, handle: 'John.Walker@outlook.fr', - handleAliases: '', + handleAliases: [] as string[], accessToken: accessToken, refreshToken: refreshToken, }; diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.spec.ts index bb597fc616..2b06b4c3d0 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.spec.ts @@ -69,7 +69,7 @@ describe('Microsoft get messages service', () => { accessToken: 'access-token', refreshToken: 'refresh-token', handle: 'John.l@outlook.fr', - handleAliases: '', + handleAliases: [] as string[], }; const messages = service.formatBatchResponsesAsMessages( batchResponses, @@ -170,7 +170,7 @@ describe('Microsoft get messages service', () => { accessToken: 'access-token', refreshToken: 'refresh-token', handle: 'John.l@outlook.fr', - handleAliases: '', + handleAliases: [] as string[], }; const messages = service.formatBatchResponsesAsMessages( batchResponses, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts index bdec6b6398..a3759b7b1f 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.service.ts @@ -4,7 +4,7 @@ import { type EmailAddress } from 'addressparser'; import { MessageParticipantRole } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum'; import { computeMessageDirection } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util'; import { MicrosoftImportDriverException } from 'src/modules/messaging/message-import-manager/drivers/microsoft/exceptions/microsoft-import-driver.exception'; @@ -17,7 +17,7 @@ import { MicrosoftFetchByBatchService } from './microsoft-fetch-by-batch.service import { MicrosoftMessagesImportErrorHandler } from './microsoft-messages-import-error-handler.service'; type ConnectedAccountType = Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, | 'accessToken' | 'refreshToken' | 'id' diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider.ts index 0441dd43a7..1d888443af 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider.ts @@ -6,7 +6,7 @@ import { isDefined } from 'twenty-shared/utils'; import type SMTPConnection from 'nodemailer/lib/smtp-connection'; import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; @Injectable() export class SmtpClientProvider { @@ -16,7 +16,7 @@ export class SmtpClientProvider { public async getSmtpClient( connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, 'connectionParameters' | 'handle' >, ): Promise { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/__tests__/messaging-relaunch-failed-message-channel.job.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/__tests__/messaging-relaunch-failed-message-channel.job.spec.ts index fc0a539bc4..30d93c3ce4 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/__tests__/messaging-relaunch-failed-message-channel.job.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/__tests__/messaging-relaunch-failed-message-channel.job.spec.ts @@ -1,14 +1,14 @@ import { type Provider } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; -import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { MessageChannelSyncStage, MessageChannelSyncStatus, - type MessageChannelWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +} from 'twenty-shared/types'; +import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { MessagingRelaunchFailedMessageChannelJob } from 'src/modules/messaging/message-import-manager/jobs/messaging-relaunch-failed-message-channel.job'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; describe('MessagingRelaunchFailedMessageChannelJob', () => { let job: MessagingRelaunchFailedMessageChannelJob; @@ -33,7 +33,7 @@ describe('MessagingRelaunchFailedMessageChannelJob', () => { }, }, { - provide: MessageChannelDataAccessService, + provide: getRepositoryToken(MessageChannelEntity), useValue: { findOne: mockFindOne, update: mockUpdate, @@ -56,13 +56,12 @@ describe('MessagingRelaunchFailedMessageChannelJob', () => { throttleFailureCount: 5, throttleRetryAfter: '2026-03-19T06:49:34.295Z', syncStageStartedAt: '2026-03-19T06:34:34.000Z', - } as Partial); + } as unknown as Partial); await job.handle({ workspaceId, messageChannelId }); expect(mockUpdate).toHaveBeenCalledWith( - workspaceId, - { id: messageChannelId }, + { id: messageChannelId, workspaceId }, { syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, syncStatus: MessageChannelSyncStatus.ACTIVE, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts index a00fb4b8b8..3a7152bc9d 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job.ts @@ -1,23 +1,23 @@ import { Scope } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { MessageChannelSyncStage } from 'twenty-shared/types'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { isThrottled } from 'src/modules/connected-account/utils/is-throttled'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; -import { - MessageChannelSyncStage, - type MessageChannelWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessageImportExceptionHandlerService, MessageImportSyncStep, } from 'src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service'; import { MessagingMessageListFetchService } from 'src/modules/messaging/message-import-manager/services/messaging-message-list-fetch.service'; import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; const toIsoStringOrNull = ( value: string | Date | null | undefined, @@ -43,7 +43,8 @@ export class MessagingMessageListFetchJob { private readonly messagingMessageListFetchService: MessagingMessageListFetchService, private readonly messagingMonitoringService: MessagingMonitoringService, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, private readonly messageImportErrorHandlerService: MessageImportExceptionHandlerService, private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService, ) {} @@ -61,15 +62,13 @@ export class MessagingMessageListFetchJob { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - const messageChannel = await this.messageChannelDataAccessService.findOne( - workspaceId, - { - where: { - id: messageChannelId, - }, - relations: ['connectedAccount', 'messageFolders'], + const messageChannel = await this.messageChannelRepository.findOne({ + where: { + id: messageChannelId, + workspaceId, }, - ); + relations: { connectedAccount: true, messageFolders: true }, + }); if (!messageChannel) { await this.messagingMonitoringService.track({ @@ -113,7 +112,7 @@ export class MessagingMessageListFetchJob { }); await this.messagingMessageListFetchService.processMessageListFetch( - messageChannel as unknown as MessageChannelWorkspaceEntity, + messageChannel, workspaceId, ); @@ -127,7 +126,7 @@ export class MessagingMessageListFetchJob { await this.messageImportErrorHandlerService.handleDriverException( error, MessageImportSyncStep.MESSAGE_LIST_FETCH, - messageChannel as unknown as MessageChannelWorkspaceEntity, + messageChannel, workspaceId, ); } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts index 5e6987fb31..c0c98d4b80 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job.ts @@ -1,19 +1,19 @@ import { Scope } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { MessageChannelSyncStage } from 'twenty-shared/types'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { isThrottled } from 'src/modules/connected-account/utils/is-throttled'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; -import { - MessageChannelSyncStage, - type MessageChannelWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessagingMessagesImportService } from 'src/modules/messaging/message-import-manager/services/messaging-messages-import.service'; import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; const toIsoStringOrNull = ( value: string | Date | null | undefined, @@ -40,7 +40,8 @@ export class MessagingMessagesImportJob { private readonly messagingMonitoringService: MessagingMonitoringService, private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, ) {} @Process(MessagingMessagesImportJob.name) @@ -56,15 +57,13 @@ export class MessagingMessagesImportJob { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - const messageChannel = await this.messageChannelDataAccessService.findOne( - workspaceId, - { - where: { - id: messageChannelId, - }, - relations: ['connectedAccount', 'messageFolders'], + const messageChannel = await this.messageChannelRepository.findOne({ + where: { + id: messageChannelId, + workspaceId, }, - ); + relations: { connectedAccount: true, messageFolders: true }, + }); if (!messageChannel) { await this.messagingMonitoringService.track({ @@ -103,12 +102,9 @@ export class MessagingMessagesImportJob { return; } - const messageChannelWorkspace = - messageChannel as unknown as MessageChannelWorkspaceEntity; - await this.messagingMessagesImportService.processMessageBatchImport( - messageChannelWorkspace, - messageChannelWorkspace.connectedAccount, + messageChannel, + messageChannel.connectedAccount, workspaceId, ); }, authContext); diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-ongoing-stale.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-ongoing-stale.job.ts index 25aa1ccdfd..7912a0e688 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-ongoing-stale.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-ongoing-stale.job.ts @@ -1,15 +1,16 @@ import { Logger, Scope } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { In } from 'typeorm'; +import { In, Repository } from 'typeorm'; +import { MessageChannelSyncStage } from 'twenty-shared/types'; import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; -import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { isSyncStale } from 'src/modules/messaging/message-import-manager/utils/is-sync-stale.util'; const toIsoStringOrNull = ( @@ -34,7 +35,8 @@ export class MessagingOngoingStaleJob { private readonly logger = new Logger(MessagingOngoingStaleJob.name); constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService, ) {} @@ -45,17 +47,17 @@ export class MessagingOngoingStaleJob { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - const messageChannels = await this.messageChannelDataAccessService.find( - workspaceId, - { + const messageChannels = await this.messageChannelRepository.find({ + where: { syncStage: In([ MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING, MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING, MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED, MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED, ]), + workspaceId, }, - ); + }); for (const messageChannel of messageChannels) { if (isSyncStale(toIsoStringOrNull(messageChannel.syncStageStartedAt))) { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-relaunch-failed-message-channel.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-relaunch-failed-message-channel.job.ts index 2019677618..437a0e374a 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-relaunch-failed-message-channel.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/jobs/messaging-relaunch-failed-message-channel.job.ts @@ -1,15 +1,18 @@ import { Scope } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Repository } from 'typeorm'; -import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; -import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; -import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; -import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { MessageChannelSyncStage, MessageChannelSyncStatus, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +} from 'twenty-shared/types'; +import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator'; +import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; +import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; +import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; export type MessagingRelaunchFailedMessageChannelJobData = { workspaceId: string; @@ -23,7 +26,8 @@ export type MessagingRelaunchFailedMessageChannelJobData = { export class MessagingRelaunchFailedMessageChannelJob { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, ) {} @Process(MessagingRelaunchFailedMessageChannelJob.name) @@ -33,14 +37,12 @@ export class MessagingRelaunchFailedMessageChannelJob { const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - const messageChannel = await this.messageChannelDataAccessService.findOne( - workspaceId, - { - where: { - id: messageChannelId, - }, + const messageChannel = await this.messageChannelRepository.findOne({ + where: { + id: messageChannelId, + workspaceId, }, - ); + }); if ( !messageChannel || @@ -50,9 +52,8 @@ export class MessagingRelaunchFailedMessageChannelJob { return; } - await this.messageChannelDataAccessService.update( - workspaceId, - { id: messageChannelId }, + await this.messageChannelRepository.update( + { id: messageChannelId, workspaceId }, { syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, syncStatus: MessageChannelSyncStatus.ACTIVE, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/listeners/messaging-import-manager-message-channel.listener.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/listeners/messaging-import-manager-message-channel.listener.ts deleted file mode 100644 index b514032667..0000000000 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/listeners/messaging-import-manager-message-channel.listener.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { type ObjectRecordDeleteEvent } from 'twenty-shared/database-events'; - -import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; -import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; -import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; -import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type'; -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { - MessagingCleanCacheJob, - type MessagingCleanCacheJobData, -} from 'src/modules/messaging/message-import-manager/jobs/messaging-clean-cache'; -import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator'; -import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; - -@Injectable() -export class MessagingMessageImportManagerMessageChannelListener { - constructor( - @InjectMessageQueue(MessageQueue.messagingQueue) - private readonly messageQueueService: MessageQueueService, - ) {} - - @OnDatabaseBatchEvent('messageChannel', DatabaseEventAction.DESTROYED) - async handleDestroyedEvent( - payload: WorkspaceEventBatch< - ObjectRecordDeleteEvent - >, - ) { - await Promise.all( - payload.events.map((eventPayload) => - this.messageQueueService.add( - MessagingCleanCacheJob.name, - { - workspaceId: payload.workspaceId, - messageChannelId: eventPayload.recordId, - }, - ), - ), - ); - } -} diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/messaging-import-manager.module.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/messaging-import-manager.module.ts index 56839daa8e..e2ca4dd292 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/messaging-import-manager.module.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/messaging-import-manager.module.ts @@ -2,10 +2,11 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; -import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module'; -import { MessageFolderDataAccessModule } from 'src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module'; @@ -36,7 +37,6 @@ import { MessagingMessageListFetchJob } from 'src/modules/messaging/message-impo import { MessagingMessagesImportJob } from 'src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job'; import { MessagingOngoingStaleJob } from 'src/modules/messaging/message-import-manager/jobs/messaging-ongoing-stale.job'; import { MessagingRelaunchFailedMessageChannelJob } from 'src/modules/messaging/message-import-manager/jobs/messaging-relaunch-failed-message-channel.job'; -import { MessagingMessageImportManagerMessageChannelListener } from 'src/modules/messaging/message-import-manager/listeners/messaging-import-manager-message-channel.listener'; import { MessagingAccountAuthenticationService } from 'src/modules/messaging/message-import-manager/services/messaging-account-authentication.service'; import { MessagingCursorService } from 'src/modules/messaging/message-import-manager/services/messaging-cursor.service'; import { MessagingDeleteFolderMessagesService } from 'src/modules/messaging/message-import-manager/services/messaging-delete-folder-messages.service'; @@ -63,12 +63,13 @@ import { MessagingMonitoringModule } from 'src/modules/messaging/monitoring/mess MessagingIMAPDriverModule, MessagingSmtpDriverModule, MessagingCommonModule, - MessageChannelDataAccessModule, - MessageFolderDataAccessModule, TypeOrmModule.forFeature([ WorkspaceEntity, DataSourceEntity, ObjectMetadataEntity, + MessageChannelEntity, + MessageFolderEntity, + UserWorkspaceEntity, ]), EmailAliasManagerModule, FeatureFlagModule, @@ -95,7 +96,6 @@ import { MessagingMonitoringModule } from 'src/modules/messaging/monitoring/mess MessagingOngoingStaleCronJob, MessagingRelaunchFailedMessageChannelsCronJob, MessagingAddSingleMessageToCacheForImportJob, - MessagingMessageImportManagerMessageChannelListener, MessagingCleanCacheJob, MessagingMessageService, MessagingMessageFolderAssociationService, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-message-list-fetch.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-message-list-fetch.service.spec.ts index 5a9ce48249..83096f4c41 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-message-list-fetch.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-message-list-fetch.service.spec.ts @@ -1,19 +1,15 @@ import { Test, type TestingModule } from '@nestjs/testing'; -import { ConnectedAccountProvider } from 'twenty-shared/types'; +import { + ConnectedAccountProvider, + MessageFolderImportPolicy, + MessageFolderPendingSyncAction, +} from 'twenty-shared/types'; import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; -import { - MessageFolderImportPolicy, - type MessageChannelWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { - MessageFolderPendingSyncAction, - type MessageFolderWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service'; import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service'; import { MessagingAccountAuthenticationService } from 'src/modules/messaging/message-import-manager/services/messaging-account-authentication.service'; @@ -24,7 +20,8 @@ import { MessagingMessageListFetchService } from 'src/modules/messaging/message- import { MessagingMessagesImportService } from 'src/modules/messaging/message-import-manager/services/messaging-messages-import.service'; import { MessagingProcessFolderActionsService } from 'src/modules/messaging/message-import-manager/services/messaging-process-folder-actions.service'; import { MessagingProcessGroupEmailActionsService } from 'src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { getRepositoryToken } from '@nestjs/typeorm'; describe('MessagingMessageListFetchService', () => { let messagingMessageListFetchService: MessagingMessageListFetchService; @@ -34,8 +31,8 @@ describe('MessagingMessageListFetchService', () => { let globalWorkspaceOrmManager: GlobalWorkspaceOrmManager; let messagingCursorService: MessagingCursorService; - let mockMicrosoftMessageChannel: MessageChannelWorkspaceEntity; - let mockGoogleMessageChannel: MessageChannelWorkspaceEntity; + let mockMicrosoftMessageChannel: MessageChannelEntity; + let mockGoogleMessageChannel: MessageChannelEntity; const workspaceId = 'workspace-id'; @@ -48,7 +45,7 @@ describe('MessagingMessageListFetchService', () => { handle: 'test@microsoft.com', accessToken: 'old-microsoft-access-token', refreshToken: 'microsoft-refresh-token', - handleAliases: '', + handleAliases: [] as string[], }, messageFolders: [ { @@ -56,10 +53,10 @@ describe('MessagingMessageListFetchService', () => { name: 'inbox', syncCursor: 'inbox-sync-cursor', messageChannelId: 'microsoft-message-channel-id', - } as MessageFolderWorkspaceEntity, + }, ], messageFolderImportPolicy: MessageFolderImportPolicy.SELECTED_FOLDERS, - } as MessageChannelWorkspaceEntity; + } as unknown as MessageChannelEntity; mockGoogleMessageChannel = { id: 'google-message-channel-id', @@ -69,12 +66,12 @@ describe('MessagingMessageListFetchService', () => { handle: 'test@gmail.com', accessToken: 'old-google-access-token', refreshToken: 'google-refresh-token', - handleAliases: '', + handleAliases: [] as string[], }, syncCursor: 'google-sync-cursor', messageFolders: [], messageFolderImportPolicy: MessageFolderImportPolicy.SELECTED_FOLDERS, - } as unknown as MessageChannelWorkspaceEntity; + } as unknown as MessageChannelEntity; }); beforeEach(async () => { @@ -231,7 +228,7 @@ describe('MessagingMessageListFetchService', () => { }, }, { - provide: MessageChannelDataAccessService, + provide: getRepositoryToken(MessageChannelEntity), useValue: { findOne: jest.fn().mockResolvedValue(undefined), }, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-messages-import.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-messages-import.service.spec.ts index 3f02c404e8..02ab4e1889 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-messages-import.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/__tests__/messaging-messages-import.service.spec.ts @@ -1,7 +1,11 @@ import { Logger, type Provider } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; -import { ConnectedAccountProvider } from 'twenty-shared/types'; +import { + ConnectedAccountProvider, + MessageChannelSyncStage, + MessageFolderImportPolicy, +} from 'twenty-shared/types'; import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; @@ -9,21 +13,19 @@ import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspac import { BlocklistRepository } from 'src/modules/blocklist/repositories/blocklist.repository'; import { EmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/services/email-alias-manager.service'; import { ConnectedAccountRefreshTokensService } from 'src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; -import { - MessageChannelSyncStage, - MessageFolderImportPolicy, - type MessageChannelWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-users-messages-get-batch-size.constant'; import { MessagingAccountAuthenticationService } from 'src/modules/messaging/message-import-manager/services/messaging-account-authentication.service'; import { MessagingGetMessagesService } from 'src/modules/messaging/message-import-manager/services/messaging-get-messages.service'; import { MessageImportExceptionHandlerService } from 'src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service'; import { MessagingMessagesImportService } from 'src/modules/messaging/message-import-manager/services/messaging-messages-import.service'; import { MessagingSaveMessagesAndEnqueueContactCreationService } from 'src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; +import { getRepositoryToken } from '@nestjs/typeorm'; + import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; describe('MessagingMessagesImportService', () => { let service: MessagingMessagesImportService; @@ -35,7 +37,7 @@ describe('MessagingMessagesImportService', () => { const workspaceId = 'workspace-id'; let mockMessageChannel: Pick< - MessageChannelWorkspaceEntity, + MessageChannelEntity, | 'id' | 'syncStage' | 'connectedAccountId' @@ -43,7 +45,7 @@ describe('MessagingMessagesImportService', () => { | 'messageFolders' | 'messageFolderImportPolicy' >; - let mockConnectedAccount: ConnectedAccountWorkspaceEntity; + let mockConnectedAccount: ConnectedAccountEntity; let providersBase: Provider[]; beforeEach(async () => { @@ -53,9 +55,9 @@ describe('MessagingMessagesImportService', () => { handle: 'test@gmail.com', refreshToken: 'refresh-token', accessToken: 'old-access-token', - accountOwnerId: 'account-owner-id', - handleAliases: 'alias1@gmail.com,alias2@gmail.com', - } as ConnectedAccountWorkspaceEntity; + userWorkspaceId: 'user-workspace-id', + handleAliases: ['alias1@gmail.com', 'alias2@gmail.com'], + } as ConnectedAccountEntity; mockMessageChannel = { id: 'message-channel-id', @@ -114,7 +116,9 @@ describe('MessagingMessagesImportService', () => { { provide: EmailAliasManagerService, useValue: { - refreshHandleAliases: jest.fn().mockResolvedValue(undefined), + refreshHandleAliases: jest + .fn() + .mockResolvedValue(['alias1@gmail.com', 'alias2@gmail.com']), }, }, { @@ -122,6 +126,10 @@ describe('MessagingMessagesImportService', () => { useValue: { getRepository: jest.fn().mockResolvedValue({ update: jest.fn().mockResolvedValue(undefined), + findOne: jest.fn().mockResolvedValue({ + id: 'workspace-member-id', + userId: 'user-id', + }), }), executeInWorkspaceContext: jest .fn() @@ -129,7 +137,7 @@ describe('MessagingMessagesImportService', () => { }, }, { - provide: MessageChannelDataAccessService, + provide: getRepositoryToken(MessageChannelEntity), useValue: { update: jest.fn().mockResolvedValue(undefined), }, @@ -169,6 +177,12 @@ describe('MessagingMessagesImportService', () => { provide: MessagingAccountAuthenticationService, useClass: MessagingAccountAuthenticationService, }, + { + provide: getRepositoryToken(UserWorkspaceEntity), + useValue: { + findOne: jest.fn().mockResolvedValue({ userId: 'user-id' }), + }, + }, ]; const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -219,7 +233,7 @@ describe('MessagingMessagesImportService', () => { expect( service.processMessageBatchImport( - mockMessageChannel as MessageChannelWorkspaceEntity, + mockMessageChannel as MessageChannelEntity, mockConnectedAccount, workspaceId, ), @@ -228,7 +242,7 @@ describe('MessagingMessagesImportService', () => { it('should process message batch import successfully', async () => { await service.processMessageBatchImport( - mockMessageChannel as MessageChannelWorkspaceEntity, + mockMessageChannel as MessageChannelEntity, mockConnectedAccount, workspaceId, ); @@ -312,7 +326,7 @@ describe('MessagingMessagesImportService', () => { ); await service.processMessageBatchImport( - mockMessageChannel as MessageChannelWorkspaceEntity, + mockMessageChannel as MessageChannelEntity, mockConnectedAccount, workspaceId, ); diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-account-authentication.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-account-authentication.service.ts index f58f0053de..28031e0dd5 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-account-authentication.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-account-authentication.service.ts @@ -8,7 +8,7 @@ import { ConnectedAccountRefreshTokensService, type ConnectedAccountTokens, } from 'src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { MessageImportDriverException, MessageImportDriverExceptionCode, @@ -16,7 +16,7 @@ import { import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service'; interface ValidateAndRefreshConnectedAccountAuthenticationParams { - connectedAccount: ConnectedAccountWorkspaceEntity; + connectedAccount: ConnectedAccountEntity; workspaceId: string; messageChannelId: string; } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-cursor.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-cursor.service.ts index b085d20915..bc60700ab6 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-cursor.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-cursor.service.ts @@ -1,21 +1,25 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; -import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service'; +import { Repository } from 'typeorm'; + +import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; @Injectable() export class MessagingCursorService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, - private readonly messageFolderDataAccessService: MessageFolderDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, + @InjectRepository(MessageFolderEntity) + private readonly messageFolderRepository: Repository, ) {} public async updateCursor( - messageChannel: MessageChannelWorkspaceEntity, + messageChannel: MessageChannelEntity, nextSyncCursor: string, workspaceId: string, folderId?: string, @@ -24,11 +28,8 @@ export class MessagingCursorService { await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { if (!folderId) { - await this.messageChannelDataAccessService.update( - workspaceId, - { - id: messageChannel.id, - }, + await this.messageChannelRepository.update( + { id: messageChannel.id, workspaceId }, { throttleFailureCount: 0, throttleRetryAfter: null, @@ -41,20 +42,14 @@ export class MessagingCursorService { }, ); } else { - await this.messageFolderDataAccessService.update( - workspaceId, - { - id: folderId, - }, + await this.messageFolderRepository.update( + { id: folderId, workspaceId }, { syncCursor: nextSyncCursor, }, ); - await this.messageChannelDataAccessService.update( - workspaceId, - { - id: messageChannel.id, - }, + await this.messageChannelRepository.update( + { id: messageChannel.id, workspaceId }, { throttleFailureCount: 0, throttleRetryAfter: null, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-delete-folder-messages.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-delete-folder-messages.service.ts index cc2dcbae2b..920eaec05a 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-delete-folder-messages.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-delete-folder-messages.service.ts @@ -3,12 +3,12 @@ import { Injectable, Logger } from '@nestjs/common'; import { isDefined } from 'twenty-shared/utils'; import { In } from 'typeorm'; +import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { type MessageChannelMessageAssociationMessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association-message-folder.workspace-entity'; import { type MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity'; -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service'; const BATCH_SIZE = 200; @@ -26,8 +26,8 @@ export class MessagingDeleteFolderMessagesService { async deleteFolderMessages( workspaceId: string, - messageChannel: MessageChannelWorkspaceEntity, - messageFolder: MessageFolderWorkspaceEntity, + messageChannel: MessageChannelEntity, + messageFolder: MessageFolderEntity, ): Promise { this.logger.log( `WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id}, FolderId: ${messageFolder.id} - Deleting messages from folder: ${messageFolder.name}`, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-get-message-list.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-get-message-list.service.ts index 6af7a32847..d2b4e1c331 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-get-message-list.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-get-message-list.service.ts @@ -2,9 +2,9 @@ import { Injectable } from '@nestjs/common'; import { ConnectedAccountProvider } from 'twenty-shared/types'; +import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { type MessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface'; -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessageImportDriverException, MessageImportDriverExceptionCode, @@ -23,7 +23,7 @@ export class MessagingGetMessageListService { ) {} public async getMessageLists( - messageChannel: MessageChannelWorkspaceEntity, + messageChannel: MessageChannelEntity, messageFolders: MessageFolder[], ): Promise { switch (messageChannel.connectedAccount.provider) { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-get-messages.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-get-messages.service.ts index 4b4ec15ee5..219e5df48a 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-get-messages.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-get-messages.service.ts @@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common'; import { ConnectedAccountProvider } from 'twenty-shared/types'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { MessageImportDriverException, MessageImportDriverExceptionCode, @@ -26,18 +26,18 @@ export class MessagingGetMessagesService { public async getMessages( messageIds: string[], connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, | 'provider' | 'accessToken' | 'refreshToken' | 'id' | 'handle' | 'handleAliases' - | 'accountOwnerId' + | 'userWorkspaceId' | 'connectionParameters' >, messageChannel: Pick< - MessageChannelWorkspaceEntity, + MessageChannelEntity, 'messageFolders' | 'messageFolderImportPolicy' >, ): Promise { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts index 3775afe110..0c0ec7354f 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts @@ -1,24 +1,23 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; +import { MessageChannelSyncStatus } from 'twenty-shared/types'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { type TwentyORMException, TwentyORMExceptionCode, } from 'src/engine/twenty-orm/exceptions/twenty-orm.exception'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; -import { - MessageChannelSyncStatus, - type MessageChannelWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MESSAGING_THROTTLE_MAX_ATTEMPTS } from 'src/modules/messaging/message-import-manager/constants/messaging-throttle-max-attempts'; import { MessageImportDriverException, MessageImportDriverExceptionCode, } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception'; import { MessageNetworkExceptionCode } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-network.exception'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; export enum MessageImportSyncStep { MESSAGE_LIST_FETCH = 'MESSAGE_LIST_FETCH', @@ -29,7 +28,8 @@ export enum MessageImportSyncStep { @Injectable() export class MessageImportExceptionHandlerService { constructor( - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService, private readonly exceptionHandlerService: ExceptionHandlerService, ) {} @@ -37,10 +37,7 @@ export class MessageImportExceptionHandlerService { public async handleDriverException( exception: MessageImportDriverException | Error | TwentyORMException, syncStep: MessageImportSyncStep, - messageChannel: Pick< - MessageChannelWorkspaceEntity, - 'id' | 'throttleFailureCount' - >, + messageChannel: Pick, workspaceId: string, ): Promise { if (exception instanceof MessageImportDriverException) { @@ -105,7 +102,7 @@ export class MessageImportExceptionHandlerService { } private async handleSyncCursorErrorException( - messageChannel: Pick, + messageChannel: Pick, workspaceId: string, ): Promise { await this.messageChannelSyncStatusService.resetAndMarkAsMessagesListFetchPending( @@ -116,10 +113,7 @@ export class MessageImportExceptionHandlerService { private async handleTemporaryException( syncStep: MessageImportSyncStep, - messageChannel: Pick< - MessageChannelWorkspaceEntity, - 'id' | 'throttleFailureCount' - >, + messageChannel: Pick, workspaceId: string, exception: { message: string }, ): Promise { @@ -151,9 +145,8 @@ export class MessageImportExceptionHandlerService { return; } - await this.messageChannelDataAccessService.increment( - workspaceId, - { id: messageChannel.id }, + await this.messageChannelRepository.increment( + { id: messageChannel.id, workspaceId }, 'throttleFailureCount', 1, ); @@ -163,9 +156,8 @@ export class MessageImportExceptionHandlerService { ? exception.throttleRetryAfter : undefined; - await this.messageChannelDataAccessService.update( - workspaceId, - { id: messageChannel.id }, + await this.messageChannelRepository.update( + { id: messageChannel.id, workspaceId }, { throttleRetryAfter: isDefined(throttleRetryAfter) ? throttleRetryAfter.toISOString() @@ -197,7 +189,7 @@ export class MessageImportExceptionHandlerService { } private async handleInsufficientPermissionsException( - messageChannel: Pick, + messageChannel: Pick, workspaceId: string, ): Promise { await this.messageChannelSyncStatusService.markAsFailed( @@ -209,7 +201,7 @@ export class MessageImportExceptionHandlerService { private async handleUnknownException( exception: Error, - messageChannel: Pick, + messageChannel: Pick, workspaceId: string, ): Promise { this.exceptionHandlerService.captureExceptions([exception], { @@ -223,7 +215,7 @@ export class MessageImportExceptionHandlerService { } private async handlePermanentException( - messageChannel: Pick, + messageChannel: Pick, workspaceId: string, ): Promise { await this.messageChannelSyncStatusService.markAsFailed( @@ -235,7 +227,7 @@ export class MessageImportExceptionHandlerService { private async handleNotFoundException( syncStep: MessageImportSyncStep, - messageChannel: Pick, + messageChannel: Pick, workspaceId: string, ): Promise { if (syncStep === MessageImportSyncStep.MESSAGE_LIST_FETCH) { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message-list-fetch.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message-list-fetch.service.ts index f351f76166..0ac53bf32f 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message-list-fetch.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-message-list-fetch.service.ts @@ -1,24 +1,23 @@ import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { isNonEmptyString } from '@sniptt/guards'; import chunk from 'lodash.chunk'; import { isDefined } from 'twenty-shared/utils'; -import { In, MoreThanOrEqual } from 'typeorm'; +import { In, MoreThanOrEqual, Repository } from 'typeorm'; +import { + MessageChannelPendingGroupEmailsAction, + MessageChannelSyncStage, + MessageFolderPendingSyncAction, +} from 'twenty-shared/types'; import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator'; import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; import { type MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity'; -import { - MessageChannelPendingGroupEmailsAction, - MessageChannelSyncStage, - MessageChannelWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service'; import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service'; import { MessagingAccountAuthenticationService } from 'src/modules/messaging/message-import-manager/services/messaging-account-authentication.service'; @@ -31,6 +30,7 @@ import { import { MessagingMessagesImportService } from 'src/modules/messaging/message-import-manager/services/messaging-messages-import.service'; import { MessagingProcessFolderActionsService } from 'src/modules/messaging/message-import-manager/services/messaging-process-folder-actions.service'; import { MessagingProcessGroupEmailActionsService } from 'src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; const ONE_WEEK_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000; @@ -42,7 +42,8 @@ export class MessagingMessageListFetchService { private readonly cacheStorage: CacheStorageService, private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, private readonly messagingGetMessageListService: MessagingGetMessageListService, private readonly messageImportErrorHandlerService: MessageImportExceptionHandlerService, private readonly messagingMessageCleanerService: MessagingMessageCleanerService, @@ -55,7 +56,7 @@ export class MessagingMessageListFetchService { ) {} public async processMessageListFetch( - messageChannel: MessageChannelWorkspaceEntity, + messageChannel: MessageChannelEntity, workspaceId: string, ) { const authContext = buildSystemAuthContext(workspaceId); @@ -82,11 +83,12 @@ export class MessagingMessageListFetchService { const freshMessageChannel = pendingGroupEmailActionsProcessed || pendingFolderActionsProcessed - ? await this.messageChannelDataAccessService.findOne(workspaceId, { + ? await this.messageChannelRepository.findOne({ where: { id: messageChannel.id, + workspaceId, }, - relations: ['connectedAccount', 'messageFolders'], + relations: { connectedAccount: true, messageFolders: true }, }) : messageChannel; @@ -297,7 +299,7 @@ export class MessagingMessageListFetchService { } private async processPendingGroupEmailActions( - messageChannel: MessageChannelWorkspaceEntity, + messageChannel: MessageChannelEntity, workspaceId: string, ): Promise { const hasPendingGroupEmailAction = @@ -323,7 +325,7 @@ export class MessagingMessageListFetchService { } private async processPendingFolderActions( - messageChannel: MessageChannelWorkspaceEntity, + messageChannel: MessageChannelEntity, workspaceId: string, ): Promise { const foldersWithPendingActions = messageChannel.messageFolders.filter( @@ -350,7 +352,7 @@ export class MessagingMessageListFetchService { } private async computeFullSyncMessageChannelMessageAssociationsToDelete( - messageChannel: Pick, + messageChannel: Pick, messageExternalIds: string[], workspaceId: string, ) { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts index dc36a75309..281ecb33a6 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts @@ -1,23 +1,23 @@ import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; +import { MessageChannelSyncStage } from 'twenty-shared/types'; import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator'; import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { BlocklistRepository } from 'src/modules/blocklist/repositories/blocklist.repository'; import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity'; import { EmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/services/email-alias-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; -import { - MessageChannelSyncStage, - type MessageChannelWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessageImportDriverException, MessageImportDriverExceptionCode, @@ -32,6 +32,7 @@ import { import { MessagingSaveMessagesAndEnqueueContactCreationService } from 'src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service'; import { filterEmails } from 'src/modules/messaging/message-import-manager/utils/filter-emails.util'; import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; @Injectable() export class MessagingMessagesImportService { @@ -47,15 +48,18 @@ export class MessagingMessagesImportService { private readonly blocklistRepository: BlocklistRepository, private readonly emailAliasManagerService: EmailAliasManagerService, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, private readonly messagingGetMessagesService: MessagingGetMessagesService, private readonly messageImportErrorHandlerService: MessageImportExceptionHandlerService, private readonly messagingAccountAuthenticationService: MessagingAccountAuthenticationService, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, ) {} async processMessageBatchImport( - messageChannel: MessageChannelWorkspaceEntity, - connectedAccount: ConnectedAccountWorkspaceEntity, + messageChannel: MessageChannelEntity, + connectedAccount: ConnectedAccountEntity, workspaceId: string, ) { let messageIdsToFetch: string[] = []; @@ -98,10 +102,13 @@ export class MessagingMessagesImportService { refreshToken, }; - await this.emailAliasManagerService.refreshHandleAliases( - connectedAccountWithFreshTokens, - workspaceId, - ); + const refreshedHandleAliases = + await this.emailAliasManagerService.refreshHandleAliases( + connectedAccountWithFreshTokens, + workspaceId, + ); + + connectedAccountWithFreshTokens.handleAliases = refreshedHandleAliases; messageIdsToFetch = await this.cacheStorage.setPop( `messages-to-import:${workspaceId}:${messageChannel.id}`, @@ -145,10 +152,30 @@ export class MessagingMessagesImportService { .filter(isDefined); } - const blocklist = await this.blocklistRepository.getByWorkspaceMemberId( - connectedAccountWithFreshTokens.accountOwnerId, - workspaceId, - ); + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { + id: connectedAccountWithFreshTokens.userWorkspaceId, + }, + }); + + const workspaceMemberRepository = + await this.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workspaceMember', + ); + + const workspaceMember = userWorkspace + ? await workspaceMemberRepository.findOne({ + where: { userId: userWorkspace.userId }, + }) + : null; + + const blocklist = workspaceMember + ? await this.blocklistRepository.getByWorkspaceMemberId( + workspaceMember.id, + workspaceId, + ) + : []; if (!isDefined(messageChannel.handle)) { throw new MessageImportDriverException( @@ -166,7 +193,7 @@ export class MessagingMessagesImportService { const messagesToSave = filterEmails( messageChannel.handle, - [...connectedAccountWithFreshTokens.handleAliases.split(',')], + [...connectedAccountWithFreshTokens.handleAliases], allMessages, blocklist .map((blocklistItem) => blocklistItem.handle) @@ -198,11 +225,8 @@ export class MessagingMessagesImportService { ); } - await this.messageChannelDataAccessService.update( - workspaceId, - { - id: messageChannel.id, - }, + await this.messageChannelRepository.update( + { id: messageChannel.id, workspaceId }, { throttleFailureCount: 0, throttleRetryAfter: null, @@ -239,7 +263,7 @@ export class MessagingMessagesImportService { } private async trackMessageImportCompleted( - messageChannel: MessageChannelWorkspaceEntity, + messageChannel: MessageChannelEntity, workspaceId: string, ) { await this.messagingMonitoringService.track({ diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-folder-actions.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-folder-actions.service.ts index 10b3891a2e..3f5c7f512b 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-folder-actions.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-folder-actions.service.ts @@ -1,16 +1,14 @@ import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; -import { In } from 'typeorm'; +import { In, Repository } from 'typeorm'; -import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service'; +import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { - MessageFolderPendingSyncAction, - MessageFolderWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity'; +import { MessageFolderPendingSyncAction } from 'twenty-shared/types'; import { MessagingDeleteFolderMessagesService } from 'src/modules/messaging/message-import-manager/services/messaging-delete-folder-messages.service'; @Injectable() @@ -21,13 +19,14 @@ export class MessagingProcessFolderActionsService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageFolderDataAccessService: MessageFolderDataAccessService, + @InjectRepository(MessageFolderEntity) + private readonly messageFolderRepository: Repository, private readonly messagingDeleteFolderMessagesService: MessagingDeleteFolderMessagesService, ) {} async processFolderActions( - messageChannel: MessageChannelWorkspaceEntity, - messageFolders: MessageFolderWorkspaceEntity[], + messageChannel: MessageChannelEntity, + messageFolders: MessageFolderEntity[], workspaceId: string, ): Promise { const foldersWithPendingActions = messageFolders.filter( @@ -93,9 +92,8 @@ export class MessagingProcessFolderActionsService { await this.globalWorkspaceOrmManager.executeInWorkspaceContext( async () => { if (processedFolderIds.length > 0) { - await this.messageFolderDataAccessService.update( - workspaceId, - { id: In(processedFolderIds) }, + await this.messageFolderRepository.update( + { id: In(processedFolderIds), workspaceId }, { pendingSyncAction: MessageFolderPendingSyncAction.NONE }, ); @@ -105,8 +103,9 @@ export class MessagingProcessFolderActionsService { } if (folderIdsToDelete.length > 0) { - await this.messageFolderDataAccessService.delete(workspaceId, { + await this.messageFolderRepository.delete({ id: In(folderIdsToDelete), + workspaceId, }); this.logger.log( diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service.ts index 9c3a68c817..fa8b5ab557 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service.ts @@ -1,17 +1,15 @@ import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; -import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service'; -import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; +import { MessageChannelPendingGroupEmailsAction } from 'twenty-shared/types'; +import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; -import { - MessageChannelPendingGroupEmailsAction, - MessageChannelWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessagingDeleteGroupEmailMessagesService } from 'src/modules/messaging/message-import-manager/services/messaging-delete-group-email-messages.service'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; @Injectable() export class MessagingProcessGroupEmailActionsService { @@ -21,25 +19,22 @@ export class MessagingProcessGroupEmailActionsService { constructor( private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, - private readonly messageFolderDataAccessService: MessageFolderDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, + @InjectRepository(MessageFolderEntity) + private readonly messageFolderRepository: Repository, private readonly messagingDeleteGroupEmailMessagesService: MessagingDeleteGroupEmailMessagesService, ) {} async markMessageChannelAsPendingGroupEmailsAction( - messageChannel: MessageChannelWorkspaceEntity, + messageChannel: MessageChannelEntity, workspaceId: string, pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction, ): Promise { - const authContext = buildSystemAuthContext(workspaceId); - - await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - await this.messageChannelDataAccessService.update( - workspaceId, - { id: messageChannel.id }, - { pendingGroupEmailsAction }, - ); - }, authContext); + await this.messageChannelRepository.update( + { id: messageChannel.id, workspaceId }, + { pendingGroupEmailsAction }, + ); this.logger.debug( `WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Marked message channel as pending group emails action: ${pendingGroupEmailsAction}`, @@ -47,7 +42,7 @@ export class MessagingProcessGroupEmailActionsService { } async processGroupEmailActions( - messageChannel: MessageChannelWorkspaceEntity, + messageChannel: MessageChannelEntity, workspaceId: string, ): Promise { const { pendingGroupEmailsAction } = messageChannel; @@ -65,70 +60,56 @@ export class MessagingProcessGroupEmailActionsService { const authContext = buildSystemAuthContext(workspaceId); - await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { - const workspaceDataSource = - await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource(); - - await workspaceDataSource?.transaction( - async (transactionManager: WorkspaceEntityManager) => { - try { - switch (pendingGroupEmailsAction) { - case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_DELETION: - await this.handleGroupEmailsDeletion( - workspaceId, - messageChannel.id, - transactionManager, - ); - break; - case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_IMPORT: - await this.handleGroupEmailsImport( - workspaceId, - messageChannel.id, - transactionManager, - ); - break; - } - - await this.messageChannelDataAccessService.update( - workspaceId, - { id: messageChannel.id }, - { - pendingGroupEmailsAction: - MessageChannelPendingGroupEmailsAction.NONE, - }, - transactionManager, - ); - - this.logger.debug( - `WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Reset pendingGroupEmailsAction to NONE`, - ); - } catch (error) { - this.logger.error( - `WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Error processing group email action: ${error.message}`, - error.stack, - ); - throw error; + try { + await this.globalWorkspaceOrmManager.executeInWorkspaceContext( + async () => { + switch (pendingGroupEmailsAction) { + case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_DELETION: + await this.handleGroupEmailsDeletion( + workspaceId, + messageChannel.id, + ); + break; + case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_IMPORT: + await this.handleGroupEmailsImport( + workspaceId, + messageChannel.id, + ); + break; } }, + authContext, ); - }, authContext); + + await this.messageChannelRepository.update( + { id: messageChannel.id, workspaceId }, + { + pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction.NONE, + }, + ); + + this.logger.debug( + `WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Reset pendingGroupEmailsAction to NONE`, + ); + } catch (error) { + this.logger.error( + `WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Error processing group email action: ${error.message}`, + (error as Error).stack, + ); + throw error; + } } private async handleGroupEmailsDeletion( workspaceId: string, messageChannelId: string, - transactionManager: WorkspaceEntityManager, ): Promise { await this.messagingDeleteGroupEmailMessagesService.deleteGroupEmailMessages( workspaceId, messageChannelId, ); - await this.resetCursors({ - workspaceId, - messageChannelId, - transactionManager, - }); + await this.resetCursors(workspaceId, messageChannelId); this.logger.debug( `WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannelId} - Completed GROUP_EMAILS_DELETION action`, @@ -138,42 +119,23 @@ export class MessagingProcessGroupEmailActionsService { private async handleGroupEmailsImport( workspaceId: string, messageChannelId: string, - transactionManager: WorkspaceEntityManager, ): Promise { - await this.resetCursors({ - workspaceId, - messageChannelId, - transactionManager, - }); + await this.resetCursors(workspaceId, messageChannelId); this.logger.debug( `WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannelId} - Completed GROUP_EMAILS_IMPORT action`, ); } - private async resetCursors({ - workspaceId, - messageChannelId, - transactionManager, - }: { - workspaceId: string; - messageChannelId: string; - transactionManager: WorkspaceEntityManager; - }) { - await this.messageChannelDataAccessService.update( - workspaceId, - { id: messageChannelId }, - { - syncCursor: '', - }, - transactionManager, + private async resetCursors(workspaceId: string, messageChannelId: string) { + await this.messageChannelRepository.update( + { id: messageChannelId, workspaceId }, + { syncCursor: '' }, ); - await this.messageFolderDataAccessService.update( - workspaceId, - { messageChannelId }, + await this.messageFolderRepository.update( + { messageChannelId, workspaceId }, { syncCursor: '' }, - transactionManager, ); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts index 609d9e6684..46bffe59aa 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.spec.ts @@ -1,20 +1,21 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; -import { FieldActorSource, MessageParticipantRole } from 'twenty-shared/types'; +import { + FieldActorSource, + MessageChannelContactAutoCreationPolicy, + MessageParticipantRole, +} from 'twenty-shared/types'; +import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-queue-token.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { CreateCompanyAndContactJob } from 'src/modules/contact-creation-manager/jobs/create-company-and-contact.job'; import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum'; -import { - MessageChannelContactAutoCreationPolicy, - type MessageChannelWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessagingMessageFolderAssociationService } from 'src/modules/messaging/message-import-manager/services/messaging-message-folder-association.service'; import { MessagingMessageService } from 'src/modules/messaging/message-import-manager/services/messaging-message.service'; import { MessagingSaveMessagesAndEnqueueContactCreationService } from 'src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service'; @@ -31,20 +32,20 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => { const workspaceId = 'workspace-id'; - const mockConnectedAccount: ConnectedAccountWorkspaceEntity = { + const mockConnectedAccount: ConnectedAccountEntity = { id: 'connected-account-id', handle: 'test@example.com', - handleAliases: 'alias1@example.com,alias2@example.com', - } as ConnectedAccountWorkspaceEntity; + handleAliases: ['alias1@example.com', 'alias2@example.com'], + } as ConnectedAccountEntity; - const mockMessageChannel: MessageChannelWorkspaceEntity = { + const mockMessageChannel: MessageChannelEntity = { id: 'message-channel-id', isContactAutoCreationEnabled: true, contactAutoCreationPolicy: MessageChannelContactAutoCreationPolicy.SENT_AND_RECEIVED, excludeNonProfessionalEmails: true, excludeGroupEmails: true, - } as MessageChannelWorkspaceEntity; + } as MessageChannelEntity; const mockMessages: MessageWithParticipants[] = [ { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts index 9a684ee002..5fbf7e4e22 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts @@ -1,23 +1,24 @@ import { Injectable } from '@nestjs/common'; -import { FieldActorSource, MessageParticipantRole } from 'twenty-shared/types'; +import { + FieldActorSource, + MessageChannelContactAutoCreationPolicy, + MessageParticipantRole, +} from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { CreateCompanyAndContactJob, type CreateCompanyAndContactJobData, } from 'src/modules/contact-creation-manager/jobs/create-company-and-contact.job'; -import { - MessageChannelContactAutoCreationPolicy, - type MessageChannelWorkspaceEntity, -} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { type Participant, type ParticipantWithMessageId, @@ -44,11 +45,11 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService { async saveMessagesAndEnqueueContactCreation( messagesToSave: MessageWithParticipants[], - messageChannel: MessageChannelWorkspaceEntity, - connectedAccount: ConnectedAccountWorkspaceEntity, + messageChannel: MessageChannelEntity, + connectedAccount: ConnectedAccountEntity, workspaceId: string, ) { - const handleAliases = connectedAccount.handleAliases?.split(',') || []; + const handleAliases = connectedAccount.handleAliases || []; const authContext = buildSystemAuthContext(workspaceId); const participantsWithMessageId = diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/types/get-message-lists-args.type.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/types/get-message-lists-args.type.ts index 3c58548e71..590f4c6562 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/types/get-message-lists-args.type.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/types/get-message-lists-args.type.ts @@ -1,15 +1,15 @@ +import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { type MessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; export type GetMessageListsArgs = { messageChannel: Pick< - MessageChannelWorkspaceEntity, + MessageChannelEntity, 'syncCursor' | 'id' | 'messageFolderImportPolicy' >; connectedAccount: Pick< - ConnectedAccountWorkspaceEntity, + ConnectedAccountEntity, | 'provider' | 'accessToken' | 'refreshToken' diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/gmail/services/gmail-message-outbound.service.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/gmail/services/gmail-message-outbound.service.ts index acc1124e32..6f38b3fe1b 100644 --- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/gmail/services/gmail-message-outbound.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/gmail/services/gmail-message-outbound.service.ts @@ -7,7 +7,7 @@ import { isDefined } from 'twenty-shared/utils'; import { type MessageOutboundDriver } from 'src/modules/messaging/message-outbound-manager/interfaces/message-outbound-driver.interface'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { mimeEncode } from 'src/modules/messaging/message-import-manager/utils/mime-encode.util'; import { type SendMessageInput } from 'src/modules/messaging/message-outbound-manager/types/send-message-input.type'; import { toMailComposerOptions } from 'src/modules/messaging/message-outbound-manager/utils/to-mail-composer-options.util'; @@ -20,7 +20,7 @@ export class GmailMessageOutboundService implements MessageOutboundDriver { async sendMessage( sendMessageInput: SendMessageInput, - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, ): Promise { const { gmailClient, encodedMessage } = await this.composeGmailMessage( connectedAccount, @@ -37,7 +37,7 @@ export class GmailMessageOutboundService implements MessageOutboundDriver { async createDraft( sendMessageInput: SendMessageInput, - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, ): Promise { const { gmailClient, encodedMessage } = await this.composeGmailMessage( connectedAccount, @@ -55,7 +55,7 @@ export class GmailMessageOutboundService implements MessageOutboundDriver { } private async composeGmailMessage( - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, sendMessageInput: SendMessageInput, ): Promise<{ gmailClient: gmail_v1.Gmail; diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/imap/services/imap-smtp-message-outbound.service.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/imap/services/imap-smtp-message-outbound.service.ts index f97cbc9212..58bcdc3bc3 100644 --- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/imap/services/imap-smtp-message-outbound.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/imap/services/imap-smtp-message-outbound.service.ts @@ -1,11 +1,15 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import MailComposer from 'nodemailer/lib/mail-composer'; import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; import { type MessageOutboundDriver } from 'src/modules/messaging/message-outbound-manager/interfaces/message-outbound-driver.interface'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { ImapClientProvider } from 'src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider'; import { ImapFindDraftsFolderService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-find-drafts-folder.service'; import { SmtpClientProvider } from 'src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider'; @@ -18,13 +22,17 @@ export class ImapSmtpMessageOutboundService implements MessageOutboundDriver { private readonly smtpClientProvider: SmtpClientProvider, private readonly imapClientProvider: ImapClientProvider, private readonly imapFindDraftsFolderService: ImapFindDraftsFolderService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, + @InjectRepository(MessageFolderEntity) + private readonly messageFolderRepository: Repository, ) {} async sendMessage( sendMessageInput: SendMessageInput, - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, ): Promise { - const { handle, connectionParameters, messageChannels } = connectedAccount; + const { handle, connectionParameters } = connectedAccount; const smtpClient = await this.smtpClientProvider.getSmtpClient(connectedAccount); @@ -48,13 +56,23 @@ export class ImapSmtpMessageOutboundService implements MessageOutboundDriver { const imapClient = await this.imapClientProvider.getClient(connectedAccount); - const messageChannel = messageChannels.find( - (channel) => channel.handle === handle, - ); + const messageChannel = await this.messageChannelRepository.findOne({ + where: { + connectedAccountId: connectedAccount.id, + handle: handle, + }, + }); - const sentFolder = messageChannel?.messageFolders.find( - (messageFolder) => messageFolder.isSentFolder, - ); + let sentFolder: MessageFolderEntity | null = null; + + if (isDefined(messageChannel)) { + sentFolder = await this.messageFolderRepository.findOne({ + where: { + messageChannelId: messageChannel.id, + isSentFolder: true, + }, + }); + } if (isDefined(sentFolder) && isDefined(sentFolder.name)) { await imapClient.append(sentFolder.name, messageBuffer); @@ -66,7 +84,7 @@ export class ImapSmtpMessageOutboundService implements MessageOutboundDriver { async createDraft( sendMessageInput: SendMessageInput, - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, ): Promise { const { handle, connectionParameters } = connectedAccount; diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/microsoft/services/microsoft-message-outbound.service.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/microsoft/services/microsoft-message-outbound.service.ts index 6cdbc16f8d..c3b4975d25 100644 --- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/microsoft/services/microsoft-message-outbound.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/microsoft/services/microsoft-message-outbound.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common'; import { type MessageOutboundDriver } from 'src/modules/messaging/message-outbound-manager/interfaces/message-outbound-driver.interface'; import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { toMicrosoftRecipients } from 'src/modules/messaging/message-import-manager/utils/to-microsoft-recipients.util'; import { type SendMessageInput } from 'src/modules/messaging/message-outbound-manager/types/send-message-input.type'; import { type Client as MicrosoftGraphClient } from '@microsoft/microsoft-graph-client'; @@ -17,7 +17,7 @@ export class MicrosoftMessageOutboundService implements MessageOutboundDriver { async sendMessage( sendMessageInput: SendMessageInput, - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, ): Promise { const microsoftClient = await this.oAuth2ClientManagerService.getMicrosoftOAuth2Client( @@ -34,7 +34,7 @@ export class MicrosoftMessageOutboundService implements MessageOutboundDriver { async createDraft( sendMessageInput: SendMessageInput, - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, ): Promise { const microsoftClient = await this.oAuth2ClientManagerService.getMicrosoftOAuth2Client( diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/interfaces/message-outbound-driver.interface.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/interfaces/message-outbound-driver.interface.ts index be7c8a8e13..d577b38585 100644 --- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/interfaces/message-outbound-driver.interface.ts +++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/interfaces/message-outbound-driver.interface.ts @@ -1,14 +1,14 @@ -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { type SendMessageInput } from 'src/modules/messaging/message-outbound-manager/types/send-message-input.type'; export type MessageOutboundDriver = { sendMessage( sendMessageInput: SendMessageInput, - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, ): Promise; createDraft( sendMessageInput: SendMessageInput, - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, ): Promise; }; diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/messaging-send-manager.module.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/messaging-send-manager.module.ts index d71272236c..3d004329fa 100644 --- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/messaging-send-manager.module.ts +++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/messaging-send-manager.module.ts @@ -1,5 +1,8 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; +import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-client-manager/oauth2-client-manager.module'; import { MessagingIMAPDriverModule } from 'src/modules/messaging/message-import-manager/drivers/imap/messaging-imap-driver.module'; import { MessagingSmtpDriverModule } from 'src/modules/messaging/message-import-manager/drivers/smtp/messaging-smtp-driver.module'; @@ -13,6 +16,7 @@ import { MessagingMessageOutboundService } from 'src/modules/messaging/message-o OAuth2ClientManagerModule, MessagingIMAPDriverModule, MessagingSmtpDriverModule, + TypeOrmModule.forFeature([MessageChannelEntity, MessageFolderEntity]), ], providers: [ GmailMessageOutboundService, diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/services/messaging-message-outbound.service.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/services/messaging-message-outbound.service.ts index 5cba7dbc7b..e43c5756cc 100644 --- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/services/messaging-message-outbound.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/services/messaging-message-outbound.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common'; import { ConnectedAccountProvider } from 'twenty-shared/types'; import { assertUnreachable } from 'twenty-shared/utils'; -import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { GmailMessageOutboundService } from 'src/modules/messaging/message-outbound-manager/drivers/gmail/services/gmail-message-outbound.service'; import { ImapSmtpMessageOutboundService } from 'src/modules/messaging/message-outbound-manager/drivers/imap/services/imap-smtp-message-outbound.service'; import { MicrosoftMessageOutboundService } from 'src/modules/messaging/message-outbound-manager/drivers/microsoft/services/microsoft-message-outbound.service'; @@ -19,7 +19,7 @@ export class MessagingMessageOutboundService { public async sendMessage( sendMessageInput: SendMessageInput, - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, ): Promise { switch (connectedAccount.provider) { case ConnectedAccountProvider.GOOGLE: @@ -52,7 +52,7 @@ export class MessagingMessageOutboundService { public async createDraft( sendMessageInput: SendMessageInput, - connectedAccount: ConnectedAccountWorkspaceEntity, + connectedAccount: ConnectedAccountEntity, ): Promise { switch (connectedAccount.provider) { case ConnectedAccountProvider.GOOGLE: diff --git a/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job.ts b/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job.ts index 0f86f646cf..9eff83845d 100644 --- a/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job.ts +++ b/packages/twenty-server/src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job.ts @@ -10,7 +10,7 @@ import { Process } from 'src/engine/core-modules/message-queue/decorators/proces import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; -import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service'; @@ -25,7 +25,8 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob { private readonly workspaceRepository: Repository, private readonly messagingMonitoringService: MessagingMonitoringService, private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, - private readonly messageChannelDataAccessService: MessageChannelDataAccessService, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, private readonly exceptionHandlerService: ExceptionHandlerService, ) {} @@ -52,13 +53,10 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob { await this.globalWorkspaceOrmManager.executeInWorkspaceContext( async () => { - const messageChannels = - await this.messageChannelDataAccessService.findMany( - activeWorkspace.id, - { - select: ['id', 'syncStatus', 'connectedAccountId'], - }, - ); + const messageChannels = await this.messageChannelRepository.find({ + select: ['id', 'syncStatus', 'connectedAccountId'], + where: { workspaceId: activeWorkspace.id }, + }); for (const messageChannel of messageChannels) { if (!messageChannel.syncStatus) { diff --git a/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts b/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts index 95d5360c1b..916c0486fb 100644 --- a/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts +++ b/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts @@ -4,7 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { AuditModule } from 'src/engine/core-modules/audit/audit.module'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; -import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module'; import { MessagingMessageChannelSyncStatusMonitoringCronCommand } from 'src/modules/messaging/monitoring/crons/commands/messaging-message-channel-sync-status-monitoring.cron.command'; import { MessagingMessageChannelSyncStatusMonitoringCronJob } from 'src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job'; @@ -13,10 +13,12 @@ import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/ser @Module({ imports: [ AuditModule, - MessageChannelDataAccessModule, MessagingCommonModule, - TypeOrmModule.forFeature([WorkspaceEntity]), - TypeOrmModule.forFeature([DataSourceEntity]), + TypeOrmModule.forFeature([ + WorkspaceEntity, + DataSourceEntity, + MessageChannelEntity, + ]), ], providers: [ MessagingMessageChannelSyncStatusMonitoringCronCommand, diff --git a/packages/twenty-server/test/integration/metadata/suites/calendar-channel/calendar-channel-resolver.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/calendar-channel/calendar-channel-resolver.integration-spec.ts index da7a487e7e..85aafe2398 100644 --- a/packages/twenty-server/test/integration/metadata/suites/calendar-channel/calendar-channel-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/calendar-channel/calendar-channel-resolver.integration-spec.ts @@ -1,28 +1,10 @@ import { gql } from 'graphql-tag'; import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; -import { FeatureFlagKey } from 'twenty-shared/types'; import { CALENDAR_CHANNEL_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/calendar-channel-data-seeds.constant'; import { CONNECTED_ACCOUNT_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/connected-account-data-seeds.constant'; describe('calendarChannelResolver (e2e)', () => { - beforeAll(async () => { - await updateFeatureFlag({ - featureFlag: FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - value: true, - expectToFail: false, - }); - }); - - afterAll(async () => { - await updateFeatureFlag({ - featureFlag: FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - value: false, - expectToFail: false, - }); - }); - describe('myCalendarChannels', () => { it('should return only the current user calendar channels', async () => { const response = await makeMetadataAPIRequest({ @@ -43,14 +25,10 @@ describe('calendarChannelResolver (e2e)', () => { expect(response.body.errors).toBeUndefined(); const channels = response.body.data.myCalendarChannels; - const channelIds = channels.map( - (channel: { id: string }) => channel.id, - ); + const channelIds = channels.map((channel: { id: string }) => channel.id); expect(channelIds).toContain(CALENDAR_CHANNEL_DATA_SEED_IDS.JANE); - expect(channelIds).not.toContain( - CALENDAR_CHANNEL_DATA_SEED_IDS.JONY, - ); + expect(channelIds).not.toContain(CALENDAR_CHANNEL_DATA_SEED_IDS.JONY); }); it('should deny filtering by another user connectedAccountId', async () => { @@ -76,9 +54,7 @@ describe('calendarChannelResolver (e2e)', () => { it('should allow updating own channel', async () => { const response = await makeMetadataAPIRequest({ query: gql` - mutation UpdateCalendarChannel( - $input: UpdateCalendarChannelInput! - ) { + mutation UpdateCalendarChannel($input: UpdateCalendarChannelInput!) { updateCalendarChannel(input: $input) { id visibility @@ -103,9 +79,7 @@ describe('calendarChannelResolver (e2e)', () => { it('should deny updating another user channel', async () => { const response = await makeMetadataAPIRequest({ query: gql` - mutation UpdateCalendarChannel( - $input: UpdateCalendarChannelInput! - ) { + mutation UpdateCalendarChannel($input: UpdateCalendarChannelInput!) { updateCalendarChannel(input: $input) { id } diff --git a/packages/twenty-server/test/integration/metadata/suites/connected-account/connected-account-resolver.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/connected-account/connected-account-resolver.integration-spec.ts index 63541cb01a..836e4e5c08 100644 --- a/packages/twenty-server/test/integration/metadata/suites/connected-account/connected-account-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/connected-account/connected-account-resolver.integration-spec.ts @@ -1,28 +1,10 @@ import { gql } from 'graphql-tag'; import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; import { makeMetadataAPIRequestWithMemberRole } from 'test/integration/metadata/suites/utils/make-metadata-api-request-with-member-role.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; -import { FeatureFlagKey } from 'twenty-shared/types'; import { CONNECTED_ACCOUNT_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/connected-account-data-seeds.constant'; describe('connectedAccountResolver (e2e)', () => { - beforeAll(async () => { - await updateFeatureFlag({ - featureFlag: FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - value: true, - expectToFail: false, - }); - }); - - afterAll(async () => { - await updateFeatureFlag({ - featureFlag: FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - value: false, - expectToFail: false, - }); - }); - describe('myConnectedAccounts', () => { it('should return only the current user connected accounts', async () => { const response = await makeMetadataAPIRequest({ @@ -41,14 +23,10 @@ describe('connectedAccountResolver (e2e)', () => { expect(response.body.errors).toBeUndefined(); const accounts = response.body.data.myConnectedAccounts; - const accountIds = accounts.map( - (account: { id: string }) => account.id, - ); + const accountIds = accounts.map((account: { id: string }) => account.id); expect(accountIds).toContain(CONNECTED_ACCOUNT_DATA_SEED_IDS.JANE); - expect(accountIds).not.toContain( - CONNECTED_ACCOUNT_DATA_SEED_IDS.JONY, - ); + expect(accountIds).not.toContain(CONNECTED_ACCOUNT_DATA_SEED_IDS.JONY); }); it('should not return sensitive fields', async () => { @@ -123,9 +101,7 @@ describe('connectedAccountResolver (e2e)', () => { expect(response.body.errors).toBeUndefined(); const accounts = response.body.data.connectedAccounts; - const accountIds = accounts.map( - (account: { id: string }) => account.id, - ); + const accountIds = accounts.map((account: { id: string }) => account.id); expect(accountIds).toContain(CONNECTED_ACCOUNT_DATA_SEED_IDS.JANE); expect(accountIds).toContain(CONNECTED_ACCOUNT_DATA_SEED_IDS.JONY); diff --git a/packages/twenty-server/test/integration/metadata/suites/message-channel/message-channel-resolver.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/message-channel/message-channel-resolver.integration-spec.ts index 92f51ee5ff..bc2b21bda0 100644 --- a/packages/twenty-server/test/integration/metadata/suites/message-channel/message-channel-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/message-channel/message-channel-resolver.integration-spec.ts @@ -1,28 +1,10 @@ import { gql } from 'graphql-tag'; import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; -import { FeatureFlagKey } from 'twenty-shared/types'; import { CONNECTED_ACCOUNT_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/connected-account-data-seeds.constant'; import { MESSAGE_CHANNEL_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/message-channel-data-seeds.constant'; describe('messageChannelResolver (e2e)', () => { - beforeAll(async () => { - await updateFeatureFlag({ - featureFlag: FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - value: true, - expectToFail: false, - }); - }); - - afterAll(async () => { - await updateFeatureFlag({ - featureFlag: FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - value: false, - expectToFail: false, - }); - }); - describe('myMessageChannels', () => { it('should return only the current user message channels', async () => { const response = await makeMetadataAPIRequest({ @@ -43,9 +25,7 @@ describe('messageChannelResolver (e2e)', () => { expect(response.body.errors).toBeUndefined(); const channels = response.body.data.myMessageChannels; - const channelIds = channels.map( - (channel: { id: string }) => channel.id, - ); + const channelIds = channels.map((channel: { id: string }) => channel.id); expect(channelIds).toContain(MESSAGE_CHANNEL_DATA_SEED_IDS.JANE); expect(channelIds).not.toContain(MESSAGE_CHANNEL_DATA_SEED_IDS.JONY); @@ -70,9 +50,7 @@ describe('messageChannelResolver (e2e)', () => { expect(response.body.errors).toBeUndefined(); const channels = response.body.data.myMessageChannels; - const channelIds = channels.map( - (channel: { id: string }) => channel.id, - ); + const channelIds = channels.map((channel: { id: string }) => channel.id); expect(channelIds).toContain(MESSAGE_CHANNEL_DATA_SEED_IDS.JANE); expect(channelIds).not.toContain(MESSAGE_CHANNEL_DATA_SEED_IDS.JONY); diff --git a/packages/twenty-server/test/integration/metadata/suites/message-folder/message-folder-resolver.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/message-folder/message-folder-resolver.integration-spec.ts index 64b80be752..3ade48154a 100644 --- a/packages/twenty-server/test/integration/metadata/suites/message-folder/message-folder-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/message-folder/message-folder-resolver.integration-spec.ts @@ -1,28 +1,10 @@ import { gql } from 'graphql-tag'; import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; -import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; -import { FeatureFlagKey } from 'twenty-shared/types'; import { MESSAGE_CHANNEL_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/message-channel-data-seeds.constant'; import { MESSAGE_FOLDER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/message-folder-data-seeds.constant'; describe('messageFolderResolver (e2e)', () => { - beforeAll(async () => { - await updateFeatureFlag({ - featureFlag: FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - value: true, - expectToFail: false, - }); - }); - - afterAll(async () => { - await updateFeatureFlag({ - featureFlag: FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED, - value: false, - expectToFail: false, - }); - }); - describe('myMessageFolders', () => { it('should return only the current user message folders', async () => { const response = await makeMetadataAPIRequest({ @@ -46,9 +28,7 @@ describe('messageFolderResolver (e2e)', () => { expect(folderIds).toContain(MESSAGE_FOLDER_DATA_SEED_IDS.JANE_INBOX); expect(folderIds).toContain(MESSAGE_FOLDER_DATA_SEED_IDS.JANE_SENT); - expect(folderIds).not.toContain( - MESSAGE_FOLDER_DATA_SEED_IDS.JONY_INBOX, - ); + expect(folderIds).not.toContain(MESSAGE_FOLDER_DATA_SEED_IDS.JONY_INBOX); }); it('should filter by messageChannelId', async () => { @@ -73,9 +53,7 @@ describe('messageFolderResolver (e2e)', () => { const folderIds = folders.map((folder: { id: string }) => folder.id); expect(folderIds).toContain(MESSAGE_FOLDER_DATA_SEED_IDS.JANE_INBOX); - expect(folderIds).not.toContain( - MESSAGE_FOLDER_DATA_SEED_IDS.JONY_INBOX, - ); + expect(folderIds).not.toContain(MESSAGE_FOLDER_DATA_SEED_IDS.JONY_INBOX); }); it('should deny filtering by another user messageChannelId', async () => { diff --git a/packages/twenty-shared/src/metadata/constants/standard-object.constant.ts b/packages/twenty-shared/src/metadata/constants/standard-object.constant.ts index 724dac460c..a666317bdb 100644 --- a/packages/twenty-shared/src/metadata/constants/standard-object.constant.ts +++ b/packages/twenty-shared/src/metadata/constants/standard-object.constant.ts @@ -216,7 +216,7 @@ export const STANDARD_OBJECTS = { deletedAt: { universalIdentifier: '20202020-c01d-4024-9d24-cffef9effef1', }, - calendarChannel: { + calendarChannelId: { universalIdentifier: '20202020-93ee-4da4-8d58-0282c4a9cb7d', }, calendarEvent: { @@ -253,7 +253,7 @@ export const STANDARD_OBJECTS = { allCalendarChannelEventAssociations: { universalIdentifier: '001893be-c06c-4ba1-9f18-53bd26f0179f', viewFields: { - calendarChannel: { + calendarChannelId: { universalIdentifier: 'e3adffd2-d820-4c89-912c-34908d90057e', }, calendarEvent: { @@ -278,7 +278,7 @@ export const STANDARD_OBJECTS = { }, }, viewFields: { - calendarChannel: { + calendarChannelId: { universalIdentifier: 'cd6c6714-fc1d-4511-a664-ec5e8dfd8692', }, calendarEvent: { @@ -331,9 +331,6 @@ export const STANDARD_OBJECTS = { syncCursor: { universalIdentifier: '20202020-bac2-4852-a5cb-7a7898992b70', }, - calendarChannelEventAssociations: { - universalIdentifier: '20202020-afb0-4a9f-979f-2d5087d71d09', - }, throttleFailureCount: { universalIdentifier: '20202020-525c-4b76-b9bd-0dd57fd11d61', }, @@ -1256,7 +1253,7 @@ export const STANDARD_OBJECTS = { deletedAt: { universalIdentifier: '20202020-b01d-40b4-9db4-8d9eafbacbdc', }, - messageChannel: { + messageChannelId: { universalIdentifier: '20202020-b658-408f-bd46-3bd2d15d7e52', }, message: { @@ -1305,7 +1302,7 @@ export const STANDARD_OBJECTS = { allMessageChannelMessageAssociations: { universalIdentifier: 'a4f465ac-d5cb-4f24-93ac-7a24bafd398e', viewFields: { - messageChannel: { + messageChannelId: { universalIdentifier: 'b86e652b-04ce-4089-9f71-e190eaf5b798', }, message: { @@ -1333,7 +1330,7 @@ export const STANDARD_OBJECTS = { }, }, viewFields: { - messageChannel: { + messageChannelId: { universalIdentifier: '376c7685-9ebe-4c95-b820-424b1c2f264f', }, message: { @@ -1385,7 +1382,7 @@ export const STANDARD_OBJECTS = { messageChannelMessageAssociation: { universalIdentifier: '7411cfa3-4fd9-4b90-a636-940015fd7243', }, - messageFolder: { + messageFolderId: { universalIdentifier: 'b3369d31-3856-4a7a-b007-ee353918127c', }, }, @@ -1407,7 +1404,7 @@ export const STANDARD_OBJECTS = { messageChannelMessageAssociation: { universalIdentifier: '1251e67a-e795-4bc2-a468-6cfc838b6a0a', }, - messageFolder: { + messageFolderId: { universalIdentifier: 'aff2203d-6439-43b8-9cb4-55e8d78bba43', }, createdAt: { @@ -1429,7 +1426,7 @@ export const STANDARD_OBJECTS = { messageChannelMessageAssociation: { universalIdentifier: 'd34ed53e-5156-4a18-a8df-572269496aac', }, - messageFolder: { + messageFolderId: { universalIdentifier: '04f14582-caf9-49ee-81ea-e5d4f977bfe1', }, createdAt: { @@ -1477,12 +1474,6 @@ export const STANDARD_OBJECTS = { excludeGroupEmails: { universalIdentifier: '20202020-45a0-4be4-9164-5820a6a109fb', }, - messageChannelMessageAssociations: { - universalIdentifier: '20202020-49b8-4766-88fd-75f1e21b3d5f', - }, - messageFolders: { - universalIdentifier: '20202020-cc39-4432-9fe8-ec8ab8bbed94', - }, messageFolderImportPolicy: { universalIdentifier: '20202020-cc39-4432-9fe8-ec8ab8bbed95', }, @@ -1611,7 +1602,7 @@ export const STANDARD_OBJECTS = { parentFolderId: { universalIdentifier: '20202020-e45d-49de-a4aa-587bbf9601f3', }, - messageChannel: { + messageChannelId: { universalIdentifier: '20202020-c9f8-43db-a3e7-7f2e8b5d9c1a', }, syncCursor: { @@ -1641,9 +1632,6 @@ export const STANDARD_OBJECTS = { searchVector: { universalIdentifier: '5f2d3937-bafd-4d71-b4cb-b34037efd2e1', }, - messageChannelMessageAssociationMessageFolders: { - universalIdentifier: 'cce5ce1e-31d0-42e6-83cd-90059244a484', - }, }, indexes: { messageChannelIdIndex: { @@ -1657,7 +1645,7 @@ export const STANDARD_OBJECTS = { name: { universalIdentifier: '72b2d56f-5245-4cb6-933c-3d5031de0778', }, - messageChannel: { + messageChannelId: { universalIdentifier: '121abdf0-c89f-4313-b156-e8a3c0cb77ec', }, isSentFolder: { @@ -1682,7 +1670,7 @@ export const STANDARD_OBJECTS = { }, }, viewFields: { - messageChannel: { + messageChannelId: { universalIdentifier: '2fb6ff09-bed5-4b31-af0f-7fa3df5612da', }, isSentFolder: { diff --git a/packages/twenty-shared/src/types/FeatureFlagKey.ts b/packages/twenty-shared/src/types/FeatureFlagKey.ts index 635fbf6fc7..5c61e4c26b 100644 --- a/packages/twenty-shared/src/types/FeatureFlagKey.ts +++ b/packages/twenty-shared/src/types/FeatureFlagKey.ts @@ -13,7 +13,6 @@ export enum FeatureFlagKey { IS_USAGE_ANALYTICS_ENABLED = 'IS_USAGE_ANALYTICS_ENABLED', IS_RICH_TEXT_V1_MIGRATED = 'IS_RICH_TEXT_V1_MIGRATED', IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED = 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED', - IS_CONNECTED_ACCOUNT_MIGRATED = 'IS_CONNECTED_ACCOUNT_MIGRATED', IS_RECORD_TABLE_WIDGET_ENABLED = 'IS_RECORD_TABLE_WIDGET_ENABLED', IS_DATASOURCE_MIGRATED = 'IS_DATASOURCE_MIGRATED', }