From 3d49c17e3436ec021631dfcda38997fa041ac9c6 Mon Sep 17 00:00:00 2001 From: Paul Rastoin <45004772+prastoin@users.noreply.github.com> Date: Wed, 20 May 2026 13:36:58 +0200 Subject: [PATCH] `[CONNECTED_ACCOUNT_BREAKING_CHANGE]` Unify connected account permissions (#20732) # Introduction This PR is a followup of https://github.com/twentyhq/twenty/pull/20673 It aims to unify the authentication/permissions layer with all the connectedAccount interactions across the application ## Deprecate - findAll - findById ## Email sync An user can only sync the message of his own connected account ## Workflow email - Related https://github.com/twentyhq/private-issues/issues/478 - Only reauthorize owned account --- .../src/metadata/generated/schema.graphql | 2 - .../src/metadata/generated/schema.ts | 4 -- .../src/metadata/generated/types.ts | 12 ---- .../src/generated-metadata/graphql.ts | 18 ----- .../twenty-front/src/generated/graphql.ts | 21 ++++++ .../queries/getConnectedAccountById.ts | 31 --------- .../workflowStepConnectedAccountHandle.ts | 13 ++++ .../WorkflowEditActionEmailBase.tsx | 51 ++++++-------- .../workflow-version-step.resolver.ts | 28 +++++++- .../workflow/workflow-api.module.ts | 2 + .../connected-account-metadata.service.ts | 33 +++------ .../dtos/connected-account-handle.dto.ts | 23 +++++++ .../resolvers/connected-account.resolver.ts | 41 ++++------- .../build-public-connected-account.util.ts | 24 +++++++ .../resolvers/message-channel.resolver.ts | 66 +++++++++--------- .../channel-sync/channel-sync.module.ts | 2 + .../channel-sync/channel-sync.resolver.ts | 14 +++- .../connected-accounts.integration-spec.ts | 69 ------------------- ...flow-step-connected-account-handle.util.ts | 27 ++++++++ ...nnected-account-handle.integration-spec.ts | 31 +++++++++ ...ected-account-resolver.integration-spec.ts | 43 ------------ 21 files changed, 258 insertions(+), 297 deletions(-) delete mode 100644 packages/twenty-front/src/modules/settings/accounts/graphql/queries/getConnectedAccountById.ts create mode 100644 packages/twenty-front/src/modules/workflow/graphql/queries/workflowStepConnectedAccountHandle.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/connected-account/dtos/connected-account-handle.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/connected-account/utils/build-public-connected-account.util.ts delete mode 100644 packages/twenty-server/test/integration/graphql/suites/object-generated/connected-accounts.integration-spec.ts create mode 100644 packages/twenty-server/test/integration/graphql/suites/workflow/utils/get-workflow-step-connected-account-handle.util.ts create mode 100644 packages/twenty-server/test/integration/graphql/suites/workflow/workflow-step-connected-account-handle.integration-spec.ts diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index 16fdd1f4b2..d0a8d32195 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -2998,8 +2998,6 @@ type Query { myMessageFolders(messageChannelId: UUID): [MessageFolder!]! myMessageChannels(connectedAccountId: UUID): [MessageChannel!]! myConnectedAccounts: [ConnectedAccountPublicDTO!]! - connectedAccountById(id: UUID!): ConnectedAccountPublicDTO - connectedAccounts: [ConnectedAccountPublicDTO!]! myCalendarChannels(connectedAccountId: UUID): [CalendarChannel!]! webhooks: [Webhook!]! webhook(id: UUID!): Webhook diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index cc41fbc4a1..7e0025ea0e 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -2598,8 +2598,6 @@ export interface Query { myMessageFolders: MessageFolder[] myMessageChannels: MessageChannel[] myConnectedAccounts: ConnectedAccountPublicDTO[] - connectedAccountById?: ConnectedAccountPublicDTO - connectedAccounts: ConnectedAccountPublicDTO[] myCalendarChannels: CalendarChannel[] webhooks: Webhook[] webhook?: Webhook @@ -5631,8 +5629,6 @@ export interface QueryGenqlSelection{ myMessageFolders?: (MessageFolderGenqlSelection & { __args?: {messageChannelId?: (Scalars['UUID'] | null)} }) myMessageChannels?: (MessageChannelGenqlSelection & { __args?: {connectedAccountId?: (Scalars['UUID'] | null)} }) myConnectedAccounts?: ConnectedAccountPublicDTOGenqlSelection - connectedAccountById?: (ConnectedAccountPublicDTOGenqlSelection & { __args: {id: Scalars['UUID']} }) - connectedAccounts?: ConnectedAccountPublicDTOGenqlSelection myCalendarChannels?: (CalendarChannelGenqlSelection & { __args?: {connectedAccountId?: (Scalars['UUID'] | null)} }) webhooks?: WebhookGenqlSelection webhook?: (WebhookGenqlSelection & { __args: {id: Scalars['UUID']} }) diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index 59ccad2179..b426d01484 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -6176,18 +6176,6 @@ export default { "myConnectedAccounts": [ 269 ], - "connectedAccountById": [ - 269, - { - "id": [ - 3, - "UUID!" - ] - } - ], - "connectedAccounts": [ - 269 - ], "myCalendarChannels": [ 300, { diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 64d8f1bfc7..0108c8e7dd 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -4087,8 +4087,6 @@ export type Query = { checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValid; commandMenuItem?: Maybe; commandMenuItems: Array; - connectedAccountById?: Maybe; - connectedAccounts: Array; currentUser: User; currentWorkspace: Workspace; enterpriseCheckoutSession?: Maybe; @@ -4236,11 +4234,6 @@ export type QueryCommandMenuItemArgs = { }; -export type QueryConnectedAccountByIdArgs = { - id: Scalars['UUID']; -}; - - export type QueryEnterpriseCheckoutSessionArgs = { billingInterval?: InputMaybe; }; @@ -6899,15 +6892,6 @@ export type UpdateMessageFoldersMutationVariables = Exact<{ export type UpdateMessageFoldersMutation = { __typename?: 'Mutation', updateMessageFolders: Array<{ __typename?: 'MessageFolder', id: string, isSynced: boolean }> }; -export type PublicConnectionParamsFragment = { __typename?: 'PublicConnectionParametersOutput', host: string, port: number, secure?: boolean | null, username?: string | null }; - -export type ConnectedAccountByIdQueryVariables = Exact<{ - id: Scalars['UUID']; -}>; - - -export type ConnectedAccountByIdQuery = { __typename?: 'Query', connectedAccountById?: { __typename?: 'ConnectedAccountPublicDTO', id: string, handle: string, provider: string, scopes?: Array | null, userWorkspaceId: string, connectionParameters?: { __typename?: 'PublicImapSmtpCaldavConnectionParameters', IMAP?: { __typename?: 'PublicConnectionParametersOutput', host: string, port: number, secure?: boolean | null, username?: string | null } | null, SMTP?: { __typename?: 'PublicConnectionParametersOutput', host: string, port: number, secure?: boolean | null, username?: string | null } | null, CALDAV?: { __typename?: 'PublicConnectionParametersOutput', host: string, port: number, secure?: boolean | null, username?: string | null } | null } | null } | null }; - export type GetConnectedImapSmtpCaldavAccountQueryVariables = Exact<{ id: Scalars['UUID']; }>; @@ -7919,7 +7903,6 @@ export const MarketplaceAppDetailFieldsFragmentDoc = {"kind":"Document","definit export const MarketplaceAppFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarketplaceAppFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarketplaceApp"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"category"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}}]}}]} as unknown as DocumentNode; export const NavigationMenuItemFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const NavigationMenuItemQueryFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; -export const PublicConnectionParamsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PublicConnectionParams"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PublicConnectionParametersOutput"}},"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"}}]}}]} as unknown as DocumentNode; export const ApplicationRegistrationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const BillingPriceLicensedFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingPriceLicensedFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingPriceLicensed"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"unitAmount"}},{"kind":"Field","name":{"kind":"Name","value":"recurringInterval"}},{"kind":"Field","name":{"kind":"Name","value":"priceUsageType"}},{"kind":"Field","name":{"kind":"Name","value":"creditAmount"}}]}}]} as unknown as DocumentNode; export const BillingPriceMeteredFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingPriceMeteredFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingPriceMetered"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"priceUsageType"}},{"kind":"Field","name":{"kind":"Name","value":"recurringInterval"}},{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"tiers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"flatAmount"}},{"kind":"Field","name":{"kind":"Name","value":"unitAmount"}},{"kind":"Field","name":{"kind":"Name","value":"upTo"}}]}}]}}]} as unknown as DocumentNode; @@ -8070,7 +8053,6 @@ 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 ConnectedAccountByIdDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ConnectedAccountById"},"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":"connectedAccountById"},"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":"scopes"}},{"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":"FragmentSpread","name":{"kind":"Name","value":"PublicConnectionParams"}}]}},{"kind":"Field","name":{"kind":"Name","value":"SMTP"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PublicConnectionParams"}}]}},{"kind":"Field","name":{"kind":"Name","value":"CALDAV"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PublicConnectionParams"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PublicConnectionParams"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PublicConnectionParametersOutput"}},"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"}}]}}]} 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":"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":"CALDAV"},"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"}}]}}]}}]}}]}}]} 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":"connectionProviderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"lastCredentialsRefreshedAt"}},{"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":"SMTP"},"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":"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":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index e1fefa6204..0aba679a16 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -28,6 +28,13 @@ export type ComputeStepOutputSchemaInput = { workflowVersionId?: InputMaybe; }; +export type ConnectedAccountHandleDto = { + __typename?: 'ConnectedAccountHandleDTO'; + handle: Scalars['String']; + id: Scalars['UUID']; + provider: Scalars['String']; +}; + export type CreateDraftFromWorkflowVersionInput = { /** Workflow ID */ workflowId: Scalars['UUID']; @@ -252,6 +259,7 @@ export type Query = { getTimelineThreadsFromPersonId: TimelineThreadsWithTotal; isMaintenanceModeBannerDismissed: Scalars['Boolean']; search: SearchResultConnection; + workflowStepConnectedAccountHandle?: Maybe; }; @@ -306,6 +314,11 @@ export type QuerySearchArgs = { searchInput: Scalars['String']; }; + +export type QueryWorkflowStepConnectedAccountHandleArgs = { + connectedAccountId: Scalars['UUID']; +}; + export type RunWorkflowVersion = { __typename?: 'RunWorkflowVersion'; workflowRunId: Scalars['UUID']; @@ -745,6 +758,13 @@ export type UpdateWorkflowVersionStepMutationVariables = Exact<{ export type UpdateWorkflowVersionStepMutation = { __typename?: 'Mutation', updateWorkflowVersionStep: { __typename?: 'WorkflowAction', id: any, name: string, type: WorkflowActionType, settings: any, valid: boolean, nextStepIds?: Array | null, position?: { __typename?: 'WorkflowStepPosition', x: number, y: number } | null } }; +export type WorkflowStepConnectedAccountHandleQueryVariables = Exact<{ + connectedAccountId: Scalars['UUID']; +}>; + + +export type WorkflowStepConnectedAccountHandleQuery = { __typename?: 'Query', workflowStepConnectedAccountHandle?: { __typename?: 'ConnectedAccountHandleDTO', id: any, handle: string, provider: string } | null }; + export type SubmitFormStepMutationVariables = Exact<{ input: SubmitFormStepInput; }>; @@ -794,6 +814,7 @@ export const RunWorkflowVersionDocument = {"kind":"Document","definitions":[{"ki export const StopWorkflowRunDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"StopWorkflowRun"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workflowRunId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stopWorkflowRun"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workflowRunId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workflowRunId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"__typename"}}]}}]}}]} as unknown as DocumentNode; export const UpdateWorkflowRunStepDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkflowRunStep"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateWorkflowRunStepInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateWorkflowRunStep"},"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":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"settings"}},{"kind":"Field","name":{"kind":"Name","value":"valid"}},{"kind":"Field","name":{"kind":"Name","value":"nextStepIds"}},{"kind":"Field","name":{"kind":"Name","value":"position"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"x"}},{"kind":"Field","name":{"kind":"Name","value":"y"}}]}}]}}]}}]} as unknown as DocumentNode; export const UpdateWorkflowVersionStepDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkflowVersionStep"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateWorkflowVersionStepInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateWorkflowVersionStep"},"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":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"settings"}},{"kind":"Field","name":{"kind":"Name","value":"valid"}},{"kind":"Field","name":{"kind":"Name","value":"nextStepIds"}},{"kind":"Field","name":{"kind":"Name","value":"position"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"x"}},{"kind":"Field","name":{"kind":"Name","value":"y"}}]}}]}}]}}]} as unknown as DocumentNode; +export const WorkflowStepConnectedAccountHandleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkflowStepConnectedAccountHandle"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"connectedAccountId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workflowStepConnectedAccountHandle"},"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":"provider"}}]}}]}}]} as unknown as DocumentNode; export const SubmitFormStepDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SubmitFormStep"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SubmitFormStepInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"submitFormStep"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; export const TestHttpRequestDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"TestHttpRequest"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"TestHttpRequestInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"testHttpRequest"},"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":"success"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"result"}},{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusText"}},{"kind":"Field","name":{"kind":"Name","value":"headers"}}]}}]}}]} as unknown as DocumentNode; export const UpdateWorkflowVersionPositionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkflowVersionPositions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateWorkflowVersionPositionsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateWorkflowVersionPositions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getConnectedAccountById.ts b/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getConnectedAccountById.ts deleted file mode 100644 index 4dda458fae..0000000000 --- a/packages/twenty-front/src/modules/settings/accounts/graphql/queries/getConnectedAccountById.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { gql } from '@apollo/client'; - -export const GET_CONNECTED_ACCOUNT_BY_ID = gql` - fragment PublicConnectionParams on PublicConnectionParametersOutput { - host - port - secure - username - } - - query ConnectedAccountById($id: UUID!) { - connectedAccountById(id: $id) { - id - handle - provider - scopes - userWorkspaceId - connectionParameters { - IMAP { - ...PublicConnectionParams - } - SMTP { - ...PublicConnectionParams - } - CALDAV { - ...PublicConnectionParams - } - } - } - } -`; diff --git a/packages/twenty-front/src/modules/workflow/graphql/queries/workflowStepConnectedAccountHandle.ts b/packages/twenty-front/src/modules/workflow/graphql/queries/workflowStepConnectedAccountHandle.ts new file mode 100644 index 0000000000..164fa0e60f --- /dev/null +++ b/packages/twenty-front/src/modules/workflow/graphql/queries/workflowStepConnectedAccountHandle.ts @@ -0,0 +1,13 @@ +import { gql } from '@apollo/client'; + +export const WORKFLOW_STEP_CONNECTED_ACCOUNT_HANDLE = gql` + query WorkflowStepConnectedAccountHandle($connectedAccountId: UUID!) { + workflowStepConnectedAccountHandle( + connectedAccountId: $connectedAccountId + ) { + id + handle + provider + } + } +`; diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmailBase.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmailBase.tsx index 824d54d0ed..9358377410 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmailBase.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmailBase.tsx @@ -1,13 +1,13 @@ import { type ConnectedAccount } from '@/accounts/types/ConnectedAccount'; import { getMissingDraftEmailScopes } from '@/accounts/utils/hasMissingDraftEmailScopes'; import { WorkflowSendEmailAttachments } from '@/advanced-text-editor/components/WorkflowSendEmailAttachments'; -import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { FormAdvancedTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormAdvancedTextFieldInput'; import { FormMultiTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormMultiTextFieldInput'; import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput'; -import { GET_CONNECTED_ACCOUNT_BY_ID } from '@/settings/accounts/graphql/queries/getConnectedAccountById'; +import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient'; import { useMyConnectedAccounts } from '@/settings/accounts/hooks/useMyConnectedAccounts'; import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth'; +import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu'; import { Select } from '@/ui/input/components/Select'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; @@ -15,6 +15,7 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue'; +import { WORKFLOW_STEP_CONNECTED_ACCOUNT_HANDLE } from '@/workflow/graphql/queries/workflowStepConnectedAccountHandle'; import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion'; import { workflowVisualizerWorkflowIdComponentState } from '@/workflow/states/workflowVisualizerWorkflowIdComponentState'; import { type WorkflowEmailAction } from '@/workflow/types/WorkflowEmailAction'; @@ -109,6 +110,8 @@ export const WorkflowEditActionEmailBase = ({ handleFieldChange('connectedAccountId', connectedAccountId); }; + const apolloCoreClient = useApolloCoreClient(); + const { accounts: myAccounts, loading: myAccountsLoading } = useMyConnectedAccounts(); @@ -118,17 +121,13 @@ export const WorkflowEditActionEmailBase = ({ ); const { data: otherAccountData, loading: otherAccountLoading } = useQuery<{ - connectedAccountById: Pick< + workflowStepConnectedAccountHandle: Pick< ConnectedAccount, - | 'id' - | 'handle' - | 'provider' - | 'scopes' - | 'userWorkspaceId' - | 'connectionParameters' + 'id' | 'handle' | 'provider' > | null; - }>(GET_CONNECTED_ACCOUNT_BY_ID, { - variables: { id: configuredAccountId }, + }>(WORKFLOW_STEP_CONNECTED_ACCOUNT_HANDLE, { + client: apolloCoreClient, + variables: { connectedAccountId: configuredAccountId }, skip: !isDefined(configuredAccountId) || configuredAccountId === '' || @@ -137,25 +136,25 @@ export const WorkflowEditActionEmailBase = ({ const loading = myAccountsLoading || otherAccountLoading; - const otherAccount = otherAccountData?.connectedAccountById ?? null; + const otherAccount = + otherAccountData?.workflowStepConnectedAccountHandle ?? null; - const selectedAccount = - myAccounts.find((account) => account.id === configuredAccountId) ?? - otherAccount ?? - undefined; + const ownAccount = myAccounts.find( + (account) => account.id === configuredAccountId, + ); const missingDraftScopes = - action.type === 'DRAFT_EMAIL' && isDefined(selectedAccount) - ? getMissingDraftEmailScopes(selectedAccount) + action.type === 'DRAFT_EMAIL' && isDefined(ownAccount) + ? getMissingDraftEmailScopes(ownAccount) : []; const missingScopes = - isDefined(selectedAccount) && - selectedAccount.provider !== ConnectedAccountProvider.IMAP_SMTP_CALDAV && + isDefined(ownAccount) && + ownAccount.provider !== ConnectedAccountProvider.IMAP_SMTP_CALDAV && missingDraftScopes.length > 0 ? { - provider: selectedAccount.provider, - loginHint: selectedAccount.handle, + provider: ownAccount.provider, + loginHint: ownAccount.handle, } : null; @@ -179,13 +178,7 @@ export const WorkflowEditActionEmailBase = ({ }); }); - if ( - isDefined(otherAccount) && - !( - otherAccount.provider === ConnectedAccountProvider.IMAP_SMTP_CALDAV && - !isDefined(otherAccount.connectionParameters?.SMTP) - ) - ) { + if (isDefined(otherAccount)) { emptyOption = { label: otherAccount.handle, value: otherAccount.id, diff --git a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version-step.resolver.ts b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version-step.resolver.ts index 0f00efefc2..058f68fe96 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version-step.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version-step.resolver.ts @@ -1,9 +1,10 @@ import { UseFilters, UseGuards, UsePipes } from '@nestjs/common'; -import { Args, Mutation } from '@nestjs/graphql'; +import { Args, Mutation, Query } from '@nestjs/graphql'; import { PermissionFlagType } from 'twenty-shared/constants'; import { CoreResolver } from 'src/engine/api/graphql/graphql-config/decorators/core-resolver.decorator'; +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter'; import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe'; import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool'; @@ -23,6 +24,8 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard'; import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; +import { ConnectedAccountMetadataService } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.service'; +import { ConnectedAccountHandleDTO } from 'src/engine/metadata-modules/connected-account/dtos/connected-account-handle.dto'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service'; import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service'; @@ -46,8 +49,31 @@ export class WorkflowVersionStepResolver { private readonly workflowRunnerWorkspaceService: WorkflowRunnerWorkspaceService, private readonly workflowRunWorkspaceService: WorkflowRunWorkspaceService, private readonly httpTool: HttpTool, + private readonly connectedAccountMetadataService: ConnectedAccountMetadataService, ) {} + // Related to https://github.com/twentyhq/private-issues/issues/478 + @Query(() => ConnectedAccountHandleDTO, { nullable: true }) + async workflowStepConnectedAccountHandle( + @Args('connectedAccountId', { type: () => UUIDScalarType }) id: string, + @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, + ): Promise { + const account = await this.connectedAccountMetadataService.findById({ + id, + workspaceId, + }); + + if (!account) { + return null; + } + + return { + id: account.id, + handle: account.handle, + provider: account.provider, + }; + } + @Mutation(() => WorkflowVersionStepChangesDTO) async createWorkflowVersionStep( @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, diff --git a/packages/twenty-server/src/engine/core-modules/workflow/workflow-api.module.ts b/packages/twenty-server/src/engine/core-modules/workflow/workflow-api.module.ts index cded9b9b4f..c16bf52d53 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/workflow-api.module.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/workflow-api.module.ts @@ -9,6 +9,7 @@ import { WorkflowVersionEdgeResolver } from 'src/engine/core-modules/workflow/re import { WorkflowVersionStepResolver } from 'src/engine/core-modules/workflow/resolvers/workflow-version-step.resolver'; import { WorkflowVersionResolver } from 'src/engine/core-modules/workflow/resolvers/workflow-version.resolver'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; +import { ConnectedAccountMetadataModule } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.module'; import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; @@ -34,6 +35,7 @@ import { WorkflowTriggerModule } from 'src/modules/workflow/workflow-trigger/wor LogicFunctionModule, CodeStepBuildModule, WorkspaceManyOrAllFlatEntityMapsCacheModule, + ConnectedAccountMetadataModule, ], controllers: [WorkflowTriggerController], providers: [ 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 521b9c61c3..2a413aea9c 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 @@ -2,20 +2,19 @@ import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; -import { In, Repository } from 'typeorm'; +import { Repository } from 'typeorm'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { AppOAuthRevokeService } from 'src/engine/core-modules/application/connection-provider/refresh/services/app-oauth-revoke.service'; +import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; import { ConnectedAccountException, ConnectedAccountExceptionCode, } 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 { 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 { 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 { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; @@ -37,17 +36,13 @@ export class ConnectedAccountMetadataService { private readonly appOAuthRevokeService: AppOAuthRevokeService, ) {} - async findAll(workspaceId: string): Promise { - return this.repository.find({ where: { workspaceId } }); - } - async findByUserWorkspaceId({ userWorkspaceId, workspaceId, }: { userWorkspaceId: string; workspaceId: string; - }): Promise { + }): Promise { return this.repository.find({ where: { userWorkspaceId, workspaceId }, }); @@ -59,7 +54,7 @@ export class ConnectedAccountMetadataService { }: { id: string; workspaceId: string; - }): Promise { + }): Promise { return this.repository.findOne({ where: { id, workspaceId } }); } @@ -77,18 +72,6 @@ export class ConnectedAccountMetadataService { }); } - async findByIds({ - ids, - workspaceId, - }: { - ids: string[]; - workspaceId: string; - }): Promise { - return this.repository.find({ - where: { id: In(ids), workspaceId }, - }); - } - async verifyOwnership({ id, userWorkspaceId, @@ -141,7 +124,7 @@ export class ConnectedAccountMetadataService { provider: string; userWorkspaceId: string; }, - ): Promise { + ): Promise { const entity = this.repository.create(data); return this.repository.save(entity); @@ -155,7 +138,7 @@ export class ConnectedAccountMetadataService { id: string; workspaceId: string; data: Partial; - }): Promise { + }): Promise { await this.repository.update( { id, workspaceId }, data as Record, @@ -170,7 +153,7 @@ export class ConnectedAccountMetadataService { }: { id: string; workspaceId: string; - }): Promise { + }): Promise { const connectedAccount = await this.repository.findOneOrFail({ where: { id, workspaceId }, }); diff --git a/packages/twenty-server/src/engine/metadata-modules/connected-account/dtos/connected-account-handle.dto.ts b/packages/twenty-server/src/engine/metadata-modules/connected-account/dtos/connected-account-handle.dto.ts new file mode 100644 index 0000000000..25f9f483cb --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/connected-account/dtos/connected-account-handle.dto.ts @@ -0,0 +1,23 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { IsNotEmpty, IsString, IsUUID } from 'class-validator'; + +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; + +@ObjectType('ConnectedAccountHandleDTO') +export class ConnectedAccountHandleDTO { + @IsUUID() + @IsNotEmpty() + @Field(() => UUIDScalarType) + id: string; + + @IsString() + @IsNotEmpty() + @Field() + handle: string; + + @IsString() + @IsNotEmpty() + @Field() + provider: string; +} 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 d3ba26f55a..14bdd46f22 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 @@ -1,20 +1,18 @@ import { UseGuards, UseInterceptors } from '@nestjs/common'; import { Args, Mutation, Query } from '@nestjs/graphql'; -import { PermissionFlagType } from 'twenty-shared/constants'; - -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 { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; 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 { 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'; import { ConnectedAccountMetadataService } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.service'; -import { ConnectedAccountDTO } from 'src/engine/metadata-modules/connected-account/dtos/connected-account.dto'; import { ConnectedAccountPublicDTO } from 'src/engine/metadata-modules/connected-account/dtos/connected-account-public.dto'; +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'; +import { buildPublicConnectedAccount } from 'src/engine/metadata-modules/connected-account/utils/build-public-connected-account.util'; @UseGuards(WorkspaceAuthGuard) @UseInterceptors(ConnectedAccountGraphqlApiExceptionInterceptor) @@ -30,30 +28,13 @@ export class ConnectedAccountResolver { @AuthWorkspace() workspace: WorkspaceEntity, @AuthUserWorkspaceId() userWorkspaceId: string, ): Promise { - return this.connectedAccountMetadataService.findByUserWorkspaceId({ - userWorkspaceId, - workspaceId: workspace.id, - }); - } + const accounts = + await this.connectedAccountMetadataService.findByUserWorkspaceId({ + userWorkspaceId, + workspaceId: workspace.id, + }); - @Query(() => ConnectedAccountPublicDTO, { nullable: true }) - @UseGuards(NoPermissionGuard) - async connectedAccountById( - @Args('id', { type: () => UUIDScalarType }) id: string, - @AuthWorkspace() workspace: WorkspaceEntity, - ): Promise { - return this.connectedAccountMetadataService.findById({ - id, - workspaceId: workspace.id, - }); - } - - @Query(() => [ConnectedAccountPublicDTO]) - @UseGuards(SettingsPermissionGuard(PermissionFlagType.CONNECTED_ACCOUNTS)) - async connectedAccounts( - @AuthWorkspace() workspace: WorkspaceEntity, - ): Promise { - return this.connectedAccountMetadataService.findAll(workspace.id); + return accounts.map((account) => buildPublicConnectedAccount(account)); } @Mutation(() => ConnectedAccountPublicDTO) @@ -69,9 +50,11 @@ export class ConnectedAccountResolver { workspaceId: workspace.id, }); - return this.connectedAccountMetadataService.delete({ + const deleted = await this.connectedAccountMetadataService.delete({ id, workspaceId: workspace.id, }); + + return buildPublicConnectedAccount(deleted); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/connected-account/utils/build-public-connected-account.util.ts b/packages/twenty-server/src/engine/metadata-modules/connected-account/utils/build-public-connected-account.util.ts new file mode 100644 index 0000000000..9cda958ad2 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/connected-account/utils/build-public-connected-account.util.ts @@ -0,0 +1,24 @@ +import { buildPublicConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/utils/build-public-connection-parameters.util'; +import { type ConnectedAccountPublicDTO } from 'src/engine/metadata-modules/connected-account/dtos/connected-account-public.dto'; +import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; + +export function buildPublicConnectedAccount( + account: ConnectedAccountEntity, +): ConnectedAccountPublicDTO; +export function buildPublicConnectedAccount( + account: ConnectedAccountEntity | null, +): ConnectedAccountPublicDTO | null; +export function buildPublicConnectedAccount( + account: ConnectedAccountEntity | null, +): ConnectedAccountPublicDTO | null { + if (!account) { + return null; + } + + return { + ...account, + connectionParameters: buildPublicConnectionParameters( + account.connectionParameters, + ), + }; +} 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 441a8f3556..e90a7368b5 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 @@ -6,34 +6,35 @@ import { isDefined } from 'twenty-shared/utils'; import { Not, Repository } from 'typeorm'; +import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator'; +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { buildPublicConnectedAccount } from 'src/engine/metadata-modules/connected-account/utils/build-public-connected-account.util'; +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 { NoPermissionGuard } from 'src/engine/guards/no-permission.guard'; +import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; +import { ConnectedAccountMetadataService } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.service'; +import { ConnectedAccountPublicDTO } from 'src/engine/metadata-modules/connected-account/dtos/connected-account-public.dto'; +import { CreateEmailGroupChannelInput } from 'src/engine/metadata-modules/message-channel/dtos/create-email-group-channel.input'; +import { CreateEmailGroupChannelOutput } from 'src/engine/metadata-modules/message-channel/dtos/create-email-group-channel.output'; +import { MessageChannelDTO } from 'src/engine/metadata-modules/message-channel/dtos/message-channel.dto'; +import { UpdateMessageChannelInput } from 'src/engine/metadata-modules/message-channel/dtos/update-message-channel.input'; +import { type 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 { + MessageChannelException, + MessageChannelExceptionCode, +} from 'src/engine/metadata-modules/message-channel/message-channel.exception'; +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 { MessageChannelPendingGroupEmailsAction, MessageChannelSyncStage, MessageChannelType, 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 { NoPermissionGuard } from 'src/engine/guards/no-permission.guard'; -import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; -import { CreateEmailGroupChannelInput } from 'src/engine/metadata-modules/message-channel/dtos/create-email-group-channel.input'; -import { CreateEmailGroupChannelOutput } from 'src/engine/metadata-modules/message-channel/dtos/create-email-group-channel.output'; -import { ConnectedAccountMetadataService } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.service'; -import { ConnectedAccountPublicDTO } from 'src/engine/metadata-modules/connected-account/dtos/connected-account-public.dto'; -import { MessageChannelDTO } from 'src/engine/metadata-modules/message-channel/dtos/message-channel.dto'; -import { UpdateMessageChannelInput } from 'src/engine/metadata-modules/message-channel/dtos/update-message-channel.input'; -import { - MessageChannelException, - MessageChannelExceptionCode, -} 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 { 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'; @UseGuards(WorkspaceAuthGuard) @UseInterceptors(MessageChannelGraphqlApiExceptionInterceptor) @@ -55,24 +56,23 @@ export class MessageChannelResolver { @AuthWorkspace() workspace: WorkspaceEntity, @AuthUserWorkspaceId() userWorkspaceId: string, ): Promise { - const connectedAccount = - await this.connectedAccountMetadataService.findById({ + if (messageChannel.type === MessageChannelType.EMAIL_GROUP) { + const account = await this.connectedAccountMetadataService.findById({ id: messageChannel.connectedAccountId, workspaceId: workspace.id, }); - if (!isDefined(connectedAccount)) { - return null; + return buildPublicConnectedAccount(account); } - if ( - messageChannel.type !== MessageChannelType.EMAIL_GROUP && - connectedAccount.userWorkspaceId !== userWorkspaceId - ) { - return null; - } + const account = + await this.connectedAccountMetadataService.findByIdAndUserWorkspaceId({ + id: messageChannel.connectedAccountId, + userWorkspaceId, + workspaceId: workspace.id, + }); - return connectedAccount; + return buildPublicConnectedAccount(account); } @Query(() => [MessageChannelDTO]) 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 000ab64218..4ba36f589c 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 @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity'; +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 { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; @@ -12,6 +13,7 @@ import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-co @Module({ imports: [ TypeOrmModule.forFeature([CalendarChannelEntity, MessageChannelEntity]), + ConnectedAccountMetadataModule, PermissionsModule, WorkspaceDataSourceModule, MessagingCommonModule, diff --git a/packages/twenty-server/src/modules/connected-account/channel-sync/channel-sync.resolver.ts b/packages/twenty-server/src/modules/connected-account/channel-sync/channel-sync.resolver.ts index fc5b3c5aee..ef9ddb8631 100644 --- a/packages/twenty-server/src/modules/connected-account/channel-sync/channel-sync.resolver.ts +++ b/packages/twenty-server/src/modules/connected-account/channel-sync/channel-sync.resolver.ts @@ -8,9 +8,11 @@ import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorato import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter'; import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe'; 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 { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; +import { ConnectedAccountMetadataService } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.service'; import { ChannelSyncSuccessDTO } from 'src/modules/connected-account/channel-sync/dtos/channel-sync-success.dto'; import { ChannelSyncService } from 'src/modules/connected-account/channel-sync/services/channel-sync.service'; @@ -19,7 +21,10 @@ import { ChannelSyncService } from 'src/modules/connected-account/channel-sync/s @UseFilters(AuthGraphqlApiExceptionFilter) @UseGuards(WorkspaceAuthGuard) export class ChannelSyncResolver { - constructor(private readonly channelSyncService: ChannelSyncService) {} + constructor( + private readonly channelSyncService: ChannelSyncService, + private readonly connectedAccountMetadataService: ConnectedAccountMetadataService, + ) {} @Mutation(() => ChannelSyncSuccessDTO) @UseGuards(SettingsPermissionGuard(PermissionFlagType.CONNECTED_ACCOUNTS)) @@ -27,7 +32,14 @@ export class ChannelSyncResolver { @Args('connectedAccountId', { type: () => UUIDScalarType }) connectedAccountId: string, @AuthWorkspace() workspace: WorkspaceEntity, + @AuthUserWorkspaceId() userWorkspaceId: string, ): Promise { + await this.connectedAccountMetadataService.verifyOwnership({ + id: connectedAccountId, + userWorkspaceId, + workspaceId: workspace.id, + }); + await this.channelSyncService.startChannelSync({ connectedAccountId, workspaceId: workspace.id, diff --git a/packages/twenty-server/test/integration/graphql/suites/object-generated/connected-accounts.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-generated/connected-accounts.integration-spec.ts deleted file mode 100644 index 45ee425f42..0000000000 --- a/packages/twenty-server/test/integration/graphql/suites/object-generated/connected-accounts.integration-spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import request from 'supertest'; - -const client = request(`http://localhost:${APP_PORT}`); - -describe('connectedAccountsResolver (e2e)', () => { - it('should find many connectedAccounts', () => { - const queryData = { - query: ` - query connectedAccounts { - connectedAccounts { - edges { - node { - handle - provider - accessToken - refreshToken - lastSyncHistoryId - authFailedAt - handleAliases - scopes - id - createdAt - updatedAt - deletedAt - accountOwnerId - } - } - } - } - `, - }; - - return client - .post('/graphql') - .set('Authorization', `Bearer ${APPLE_JANE_ADMIN_ACCESS_TOKEN}`) - .send(queryData) - .expect(200) - .expect((res) => { - expect(res.body.data).toBeDefined(); - expect(res.body.errors).toBeUndefined(); - }) - .expect((res) => { - const data = res.body.data.connectedAccounts; - - expect(data).toBeDefined(); - expect(Array.isArray(data.edges)).toBe(true); - - const edges = data.edges; - - if (edges.length > 0) { - const connectedAccounts = edges[0].node; - - expect(connectedAccounts).toHaveProperty('handle'); - expect(connectedAccounts).toHaveProperty('provider'); - expect(connectedAccounts).toHaveProperty('accessToken'); - expect(connectedAccounts).toHaveProperty('refreshToken'); - expect(connectedAccounts).toHaveProperty('lastSyncHistoryId'); - expect(connectedAccounts).toHaveProperty('authFailedAt'); - expect(connectedAccounts).toHaveProperty('handleAliases'); - expect(connectedAccounts).toHaveProperty('scopes'); - expect(connectedAccounts).toHaveProperty('id'); - expect(connectedAccounts).toHaveProperty('createdAt'); - expect(connectedAccounts).toHaveProperty('updatedAt'); - expect(connectedAccounts).toHaveProperty('deletedAt'); - expect(connectedAccounts).toHaveProperty('accountOwnerId'); - } - }); - }); -}); diff --git a/packages/twenty-server/test/integration/graphql/suites/workflow/utils/get-workflow-step-connected-account-handle.util.ts b/packages/twenty-server/test/integration/graphql/suites/workflow/utils/get-workflow-step-connected-account-handle.util.ts new file mode 100644 index 0000000000..5f52ce5b03 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/workflow/utils/get-workflow-step-connected-account-handle.util.ts @@ -0,0 +1,27 @@ +import { gql } from 'graphql-tag'; +import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; + +export const getWorkflowStepConnectedAccountHandle = async ({ + connectedAccountId, +}: { + connectedAccountId: string; +}) => { + const response = await makeGraphqlAPIRequest({ + query: gql` + query WorkflowStepConnectedAccountHandle($connectedAccountId: UUID!) { + workflowStepConnectedAccountHandle( + connectedAccountId: $connectedAccountId + ) { + id + handle + provider + } + } + `, + variables: { connectedAccountId }, + }); + + expect(response.body.errors).toBeUndefined(); + + return response.body.data.workflowStepConnectedAccountHandle; +}; diff --git a/packages/twenty-server/test/integration/graphql/suites/workflow/workflow-step-connected-account-handle.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/workflow/workflow-step-connected-account-handle.integration-spec.ts new file mode 100644 index 0000000000..1c89585533 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/workflow/workflow-step-connected-account-handle.integration-spec.ts @@ -0,0 +1,31 @@ +import { getWorkflowStepConnectedAccountHandle } from 'test/integration/graphql/suites/workflow/utils/get-workflow-step-connected-account-handle.util'; + +import { CONNECTED_ACCOUNT_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/connected-account-data-seeds.constant'; + +describe('workflowStepConnectedAccountHandle', () => { + it('should return handle for own account', async () => { + const result = await getWorkflowStepConnectedAccountHandle({ + connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.JANE, + }); + + expect(result).toBeDefined(); + expect(result.handle).toBe('jane.austen@apple.dev'); + }); + + it('should return handle for another user account in the same workspace', async () => { + const result = await getWorkflowStepConnectedAccountHandle({ + connectedAccountId: CONNECTED_ACCOUNT_DATA_SEED_IDS.JONY, + }); + + expect(result).toBeDefined(); + expect(result.id).toBe(CONNECTED_ACCOUNT_DATA_SEED_IDS.JONY); + }); + + it('should return null for non-existent account', async () => { + const result = await getWorkflowStepConnectedAccountHandle({ + connectedAccountId: '00000000-0000-0000-0000-000000000000', + }); + + expect(result).toBeNull(); + }); +}); 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 836e4e5c08..8a56501677 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,6 +1,5 @@ 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 { CONNECTED_ACCOUNT_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/connected-account-data-seeds.constant'; @@ -83,48 +82,6 @@ describe('connectedAccountResolver (e2e)', () => { }); }); - describe('connectedAccounts (admin listing)', () => { - it('should return all workspace accounts for admin', async () => { - const response = await makeMetadataAPIRequest({ - query: gql` - query ConnectedAccounts { - connectedAccounts { - id - handle - provider - } - } - `, - }); - - expect(response.status).toBe(200); - expect(response.body.errors).toBeUndefined(); - - const accounts = response.body.data.connectedAccounts; - 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); - }); - - it('should also be accessible for member role (tool permission)', async () => { - const response = await makeMetadataAPIRequestWithMemberRole({ - query: gql` - query ConnectedAccounts { - connectedAccounts { - id - handle - } - } - `, - }); - - expect(response.status).toBe(200); - expect(response.body.errors).toBeUndefined(); - expect(response.body.data.connectedAccounts).toBeDefined(); - }); - }); - describe('deleteConnectedAccount', () => { it('should allow deleting own account', async () => { const response = await makeMetadataAPIRequest({