feat: CalDav Driver (#13170)

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
neo773
2025-07-15 21:11:23 +05:30
committed by GitHub
parent c5a74b8e92
commit 3e8fa3120d
22 changed files with 1210 additions and 339 deletions

View File

@ -22,10 +22,6 @@ export type Scalars = {
Upload: any;
};
export type AccountType = {
type: Scalars['String'];
};
export type ActivateWorkspaceInput = {
displayName?: InputMaybe<Scalars['String']>;
};
@ -435,6 +431,7 @@ export type ConnectionParameters = {
password: Scalars['String'];
port: Scalars['Float'];
secure?: InputMaybe<Scalars['Boolean']>;
username?: InputMaybe<Scalars['String']>;
};
export type ConnectionParametersOutput = {
@ -443,6 +440,7 @@ export type ConnectionParametersOutput = {
password: Scalars['String'];
port: Scalars['Float'];
secure?: Maybe<Scalars['Boolean']>;
username?: Maybe<Scalars['String']>;
};
export type CreateApiKeyDto = {
@ -673,6 +671,12 @@ export type EditSsoOutput = {
type: IdentityProviderType;
};
export type EmailAccountConnectionParameters = {
CALDAV?: InputMaybe<ConnectionParameters>;
IMAP?: InputMaybe<ConnectionParameters>;
SMTP?: InputMaybe<ConnectionParameters>;
};
export type EmailPasswordResetLink = {
__typename?: 'EmailPasswordResetLink';
/** Boolean that confirms query was dispatched */
@ -1117,7 +1121,7 @@ export type Mutation = {
resendWorkspaceInvitation: SendInvitationsOutput;
revokeApiKey?: Maybe<ApiKey>;
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<Scalars['String']>;
};
@ -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<Scalars['String']>;
}>;
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<typeof useSkipSyncEmailOnboardingStepMutation>;
export type SkipSyncEmailOnboardingStepMutationResult = Apollo.MutationResult<SkipSyncEmailOnboardingStepMutation>;
export type SkipSyncEmailOnboardingStepMutationOptions = Apollo.BaseMutationOptions<SkipSyncEmailOnboardingStepMutation, SkipSyncEmailOnboardingStepMutationVariables>;
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<SaveImapSmtpCaldavMutation, SaveImapSmtpCaldavMutationVariables>;
export type SaveImapSmtpCaldavAccountMutationFn = Apollo.MutationFunction<SaveImapSmtpCaldavAccountMutation, SaveImapSmtpCaldavAccountMutationVariables>;
/**
* __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<SaveImapSmtpCaldavMutation, SaveImapSmtpCaldavMutationVariables>) {
export function useSaveImapSmtpCaldavAccountMutation(baseOptions?: Apollo.MutationHookOptions<SaveImapSmtpCaldavAccountMutation, SaveImapSmtpCaldavAccountMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<SaveImapSmtpCaldavMutation, SaveImapSmtpCaldavMutationVariables>(SaveImapSmtpCaldavDocument, options);
return Apollo.useMutation<SaveImapSmtpCaldavAccountMutation, SaveImapSmtpCaldavAccountMutationVariables>(SaveImapSmtpCaldavAccountDocument, options);
}
export type SaveImapSmtpCaldavMutationHookResult = ReturnType<typeof useSaveImapSmtpCaldavMutation>;
export type SaveImapSmtpCaldavMutationResult = Apollo.MutationResult<SaveImapSmtpCaldavMutation>;
export type SaveImapSmtpCaldavMutationOptions = Apollo.BaseMutationOptions<SaveImapSmtpCaldavMutation, SaveImapSmtpCaldavMutationVariables>;
export type SaveImapSmtpCaldavAccountMutationHookResult = ReturnType<typeof useSaveImapSmtpCaldavAccountMutation>;
export type SaveImapSmtpCaldavAccountMutationResult = Apollo.MutationResult<SaveImapSmtpCaldavAccountMutation>;
export type SaveImapSmtpCaldavAccountMutationOptions = Apollo.BaseMutationOptions<SaveImapSmtpCaldavAccountMutation, SaveImapSmtpCaldavAccountMutationVariables>;
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
}
}

View File

@ -22,10 +22,6 @@ export type Scalars = {
Upload: any;
};
export type AccountType = {
type: Scalars['String'];
};
export type ActivateWorkspaceInput = {
displayName?: InputMaybe<Scalars['String']>;
};
@ -435,6 +431,7 @@ export type ConnectionParameters = {
password: Scalars['String'];
port: Scalars['Float'];
secure?: InputMaybe<Scalars['Boolean']>;
username?: InputMaybe<Scalars['String']>;
};
export type ConnectionParametersOutput = {
@ -443,6 +440,7 @@ export type ConnectionParametersOutput = {
password: Scalars['String'];
port: Scalars['Float'];
secure?: Maybe<Scalars['Boolean']>;
username?: Maybe<Scalars['String']>;
};
export type CreateApiKeyDto = {
@ -637,6 +635,12 @@ export type EditSsoOutput = {
type: IdentityProviderType;
};
export type EmailAccountConnectionParameters = {
CALDAV?: InputMaybe<ConnectionParameters>;
IMAP?: InputMaybe<ConnectionParameters>;
SMTP?: InputMaybe<ConnectionParameters>;
};
export type EmailPasswordResetLink = {
__typename?: 'EmailPasswordResetLink';
/** Boolean that confirms query was dispatched */
@ -1072,7 +1076,7 @@ export type Mutation = {
resendWorkspaceInvitation: SendInvitationsOutput;
revokeApiKey?: Maybe<ApiKey>;
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<Scalars['String']>;
};

View File

@ -291,6 +291,22 @@ export const SettingsAccountsConnectionForm = ({
)}
/>
<Controller
name="CALDAV.username"
control={control}
render={({ field, fieldState }) => (
<TextInput
instanceId="caldav-username-connection-form"
label={t`CalDAV Username`}
placeholder={t`john.doe`}
required={false}
value={field.value || ''}
onChange={field.onChange}
error={fieldState.error?.message}
/>
)}
/>
<Controller
name="CALDAV.password"
control={control}
@ -306,47 +322,6 @@ export const SettingsAccountsConnectionForm = ({
/>
)}
/>
<StyledFieldRow>
<StyledFieldGroup>
<Controller
name="CALDAV.port"
control={control}
render={({ field, fieldState }) => (
<TextInput
instanceId="caldav-port-connection-form"
label={t`CalDAV Port`}
type="number"
placeholder="443"
value={field?.value ? field.value : 443}
onChange={(value) =>
field.onChange(handlePortChange(value))
}
error={fieldState.error?.message}
/>
)}
/>
</StyledFieldGroup>
<StyledFieldGroup>
<Controller
name="CALDAV.secure"
control={control}
render={({ field }) => (
<Select
label={t`CalDAV Encryption`}
options={[
{ label: 'SSL/TLS', value: true },
{ label: 'None', value: false },
]}
value={field.value}
onChange={field.onChange}
dropdownId="caldav-secure-dropdown"
/>
)}
/>
</StyledFieldGroup>
</StyledFieldRow>
</StyledConnectionSection>
</StyledFormContainer>
</Section>

View File

@ -1,17 +1,15 @@
import gql from 'graphql-tag';
export const SAVE_IMAP_SMTP_CALDAV_CONNECTION = gql`
mutation SaveImapSmtpCaldav(
export const SAVE_IMAP_SMTP_CALDAV_ACCOUNT = gql`
mutation SaveImapSmtpCaldavAccount(
$accountOwnerId: String!
$handle: String!
$accountType: AccountType!
$connectionParameters: ConnectionParameters!
$connectionParameters: EmailAccountConnectionParameters!
$id: String
) {
saveImapSmtpCaldav(
saveImapSmtpCaldavAccount(
accountOwnerId: $accountOwnerId
handle: $handle
accountType: $accountType
connectionParameters: $connectionParameters
id: $id
) {

View File

@ -22,8 +22,7 @@ export const GET_CONNECTED_IMAP_SMTP_CALDAV_ACCOUNT = gql`
}
CALDAV {
host
port
secure
username
password
}
}

View File

@ -10,7 +10,7 @@ import { SettingsPath } from '@/types/SettingsPath';
import { t } from '@lingui/core/macro';
import {
ConnectionParameters,
useSaveImapSmtpCaldavMutation,
useSaveImapSmtpCaldavAccountMutation,
} from '~/generated-metadata/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
@ -49,7 +49,13 @@ export const useImapSmtpCaldavConnectionForm = ({
handle: '',
IMAP: { host: '', port: 993, password: '', secure: true },
SMTP: { host: '', port: 587, password: '', secure: true },
CALDAV: { host: '', port: 443, password: '', secure: true },
CALDAV: {
host: '',
port: 443,
password: '',
secure: true,
username: undefined,
},
},
});
@ -76,7 +82,7 @@ export const useImapSmtpCaldavConnectionForm = ({
);
const [saveConnection, { loading: saveLoading }] =
useSaveImapSmtpCaldavMutation();
useSaveImapSmtpCaldavAccountMutation();
const watchedValues = watch();
@ -102,47 +108,39 @@ export const useImapSmtpCaldavConnectionForm = ({
);
}, [getConfiguredProtocols, watchedValues.handle]);
const saveIndividualConnection = useCallback(
async (
protocol: keyof ImapSmtpCaldavAccount,
formValues: ConnectionFormData,
): Promise<void> => {
const handleSave = useCallback(
async (formValues: ConnectionFormData): Promise<void> => {
if (!currentWorkspaceMember?.id) {
throw new Error('Workspace member ID is missing');
}
const protocolConfig = formValues[protocol];
if (!protocolConfig) {
throw new Error(`${protocol} configuration is missing`);
}
await saveConnection({
variables: {
...(isEditing && connectedAccountId
? { id: connectedAccountId }
: {}),
accountOwnerId: currentWorkspaceMember.id,
handle: formValues.handle,
accountType: {
type: protocol,
},
connectionParameters: protocolConfig,
},
});
},
[saveConnection, isEditing, connectedAccountId, currentWorkspaceMember?.id],
);
const handleSave = useCallback(
async (formValues: ConnectionFormData): Promise<void> => {
const configuredProtocols = getConfiguredProtocols(formValues);
if (configuredProtocols.length === 0) {
throw new Error('At least one protocol must be configured');
}
const connectionParameters: Partial<
Record<keyof ImapSmtpCaldavAccount, ConnectionParameters>
> = {};
configuredProtocols.forEach((protocol) => {
const protocolConfig = formValues[protocol];
if (isDefined(protocolConfig)) {
connectionParameters[protocol] = protocolConfig;
}
});
try {
await Promise.all(
configuredProtocols.map((protocol) =>
saveIndividualConnection(protocol, formValues),
),
);
await saveConnection({
variables: {
...(isEditing && connectedAccountId
? { id: connectedAccountId }
: {}),
accountOwnerId: currentWorkspaceMember.id,
handle: formValues.handle,
connectionParameters,
},
});
const successMessage = isEditing
? t`Connection successfully updated`
@ -160,12 +158,14 @@ export const useImapSmtpCaldavConnectionForm = ({
}
},
[
currentWorkspaceMember?.id,
getConfiguredProtocols,
saveIndividualConnection,
saveConnection,
isEditing,
connectedAccountId,
enqueueSuccessSnackBar,
enqueueErrorSnackBar,
navigate,
enqueueErrorSnackBar,
],
);

View File

@ -6,6 +6,7 @@ const connectionParameters = z
.object({
host: z.string().default(''),
port: z.number().int().nullable().default(null),
username: z.string().optional(),
password: z.string().default(''),
secure: z.boolean().default(true),
})