From 3e8fa3120de245e637fa78a0abb624ba8f415e97 Mon Sep 17 00:00:00 2001 From: neo773 <62795688+neo773@users.noreply.github.com> Date: Tue, 15 Jul 2025 21:11:23 +0530 Subject: [PATCH] feat: CalDav Driver (#13170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Félix Malfait --- .../src/generated-metadata/graphql.ts | 59 ++- .../twenty-front/src/generated/graphql.ts | 19 +- .../SettingsAccountsConnectionForm.tsx | 57 +- .../mutations/saveImapSmtpCaldavConnection.ts | 10 +- .../getConnectedImapSmtpCaldavAccount.ts | 3 +- .../hooks/useImapSmtpCaldavConnectionForm.ts | 78 +-- .../connectionImapSmtpCalDav.ts | 1 + packages/twenty-server/package.json | 2 + .../dtos/imap-smtp-caldav-connection.dto.ts | 18 + .../imap-smtp-caldav-connection.resolver.ts | 55 +- ...mtp-caldav-connection-validator.service.ts | 1 + .../imap-smtp-caldav-connection.service.ts | 27 +- .../types/imap-smtp-caldav-connection.type.ts | 1 + .../calendar-event-import-manager.module.ts | 2 + .../drivers/caldav/caldav-driver.module.ts | 12 + .../drivers/caldav/lib/caldav.client.ts | 493 ++++++++++++++++++ .../caldav/providers/caldav.provider.ts | 30 ++ .../services/caldav-get-events.service.ts | 72 +++ .../caldav/utils/parse-caldav-error.util.ts | 53 ++ .../services/calendar-get-events.service.ts | 9 +- .../services/imap-smtp-caldav-apis.service.ts | 458 +++++++++------- yarn.lock | 89 +++- 22 files changed, 1210 insertions(+), 339 deletions(-) create mode 100644 packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/caldav-driver.module.ts create mode 100644 packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/lib/caldav.client.ts create mode 100644 packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/providers/caldav.provider.ts create mode 100644 packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/services/caldav-get-events.service.ts create mode 100644 packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/utils/parse-caldav-error.util.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index fb8fa4eaf..946747c9b 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -22,10 +22,6 @@ export type Scalars = { Upload: any; }; -export type AccountType = { - type: Scalars['String']; -}; - export type ActivateWorkspaceInput = { displayName?: InputMaybe; }; @@ -435,6 +431,7 @@ export type ConnectionParameters = { password: Scalars['String']; port: Scalars['Float']; secure?: InputMaybe; + username?: InputMaybe; }; export type ConnectionParametersOutput = { @@ -443,6 +440,7 @@ export type ConnectionParametersOutput = { password: Scalars['String']; port: Scalars['Float']; secure?: Maybe; + username?: Maybe; }; export type CreateApiKeyDto = { @@ -673,6 +671,12 @@ export type EditSsoOutput = { type: IdentityProviderType; }; +export type EmailAccountConnectionParameters = { + CALDAV?: InputMaybe; + IMAP?: InputMaybe; + SMTP?: InputMaybe; +}; + export type EmailPasswordResetLink = { __typename?: 'EmailPasswordResetLink'; /** Boolean that confirms query was dispatched */ @@ -1117,7 +1121,7 @@ export type Mutation = { resendWorkspaceInvitation: SendInvitationsOutput; revokeApiKey?: Maybe; runWorkflowVersion: WorkflowRun; - saveImapSmtpCaldav: ImapSmtpCaldavConnectionSuccess; + saveImapSmtpCaldavAccount: ImapSmtpCaldavConnectionSuccess; sendInvitations: SendInvitationsOutput; signIn: AvailableWorkspacesAndAccessTokensOutput; signUp: AvailableWorkspacesAndAccessTokensOutput; @@ -1436,10 +1440,9 @@ export type MutationRunWorkflowVersionArgs = { }; -export type MutationSaveImapSmtpCaldavArgs = { +export type MutationSaveImapSmtpCaldavAccountArgs = { accountOwnerId: Scalars['String']; - accountType: AccountType; - connectionParameters: ConnectionParameters; + connectionParameters: EmailAccountConnectionParameters; handle: Scalars['String']; id?: InputMaybe; }; @@ -3291,23 +3294,22 @@ export type SkipSyncEmailOnboardingStepMutationVariables = Exact<{ [key: string] export type SkipSyncEmailOnboardingStepMutation = { __typename?: 'Mutation', skipSyncEmailOnboardingStep: { __typename?: 'OnboardingStepSuccess', success: boolean } }; -export type SaveImapSmtpCaldavMutationVariables = Exact<{ +export type SaveImapSmtpCaldavAccountMutationVariables = Exact<{ accountOwnerId: Scalars['String']; handle: Scalars['String']; - accountType: AccountType; - connectionParameters: ConnectionParameters; + connectionParameters: EmailAccountConnectionParameters; id?: InputMaybe; }>; -export type SaveImapSmtpCaldavMutation = { __typename?: 'Mutation', saveImapSmtpCaldav: { __typename?: 'ImapSmtpCaldavConnectionSuccess', success: boolean } }; +export type SaveImapSmtpCaldavAccountMutation = { __typename?: 'Mutation', saveImapSmtpCaldavAccount: { __typename?: 'ImapSmtpCaldavConnectionSuccess', success: boolean } }; export type GetConnectedImapSmtpCaldavAccountQueryVariables = Exact<{ id: Scalars['String']; }>; -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, password: string } | null, SMTP?: { __typename?: 'ConnectionParametersOutput', host: string, port: number, secure?: boolean | null, password: string } | null, CALDAV?: { __typename?: 'ConnectionParametersOutput', host: string, port: number, secure?: boolean | null, password: string } | null } | null } }; +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, password: string } | null, SMTP?: { __typename?: 'ConnectionParametersOutput', host: string, port: number, secure?: boolean | null, password: string } | null, CALDAV?: { __typename?: 'ConnectionParametersOutput', host: string, username?: string | null, password: string } | null } | null } }; export type CreateDatabaseConfigVariableMutationVariables = Exact<{ key: Scalars['String']; @@ -6061,12 +6063,11 @@ export function useSkipSyncEmailOnboardingStepMutation(baseOptions?: Apollo.Muta export type SkipSyncEmailOnboardingStepMutationHookResult = ReturnType; export type SkipSyncEmailOnboardingStepMutationResult = Apollo.MutationResult; export type SkipSyncEmailOnboardingStepMutationOptions = Apollo.BaseMutationOptions; -export const SaveImapSmtpCaldavDocument = gql` - mutation SaveImapSmtpCaldav($accountOwnerId: String!, $handle: String!, $accountType: AccountType!, $connectionParameters: ConnectionParameters!, $id: String) { - saveImapSmtpCaldav( +export const SaveImapSmtpCaldavAccountDocument = gql` + mutation SaveImapSmtpCaldavAccount($accountOwnerId: String!, $handle: String!, $connectionParameters: EmailAccountConnectionParameters!, $id: String) { + saveImapSmtpCaldavAccount( accountOwnerId: $accountOwnerId handle: $handle - accountType: $accountType connectionParameters: $connectionParameters id: $id ) { @@ -6074,36 +6075,35 @@ export const SaveImapSmtpCaldavDocument = gql` } } `; -export type SaveImapSmtpCaldavMutationFn = Apollo.MutationFunction; +export type SaveImapSmtpCaldavAccountMutationFn = Apollo.MutationFunction; /** - * __useSaveImapSmtpCaldavMutation__ + * __useSaveImapSmtpCaldavAccountMutation__ * - * To run a mutation, you first call `useSaveImapSmtpCaldavMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useSaveImapSmtpCaldavMutation` returns a tuple that includes: + * To run a mutation, you first call `useSaveImapSmtpCaldavAccountMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useSaveImapSmtpCaldavAccountMutation` returns a tuple that includes: * - A mutate function that you can call at any time to execute the mutation * - An object with fields that represent the current status of the mutation's execution * * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; * * @example - * const [saveImapSmtpCaldavMutation, { data, loading, error }] = useSaveImapSmtpCaldavMutation({ + * const [saveImapSmtpCaldavAccountMutation, { data, loading, error }] = useSaveImapSmtpCaldavAccountMutation({ * variables: { * accountOwnerId: // value for 'accountOwnerId' * handle: // value for 'handle' - * accountType: // value for 'accountType' * connectionParameters: // value for 'connectionParameters' * id: // value for 'id' * }, * }); */ -export function useSaveImapSmtpCaldavMutation(baseOptions?: Apollo.MutationHookOptions) { +export function useSaveImapSmtpCaldavAccountMutation(baseOptions?: Apollo.MutationHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(SaveImapSmtpCaldavDocument, options); + return Apollo.useMutation(SaveImapSmtpCaldavAccountDocument, options); } -export type SaveImapSmtpCaldavMutationHookResult = ReturnType; -export type SaveImapSmtpCaldavMutationResult = Apollo.MutationResult; -export type SaveImapSmtpCaldavMutationOptions = Apollo.BaseMutationOptions; +export type SaveImapSmtpCaldavAccountMutationHookResult = ReturnType; +export type SaveImapSmtpCaldavAccountMutationResult = Apollo.MutationResult; +export type SaveImapSmtpCaldavAccountMutationOptions = Apollo.BaseMutationOptions; export const GetConnectedImapSmtpCaldavAccountDocument = gql` query GetConnectedImapSmtpCaldavAccount($id: String!) { getConnectedImapSmtpCaldavAccount(id: $id) { @@ -6126,8 +6126,7 @@ export const GetConnectedImapSmtpCaldavAccountDocument = gql` } CALDAV { host - port - secure + username password } } diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index ba8887689..afcffd49a 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -22,10 +22,6 @@ export type Scalars = { Upload: any; }; -export type AccountType = { - type: Scalars['String']; -}; - export type ActivateWorkspaceInput = { displayName?: InputMaybe; }; @@ -435,6 +431,7 @@ export type ConnectionParameters = { password: Scalars['String']; port: Scalars['Float']; secure?: InputMaybe; + username?: InputMaybe; }; export type ConnectionParametersOutput = { @@ -443,6 +440,7 @@ export type ConnectionParametersOutput = { password: Scalars['String']; port: Scalars['Float']; secure?: Maybe; + username?: Maybe; }; export type CreateApiKeyDto = { @@ -637,6 +635,12 @@ export type EditSsoOutput = { type: IdentityProviderType; }; +export type EmailAccountConnectionParameters = { + CALDAV?: InputMaybe; + IMAP?: InputMaybe; + SMTP?: InputMaybe; +}; + export type EmailPasswordResetLink = { __typename?: 'EmailPasswordResetLink'; /** Boolean that confirms query was dispatched */ @@ -1072,7 +1076,7 @@ export type Mutation = { resendWorkspaceInvitation: SendInvitationsOutput; revokeApiKey?: Maybe; runWorkflowVersion: WorkflowRun; - saveImapSmtpCaldav: ImapSmtpCaldavConnectionSuccess; + saveImapSmtpCaldavAccount: ImapSmtpCaldavConnectionSuccess; sendInvitations: SendInvitationsOutput; signIn: AvailableWorkspacesAndAccessTokensOutput; signUp: AvailableWorkspacesAndAccessTokensOutput; @@ -1367,10 +1371,9 @@ export type MutationRunWorkflowVersionArgs = { }; -export type MutationSaveImapSmtpCaldavArgs = { +export type MutationSaveImapSmtpCaldavAccountArgs = { accountOwnerId: Scalars['String']; - accountType: AccountType; - connectionParameters: ConnectionParameters; + connectionParameters: EmailAccountConnectionParameters; handle: Scalars['String']; id?: InputMaybe; }; diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsConnectionForm.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsConnectionForm.tsx index 61f9f17b8..b29d74d1e 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsConnectionForm.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsConnectionForm.tsx @@ -291,6 +291,22 @@ export const SettingsAccountsConnectionForm = ({ )} /> + ( + + )} + /> + )} /> - - - - ( - - field.onChange(handlePortChange(value)) - } - error={fieldState.error?.message} - /> - )} - /> - - - - ( -