diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index f593e716a..e51eff303 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -548,6 +548,7 @@ export type CreateRemoteServerInput = { }; export type CreateRoleInput = { + canAccessAllTools?: InputMaybe; canDestroyAllObjectRecords?: InputMaybe; canReadAllObjectRecords?: InputMaybe; canSoftDeleteAllObjectRecords?: InputMaybe; @@ -1202,7 +1203,7 @@ export type Mutation = { uploadWorkspaceLogo: SignedFileDto; upsertFieldPermissions: Array; upsertObjectPermissions: Array; - upsertSettingPermissions: Array; + upsertPermissionFlags: Array; userLookupAdminPanel: UserLookup; validateApprovedAccessDomain: ApprovedAccessDomain; verifyTwoFactorAuthenticationMethodForAuthenticatedUser: VerifyTwoFactorAuthenticationMethodOutput; @@ -1693,8 +1694,8 @@ export type MutationUpsertObjectPermissionsArgs = { }; -export type MutationUpsertSettingPermissionsArgs = { - upsertSettingPermissionsInput: UpsertSettingPermissionsInput; +export type MutationUpsertPermissionFlagsArgs = { + upsertPermissionFlagsInput: UpsertPermissionFlagsInput; }; @@ -1876,6 +1877,25 @@ export type PageInfo = { startCursor?: Maybe; }; +export type PermissionFlag = { + __typename?: 'PermissionFlag'; + flag: PermissionFlagType; + id: Scalars['String']; + roleId: Scalars['String']; +}; + +export enum PermissionFlagType { + ADMIN_PANEL = 'ADMIN_PANEL', + API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS', + DATA_MODEL = 'DATA_MODEL', + ROLES = 'ROLES', + SECURITY = 'SECURITY', + SEND_EMAIL_TOOL = 'SEND_EMAIL_TOOL', + WORKFLOWS = 'WORKFLOWS', + WORKSPACE = 'WORKSPACE', + WORKSPACE_MEMBERS = 'WORKSPACE_MEMBERS' +} + export enum PermissionsOnAllObjectRecords { DESTROY_ALL_OBJECT_RECORDS = 'DESTROY_ALL_OBJECT_RECORDS', READ_ALL_OBJECT_RECORDS = 'READ_ALL_OBJECT_RECORDS', @@ -2250,6 +2270,7 @@ export type RevokeApiKeyDto = { export type Role = { __typename?: 'Role'; + canAccessAllTools: Scalars['Boolean']; canDestroyAllObjectRecords: Scalars['Boolean']; canReadAllObjectRecords: Scalars['Boolean']; canSoftDeleteAllObjectRecords: Scalars['Boolean']; @@ -2261,7 +2282,7 @@ export type Role = { isEditable: Scalars['Boolean']; label: Scalars['String']; objectPermissions?: Maybe>; - settingPermissions?: Maybe>; + permissionFlags?: Maybe>; workspaceMembers: Array; }; @@ -2381,24 +2402,6 @@ export type ServerlessFunctionIdInput = { id: Scalars['ID']; }; -export type SettingPermission = { - __typename?: 'SettingPermission'; - id: Scalars['String']; - roleId: Scalars['String']; - setting: SettingPermissionType; -}; - -export enum SettingPermissionType { - ADMIN_PANEL = 'ADMIN_PANEL', - API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS', - DATA_MODEL = 'DATA_MODEL', - ROLES = 'ROLES', - SECURITY = 'SECURITY', - WORKFLOWS = 'WORKFLOWS', - WORKSPACE = 'WORKSPACE', - WORKSPACE_MEMBERS = 'WORKSPACE_MEMBERS' -} - export type SetupOidcSsoInput = { clientID: Scalars['String']; clientSecret: Scalars['String']; @@ -2686,6 +2689,7 @@ export type UpdateRoleInput = { }; export type UpdateRolePayload = { + canAccessAllTools?: InputMaybe; canDestroyAllObjectRecords?: InputMaybe; canReadAllObjectRecords?: InputMaybe; canSoftDeleteAllObjectRecords?: InputMaybe; @@ -2752,9 +2756,9 @@ export type UpsertObjectPermissionsInput = { roleId: Scalars['String']; }; -export type UpsertSettingPermissionsInput = { +export type UpsertPermissionFlagsInput = { + permissionFlagKeys: Array; roleId: Scalars['String']; - settingPermissionKeys: Array; }; export type User = { @@ -2832,7 +2836,7 @@ export type UserWorkspace = { objectPermissions?: Maybe>; /** @deprecated Use objectPermissions instead */ objectRecordsPermissions?: Maybe>; - settingsPermissions?: Maybe>; + settingsPermissions?: Maybe>; twoFactorAuthenticationMethodSummary?: Maybe>; updatedAt: Scalars['DateTime']; user: User; @@ -3654,16 +3658,16 @@ export type UpdateLabPublicFeatureFlagMutation = { __typename?: 'Mutation', upda export type ObjectPermissionFragmentFragment = { __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }; -export type RoleFragmentFragment = { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean }; +export type PermissionFlagFragmentFragment = { __typename?: 'PermissionFlag', id: string, flag: PermissionFlagType, roleId: string }; -export type SettingPermissionFragmentFragment = { __typename?: 'SettingPermission', id: string, setting: SettingPermissionType, roleId: string }; +export type RoleFragmentFragment = { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean }; export type CreateOneRoleMutationVariables = Exact<{ createRoleInput: CreateRoleInput; }>; -export type CreateOneRoleMutation = { __typename?: 'Mutation', createOneRole: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } }; +export type CreateOneRoleMutation = { __typename?: 'Mutation', createOneRole: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } }; export type DeleteOneRoleMutationVariables = Exact<{ roleId: Scalars['String']; @@ -3677,7 +3681,7 @@ export type UpdateOneRoleMutationVariables = Exact<{ }>; -export type UpdateOneRoleMutation = { __typename?: 'Mutation', updateOneRole: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } }; +export type UpdateOneRoleMutation = { __typename?: 'Mutation', updateOneRole: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } }; export type UpdateWorkspaceMemberRoleMutationVariables = Exact<{ workspaceMemberId: Scalars['String']; @@ -3685,7 +3689,7 @@ export type UpdateWorkspaceMemberRoleMutationVariables = Exact<{ }>; -export type UpdateWorkspaceMemberRoleMutation = { __typename?: 'Mutation', updateWorkspaceMemberRole: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, roles?: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean }> | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } }; +export type UpdateWorkspaceMemberRoleMutation = { __typename?: 'Mutation', updateWorkspaceMemberRole: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, roles?: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean }> | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } }; export type UpsertObjectPermissionsMutationVariables = Exact<{ upsertObjectPermissionsInput: UpsertObjectPermissionsInput; @@ -3694,17 +3698,17 @@ export type UpsertObjectPermissionsMutationVariables = Exact<{ export type UpsertObjectPermissionsMutation = { __typename?: 'Mutation', upsertObjectPermissions: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> }; -export type UpsertSettingPermissionsMutationVariables = Exact<{ - upsertSettingPermissionsInput: UpsertSettingPermissionsInput; +export type UpsertPermissionFlagsMutationVariables = Exact<{ + upsertPermissionFlagsInput: UpsertPermissionFlagsInput; }>; -export type UpsertSettingPermissionsMutation = { __typename?: 'Mutation', upsertSettingPermissions: Array<{ __typename?: 'SettingPermission', id: string, setting: SettingPermissionType, roleId: string }> }; +export type UpsertPermissionFlagsMutation = { __typename?: 'Mutation', upsertPermissionFlags: Array<{ __typename?: 'PermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> }; export type GetRolesQueryVariables = Exact<{ [key: string]: never; }>; -export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, settingPermissions?: Array<{ __typename?: 'SettingPermission', id: string, setting: SettingPermissionType, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> | null }> }; +export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, permissionFlags?: Array<{ __typename?: 'PermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> | null }> }; export type CreateApprovedAccessDomainMutationVariables = Exact<{ input: CreateApprovedAccessDomainInput; @@ -3835,7 +3839,7 @@ export type VerifyTwoFactorAuthenticationMethodForAuthenticatedUserMutationVaria export type VerifyTwoFactorAuthenticationMethodForAuthenticatedUserMutation = { __typename?: 'Mutation', verifyTwoFactorAuthenticationMethodForAuthenticatedUser: { __typename?: 'VerifyTwoFactorAuthenticationMethodOutput', success: boolean } }; -export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array | null, objectRecordsPermissions?: Array | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: any, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, isTwoFactorAuthenticationEnforced: boolean, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, metadata: any }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null, defaultAgent?: { __typename?: 'Agent', id: any } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } }; +export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array | null, objectRecordsPermissions?: Array | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: any, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, isTwoFactorAuthenticationEnforced: boolean, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, metadata: any }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null, defaultAgent?: { __typename?: 'Agent', id: any } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } }; export type WorkspaceUrlsFragmentFragment = { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }; @@ -3854,7 +3858,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; -export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array | null, objectRecordsPermissions?: Array | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: any, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, isTwoFactorAuthenticationEnforced: boolean, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, metadata: any }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null, defaultAgent?: { __typename?: 'Agent', id: any } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } } }; +export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: any, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', settingsPermissions?: Array | null, objectRecordsPermissions?: Array | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: any, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, isTwoFactorAuthenticationEnforced: boolean, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItem', id: any, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, billingProduct?: { __typename?: 'BillingProduct', name: string, description: string, metadata: { __typename?: 'BillingProductMetadata', planKey: BillingPlanKey, priceUsageBased: BillingUsageType, productKey: BillingProductKey } } | null }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, metadata: any }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null, defaultAgent?: { __typename?: 'Agent', id: any } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } } }; export type ActivateWorkflowVersionMutationVariables = Exact<{ workflowVersionId: Scalars['String']; @@ -3973,7 +3977,7 @@ export type UpdateWorkspaceMutationVariables = Exact<{ }>; -export type UpdateWorkspaceMutation = { __typename?: 'Mutation', updateWorkspace: { __typename?: 'Workspace', id: any, customDomain?: string | null, subdomain: string, displayName?: string | null, logo?: string | null, allowImpersonation: boolean, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, isTwoFactorAuthenticationEnforced: boolean, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } }; +export type UpdateWorkspaceMutation = { __typename?: 'Mutation', updateWorkspace: { __typename?: 'Workspace', id: any, customDomain?: string | null, subdomain: string, displayName?: string | null, logo?: string | null, allowImpersonation: boolean, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, isTwoFactorAuthenticationEnforced: boolean, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean } | null } }; export type UploadWorkspaceLogoMutationVariables = Exact<{ file: Scalars['Upload']; @@ -4063,10 +4067,10 @@ export const WebhookFragmentFragmentDoc = gql` secret } `; -export const SettingPermissionFragmentFragmentDoc = gql` - fragment SettingPermissionFragment on SettingPermission { +export const PermissionFlagFragmentFragmentDoc = gql` + fragment PermissionFlagFragment on PermissionFlag { id - setting + flag roleId } `; @@ -4133,6 +4137,7 @@ export const RoleFragmentFragmentDoc = gql` description icon canUpdateAllSettings + canAccessAllTools isEditable canReadAllObjectRecords canUpdateAllObjectRecords @@ -7732,41 +7737,39 @@ export function useUpsertObjectPermissionsMutation(baseOptions?: Apollo.Mutation export type UpsertObjectPermissionsMutationHookResult = ReturnType; export type UpsertObjectPermissionsMutationResult = Apollo.MutationResult; export type UpsertObjectPermissionsMutationOptions = Apollo.BaseMutationOptions; -export const UpsertSettingPermissionsDocument = gql` - mutation UpsertSettingPermissions($upsertSettingPermissionsInput: UpsertSettingPermissionsInput!) { - upsertSettingPermissions( - upsertSettingPermissionsInput: $upsertSettingPermissionsInput - ) { - ...SettingPermissionFragment +export const UpsertPermissionFlagsDocument = gql` + mutation UpsertPermissionFlags($upsertPermissionFlagsInput: UpsertPermissionFlagsInput!) { + upsertPermissionFlags(upsertPermissionFlagsInput: $upsertPermissionFlagsInput) { + ...PermissionFlagFragment } } - ${SettingPermissionFragmentFragmentDoc}`; -export type UpsertSettingPermissionsMutationFn = Apollo.MutationFunction; + ${PermissionFlagFragmentFragmentDoc}`; +export type UpsertPermissionFlagsMutationFn = Apollo.MutationFunction; /** - * __useUpsertSettingPermissionsMutation__ + * __useUpsertPermissionFlagsMutation__ * - * To run a mutation, you first call `useUpsertSettingPermissionsMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useUpsertSettingPermissionsMutation` returns a tuple that includes: + * To run a mutation, you first call `useUpsertPermissionFlagsMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpsertPermissionFlagsMutation` 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 [upsertSettingPermissionsMutation, { data, loading, error }] = useUpsertSettingPermissionsMutation({ + * const [upsertPermissionFlagsMutation, { data, loading, error }] = useUpsertPermissionFlagsMutation({ * variables: { - * upsertSettingPermissionsInput: // value for 'upsertSettingPermissionsInput' + * upsertPermissionFlagsInput: // value for 'upsertPermissionFlagsInput' * }, * }); */ -export function useUpsertSettingPermissionsMutation(baseOptions?: Apollo.MutationHookOptions) { +export function useUpsertPermissionFlagsMutation(baseOptions?: Apollo.MutationHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(UpsertSettingPermissionsDocument, options); + return Apollo.useMutation(UpsertPermissionFlagsDocument, options); } -export type UpsertSettingPermissionsMutationHookResult = ReturnType; -export type UpsertSettingPermissionsMutationResult = Apollo.MutationResult; -export type UpsertSettingPermissionsMutationOptions = Apollo.BaseMutationOptions; +export type UpsertPermissionFlagsMutationHookResult = ReturnType; +export type UpsertPermissionFlagsMutationResult = Apollo.MutationResult; +export type UpsertPermissionFlagsMutationOptions = Apollo.BaseMutationOptions; export const GetRolesDocument = gql` query GetRoles { getRoles { @@ -7774,8 +7777,8 @@ export const GetRolesDocument = gql` workspaceMembers { ...WorkspaceMemberQueryFragment } - settingPermissions { - ...SettingPermissionFragment + permissionFlags { + ...PermissionFlagFragment } objectPermissions { ...ObjectPermissionFragment @@ -7784,7 +7787,7 @@ export const GetRolesDocument = gql` } ${RoleFragmentFragmentDoc} ${WorkspaceMemberQueryFragmentFragmentDoc} -${SettingPermissionFragmentFragmentDoc} +${PermissionFlagFragmentFragmentDoc} ${ObjectPermissionFragmentFragmentDoc}`; /** diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 95477b938..639c1f725 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -512,6 +512,7 @@ export type CreateOneFieldMetadataInput = { }; export type CreateRoleInput = { + canAccessAllTools?: InputMaybe; canDestroyAllObjectRecords?: InputMaybe; canReadAllObjectRecords?: InputMaybe; canSoftDeleteAllObjectRecords?: InputMaybe; @@ -1153,7 +1154,7 @@ export type Mutation = { uploadWorkspaceLogo: SignedFileDto; upsertFieldPermissions: Array; upsertObjectPermissions: Array; - upsertSettingPermissions: Array; + upsertPermissionFlags: Array; userLookupAdminPanel: UserLookup; validateApprovedAccessDomain: ApprovedAccessDomain; verifyTwoFactorAuthenticationMethodForAuthenticatedUser: VerifyTwoFactorAuthenticationMethodOutput; @@ -1604,8 +1605,8 @@ export type MutationUpsertObjectPermissionsArgs = { }; -export type MutationUpsertSettingPermissionsArgs = { - upsertSettingPermissionsInput: UpsertSettingPermissionsInput; +export type MutationUpsertPermissionFlagsArgs = { + upsertPermissionFlagsInput: UpsertPermissionFlagsInput; }; @@ -1787,6 +1788,25 @@ export type PageInfo = { startCursor?: Maybe; }; +export type PermissionFlag = { + __typename?: 'PermissionFlag'; + flag: PermissionFlagType; + id: Scalars['String']; + roleId: Scalars['String']; +}; + +export enum PermissionFlagType { + ADMIN_PANEL = 'ADMIN_PANEL', + API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS', + DATA_MODEL = 'DATA_MODEL', + ROLES = 'ROLES', + SECURITY = 'SECURITY', + SEND_EMAIL_TOOL = 'SEND_EMAIL_TOOL', + WORKFLOWS = 'WORKFLOWS', + WORKSPACE = 'WORKSPACE', + WORKSPACE_MEMBERS = 'WORKSPACE_MEMBERS' +} + export enum PermissionsOnAllObjectRecords { DESTROY_ALL_OBJECT_RECORDS = 'DESTROY_ALL_OBJECT_RECORDS', READ_ALL_OBJECT_RECORDS = 'READ_ALL_OBJECT_RECORDS', @@ -2096,6 +2116,7 @@ export type RevokeApiKeyDto = { export type Role = { __typename?: 'Role'; + canAccessAllTools: Scalars['Boolean']; canDestroyAllObjectRecords: Scalars['Boolean']; canReadAllObjectRecords: Scalars['Boolean']; canSoftDeleteAllObjectRecords: Scalars['Boolean']; @@ -2107,7 +2128,7 @@ export type Role = { isEditable: Scalars['Boolean']; label: Scalars['String']; objectPermissions?: Maybe>; - settingPermissions?: Maybe>; + permissionFlags?: Maybe>; workspaceMembers: Array; }; @@ -2227,24 +2248,6 @@ export type ServerlessFunctionIdInput = { id: Scalars['ID']; }; -export type SettingPermission = { - __typename?: 'SettingPermission'; - id: Scalars['String']; - roleId: Scalars['String']; - setting: SettingPermissionType; -}; - -export enum SettingPermissionType { - ADMIN_PANEL = 'ADMIN_PANEL', - API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS', - DATA_MODEL = 'DATA_MODEL', - ROLES = 'ROLES', - SECURITY = 'SECURITY', - WORKFLOWS = 'WORKFLOWS', - WORKSPACE = 'WORKSPACE', - WORKSPACE_MEMBERS = 'WORKSPACE_MEMBERS' -} - export type SetupOidcSsoInput = { clientID: Scalars['String']; clientSecret: Scalars['String']; @@ -2524,6 +2527,7 @@ export type UpdateRoleInput = { }; export type UpdateRolePayload = { + canAccessAllTools?: InputMaybe; canDestroyAllObjectRecords?: InputMaybe; canReadAllObjectRecords?: InputMaybe; canSoftDeleteAllObjectRecords?: InputMaybe; @@ -2590,9 +2594,9 @@ export type UpsertObjectPermissionsInput = { roleId: Scalars['String']; }; -export type UpsertSettingPermissionsInput = { +export type UpsertPermissionFlagsInput = { + permissionFlagKeys: Array; roleId: Scalars['String']; - settingPermissionKeys: Array; }; export type User = { @@ -2660,7 +2664,7 @@ export type UserWorkspace = { objectPermissions?: Maybe>; /** @deprecated Use objectPermissions instead */ objectRecordsPermissions?: Maybe>; - settingsPermissions?: Maybe>; + settingsPermissions?: Maybe>; twoFactorAuthenticationMethodSummary?: Maybe>; updatedAt: Scalars['DateTime']; user: User; diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index 5b5126f47..f8db3e590 100644 --- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -4,7 +4,7 @@ import { Route, Routes } from 'react-router-dom'; import { SettingsProtectedRouteWrapper } from '@/settings/components/SettingsProtectedRouteWrapper'; import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader'; import { SettingsPath } from '@/types/SettingsPath'; -import { SettingPermissionType } from '~/generated/graphql'; +import { PermissionFlagType } from '~/generated/graphql'; const SettingsApiKeys = lazy(() => import('~/pages/settings/developers/api-keys/SettingsApiKeys').then( @@ -405,7 +405,7 @@ export const SettingsRoutes = ({ } > @@ -416,7 +416,7 @@ export const SettingsRoutes = ({ } > @@ -428,7 +428,7 @@ export const SettingsRoutes = ({ } > @@ -458,7 +458,7 @@ export const SettingsRoutes = ({ } > @@ -480,7 +480,7 @@ export const SettingsRoutes = ({ } > @@ -555,7 +555,7 @@ export const SettingsRoutes = ({ } > @@ -587,7 +587,7 @@ export const SettingsRoutes = ({ } > diff --git a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx index 0f81ac190..521f31cc2 100644 --- a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx +++ b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerBillingSubscriptionPaused.tsx @@ -5,7 +5,7 @@ import { SettingsPath } from '@/types/SettingsPath'; import { t } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { - SettingPermissionType, + PermissionFlagType, useBillingPortalSessionQuery, } from '~/generated-metadata/graphql'; import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; @@ -20,7 +20,7 @@ export const InformationBannerBillingSubscriptionPaused = () => { }); const { - [SettingPermissionType.WORKSPACE]: hasPermissionToUpdateBillingDetails, + [PermissionFlagType.WORKSPACE]: hasPermissionToUpdateBillingDetails, } = useSettingsPermissionMap(); const openBillingPortal = () => { diff --git a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerEndTrialPeriod.tsx b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerEndTrialPeriod.tsx index 70533cedb..07e8b421e 100644 --- a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerEndTrialPeriod.tsx +++ b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerEndTrialPeriod.tsx @@ -2,13 +2,13 @@ import { useEndSubscriptionTrialPeriod } from '@/billing/hooks/useEndSubscriptio import { InformationBanner } from '@/information-banner/components/InformationBanner'; import { useSettingsPermissionMap } from '@/settings/roles/hooks/useSettingsPermissionMap'; import { useLingui } from '@lingui/react/macro'; -import { SettingPermissionType } from '~/generated-metadata/graphql'; +import { PermissionFlagType } from '~/generated-metadata/graphql'; export const InformationBannerEndTrialPeriod = () => { const { endTrialPeriod, isLoading } = useEndSubscriptionTrialPeriod(); const { t } = useLingui(); - const { [SettingPermissionType.WORKSPACE]: hasPermissionToEndTrialPeriod } = + const { [PermissionFlagType.WORKSPACE]: hasPermissionToEndTrialPeriod } = useSettingsPermissionMap(); return ( diff --git a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerFailPaymentInfo.tsx b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerFailPaymentInfo.tsx index 78f2aeae9..6776a46ab 100644 --- a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerFailPaymentInfo.tsx +++ b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerFailPaymentInfo.tsx @@ -5,7 +5,7 @@ import { SettingsPath } from '@/types/SettingsPath'; import { t } from '@lingui/core/macro'; import { isDefined } from 'twenty-shared/utils'; import { - SettingPermissionType, + PermissionFlagType, useBillingPortalSessionQuery, } from '~/generated-metadata/graphql'; import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; @@ -20,7 +20,7 @@ export const InformationBannerFailPaymentInfo = () => { }); const { - [SettingPermissionType.WORKSPACE]: hasPermissionToUpdateBillingDetails, + [PermissionFlagType.WORKSPACE]: hasPermissionToUpdateBillingDetails, } = useSettingsPermissionMap(); const openBillingPortal = () => { diff --git a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerNoBillingSubscription.tsx b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerNoBillingSubscription.tsx index 3f7b6a19f..8c889315d 100644 --- a/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerNoBillingSubscription.tsx +++ b/packages/twenty-front/src/modules/information-banner/components/billing/InformationBannerNoBillingSubscription.tsx @@ -4,7 +4,7 @@ import { InformationBanner } from '@/information-banner/components/InformationBa import { useSettingsPermissionMap } from '@/settings/roles/hooks/useSettingsPermissionMap'; import { SettingsPath } from '@/types/SettingsPath'; import { t } from '@lingui/core/macro'; -import { SettingPermissionType } from '~/generated-metadata/graphql'; +import { PermissionFlagType } from '~/generated-metadata/graphql'; import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; export const InformationBannerNoBillingSubscription = () => { @@ -15,7 +15,7 @@ export const InformationBannerNoBillingSubscription = () => { successUrlPath: getSettingsPath(SettingsPath.Billing), }); - const { [SettingPermissionType.WORKSPACE]: hasPermissionToSubscribe } = + const { [PermissionFlagType.WORKSPACE]: hasPermissionToSubscribe } = useSettingsPermissionMap(); return ( diff --git a/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupActions.ts b/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupActions.ts index dd10a2294..cca79f0a5 100644 --- a/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupActions.ts +++ b/packages/twenty-front/src/modules/object-record/record-group/hooks/useRecordGroupActions.ts @@ -24,7 +24,7 @@ import { IconEyeOff, IconSettings, } from 'twenty-ui/display'; -import { SettingPermissionType } from '~/generated/graphql'; +import { PermissionFlagType } from '~/generated/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; type UseRecordGroupActionsParams = { @@ -91,7 +91,7 @@ export const useRecordGroupActions = ({ ]); const hasAccessToDataModelSettings = useHasSettingsPermission( - SettingPermissionType.DATA_MODEL, + PermissionFlagType.DATA_MODEL, ); const currentIndex = visibleRecordGroupIds.findIndex( (id) => id === recordGroupDefinition.id, diff --git a/packages/twenty-front/src/modules/settings/components/SettingsProtectedRouteWrapper.tsx b/packages/twenty-front/src/modules/settings/components/SettingsProtectedRouteWrapper.tsx index 21f5e2676..6a3fbb933 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsProtectedRouteWrapper.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsProtectedRouteWrapper.tsx @@ -3,12 +3,12 @@ import { SettingsPath } from '@/types/SettingsPath'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { ReactNode } from 'react'; import { Navigate, Outlet } from 'react-router-dom'; -import { FeatureFlagKey, SettingPermissionType } from '~/generated/graphql'; +import { FeatureFlagKey, PermissionFlagType } from '~/generated/graphql'; import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; type SettingsProtectedRouteWrapperProps = { children?: ReactNode; - settingsPermission?: SettingPermissionType; + settingsPermission?: PermissionFlagType; requiredFeatureFlag?: FeatureFlagKey; }; diff --git a/packages/twenty-front/src/modules/settings/hooks/__tests__/useSettingsNavigationItems.test.tsx b/packages/twenty-front/src/modules/settings/hooks/__tests__/useSettingsNavigationItems.test.tsx index 7bade12cc..ff8882a04 100644 --- a/packages/twenty-front/src/modules/settings/hooks/__tests__/useSettingsNavigationItems.test.tsx +++ b/packages/twenty-front/src/modules/settings/hooks/__tests__/useSettingsNavigationItems.test.tsx @@ -7,7 +7,7 @@ import { MutableSnapshot, RecoilRoot } from 'recoil'; import { Billing, OnboardingStatus, - SettingPermissionType, + PermissionFlagType, } from '~/generated/graphql'; import { currentUserState } from '@/auth/states/currentUserState'; @@ -60,12 +60,12 @@ jest.mock('@/settings/roles/hooks/useSettingsPermissionMap', () => ({ describe('useSettingsNavigationItems', () => { it('should hide workspace settings when no permissions', () => { (useSettingsPermissionMap as jest.Mock).mockImplementation(() => ({ - [SettingPermissionType.WORKSPACE]: false, - [SettingPermissionType.WORKSPACE_MEMBERS]: false, - [SettingPermissionType.DATA_MODEL]: false, - [SettingPermissionType.API_KEYS_AND_WEBHOOKS]: false, - [SettingPermissionType.ROLES]: false, - [SettingPermissionType.SECURITY]: false, + [PermissionFlagType.WORKSPACE]: false, + [PermissionFlagType.WORKSPACE_MEMBERS]: false, + [PermissionFlagType.DATA_MODEL]: false, + [PermissionFlagType.API_KEYS_AND_WEBHOOKS]: false, + [PermissionFlagType.ROLES]: false, + [PermissionFlagType.SECURITY]: false, })); const { result } = renderHook(() => useSettingsNavigationItems(), { @@ -81,12 +81,12 @@ describe('useSettingsNavigationItems', () => { it('should show workspace settings when has permissions', () => { (useSettingsPermissionMap as jest.Mock).mockImplementation(() => ({ - [SettingPermissionType.WORKSPACE]: true, - [SettingPermissionType.WORKSPACE_MEMBERS]: true, - [SettingPermissionType.DATA_MODEL]: true, - [SettingPermissionType.API_KEYS_AND_WEBHOOKS]: true, - [SettingPermissionType.ROLES]: true, - [SettingPermissionType.SECURITY]: true, + [PermissionFlagType.WORKSPACE]: true, + [PermissionFlagType.WORKSPACE_MEMBERS]: true, + [PermissionFlagType.DATA_MODEL]: true, + [PermissionFlagType.API_KEYS_AND_WEBHOOKS]: true, + [PermissionFlagType.ROLES]: true, + [PermissionFlagType.SECURITY]: true, })); const { result } = renderHook(() => useSettingsNavigationItems(), { diff --git a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx index 7b3e374dc..c52656b66 100644 --- a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx +++ b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx @@ -30,7 +30,7 @@ import { IconUsers, IconWebhook, } from 'twenty-ui/display'; -import { SettingPermissionType } from '~/generated/graphql'; +import { PermissionFlagType } from '~/generated/graphql'; export type SettingsNavigationSection = { label: string; @@ -107,46 +107,45 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { label: t`General`, path: SettingsPath.Workspace, Icon: IconSettings, - isHidden: !permissionMap[SettingPermissionType.WORKSPACE], + isHidden: !permissionMap[PermissionFlagType.WORKSPACE], }, { label: t`Members`, path: SettingsPath.WorkspaceMembersPage, Icon: IconUsers, - isHidden: !permissionMap[SettingPermissionType.WORKSPACE_MEMBERS], + isHidden: !permissionMap[PermissionFlagType.WORKSPACE_MEMBERS], }, { label: t`Roles`, path: SettingsPath.Roles, Icon: IconLock, - isHidden: !permissionMap[SettingPermissionType.ROLES], + isHidden: !permissionMap[PermissionFlagType.ROLES], }, { label: t`Billing`, path: SettingsPath.Billing, Icon: IconCurrencyDollar, isHidden: - !isBillingEnabled || - !permissionMap[SettingPermissionType.WORKSPACE], + !isBillingEnabled || !permissionMap[PermissionFlagType.WORKSPACE], }, { label: t`Data model`, path: SettingsPath.Objects, Icon: IconHierarchy2, - isHidden: !permissionMap[SettingPermissionType.DATA_MODEL], + isHidden: !permissionMap[PermissionFlagType.DATA_MODEL], }, { label: t`Integrations`, path: SettingsPath.Integrations, Icon: IconApps, - isHidden: !permissionMap[SettingPermissionType.API_KEYS_AND_WEBHOOKS], + isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS], }, { label: t`Security`, path: SettingsPath.Security, Icon: IconKey, isAdvanced: true, - isHidden: !permissionMap[SettingPermissionType.SECURITY], + isHidden: !permissionMap[PermissionFlagType.SECURITY], }, ], }, @@ -159,14 +158,14 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { path: SettingsPath.APIs, Icon: IconApi, isAdvanced: true, - isHidden: !permissionMap[SettingPermissionType.API_KEYS_AND_WEBHOOKS], + isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS], }, { label: t`Webhooks`, path: SettingsPath.Webhooks, Icon: IconWebhook, isAdvanced: true, - isHidden: !permissionMap[SettingPermissionType.API_KEYS_AND_WEBHOOKS], + isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS], }, { label: t`Functions`, @@ -192,7 +191,7 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { Icon: IconFlask, isHidden: !labPublicFeatureFlags.length || - !permissionMap[SettingPermissionType.WORKSPACE], + !permissionMap[PermissionFlagType.WORKSPACE], }, { label: t`Releases`, diff --git a/packages/twenty-front/src/modules/settings/roles/graphql/fragments/permissionFlagFragment.ts b/packages/twenty-front/src/modules/settings/roles/graphql/fragments/permissionFlagFragment.ts new file mode 100644 index 000000000..58c1ef429 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/roles/graphql/fragments/permissionFlagFragment.ts @@ -0,0 +1,9 @@ +import { gql } from '@apollo/client'; + +export const PERMISSION_FLAG_FRAGMENT = gql` + fragment PermissionFlagFragment on PermissionFlag { + id + flag + roleId + } +`; diff --git a/packages/twenty-front/src/modules/settings/roles/graphql/fragments/roleFragment.ts b/packages/twenty-front/src/modules/settings/roles/graphql/fragments/roleFragment.ts index 229e70819..fd733237f 100644 --- a/packages/twenty-front/src/modules/settings/roles/graphql/fragments/roleFragment.ts +++ b/packages/twenty-front/src/modules/settings/roles/graphql/fragments/roleFragment.ts @@ -7,6 +7,7 @@ export const ROLE_FRAGMENT = gql` description icon canUpdateAllSettings + canAccessAllTools isEditable canReadAllObjectRecords canUpdateAllObjectRecords diff --git a/packages/twenty-front/src/modules/settings/roles/graphql/fragments/settingPermissionFragment.ts b/packages/twenty-front/src/modules/settings/roles/graphql/fragments/settingPermissionFragment.ts deleted file mode 100644 index c61f457b2..000000000 --- a/packages/twenty-front/src/modules/settings/roles/graphql/fragments/settingPermissionFragment.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { gql } from '@apollo/client'; - -export const SETTING_PERMISSION_FRAGMENT = gql` - fragment SettingPermissionFragment on SettingPermission { - id - setting - roleId - } -`; diff --git a/packages/twenty-front/src/modules/settings/roles/graphql/mutations/upsertPermissionFlagsMutation.ts b/packages/twenty-front/src/modules/settings/roles/graphql/mutations/upsertPermissionFlagsMutation.ts new file mode 100644 index 000000000..2e4d1c475 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/roles/graphql/mutations/upsertPermissionFlagsMutation.ts @@ -0,0 +1,15 @@ +import { PERMISSION_FLAG_FRAGMENT } from '@/settings/roles/graphql/fragments/permissionFlagFragment'; +import { gql } from '@apollo/client'; + +export const UPSERT_PERMISSION_FLAGS = gql` + ${PERMISSION_FLAG_FRAGMENT} + mutation UpsertPermissionFlags( + $upsertPermissionFlagsInput: UpsertPermissionFlagsInput! + ) { + upsertPermissionFlags( + upsertPermissionFlagsInput: $upsertPermissionFlagsInput + ) { + ...PermissionFlagFragment + } + } +`; diff --git a/packages/twenty-front/src/modules/settings/roles/graphql/mutations/upsertSettingPermissionsMutation.ts b/packages/twenty-front/src/modules/settings/roles/graphql/mutations/upsertSettingPermissionsMutation.ts deleted file mode 100644 index 8923e8f01..000000000 --- a/packages/twenty-front/src/modules/settings/roles/graphql/mutations/upsertSettingPermissionsMutation.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { SETTING_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/settingPermissionFragment'; -import { gql } from '@apollo/client'; - -export const UPSERT_SETTING_PERMISSIONS = gql` - ${SETTING_PERMISSION_FRAGMENT} - mutation UpsertSettingPermissions( - $upsertSettingPermissionsInput: UpsertSettingPermissionsInput! - ) { - upsertSettingPermissions( - upsertSettingPermissionsInput: $upsertSettingPermissionsInput - ) { - ...SettingPermissionFragment - } - } -`; diff --git a/packages/twenty-front/src/modules/settings/roles/graphql/queries/getRolesQuery.ts b/packages/twenty-front/src/modules/settings/roles/graphql/queries/getRolesQuery.ts index 902afb8df..d83cd37a3 100644 --- a/packages/twenty-front/src/modules/settings/roles/graphql/queries/getRolesQuery.ts +++ b/packages/twenty-front/src/modules/settings/roles/graphql/queries/getRolesQuery.ts @@ -1,13 +1,13 @@ import { OBJECT_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/objectPermissionFragment'; +import { PERMISSION_FLAG_FRAGMENT } from '@/settings/roles/graphql/fragments/permissionFlagFragment'; import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment'; -import { SETTING_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/settingPermissionFragment'; import { WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/workspaceMemberQueryFragment'; import { gql } from '@apollo/client'; export const GET_ROLES = gql` ${WORKSPACE_MEMBER_QUERY_FRAGMENT} ${ROLE_FRAGMENT} - ${SETTING_PERMISSION_FRAGMENT} + ${PERMISSION_FLAG_FRAGMENT} ${OBJECT_PERMISSION_FRAGMENT} query GetRoles { getRoles { @@ -15,8 +15,8 @@ export const GET_ROLES = gql` workspaceMembers { ...WorkspaceMemberQueryFragment } - settingPermissions { - ...SettingPermissionFragment + permissionFlags { + ...PermissionFlagFragment } objectPermissions { ...ObjectPermissionFragment diff --git a/packages/twenty-front/src/modules/settings/roles/hooks/useHasSettingsPermission.ts b/packages/twenty-front/src/modules/settings/roles/hooks/useHasSettingsPermission.ts index 8c2d3421a..75b27faba 100644 --- a/packages/twenty-front/src/modules/settings/roles/hooks/useHasSettingsPermission.ts +++ b/packages/twenty-front/src/modules/settings/roles/hooks/useHasSettingsPermission.ts @@ -2,20 +2,20 @@ import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceSta import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useRecoilValue } from 'recoil'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; -import { SettingPermissionType } from '~/generated/graphql'; +import { PermissionFlagType } from '~/generated/graphql'; export const useHasSettingsPermission = ( - settingsPermission?: SettingPermissionType, + permissionFlag?: PermissionFlagType, ) => { const currentWorkspace = useRecoilValue(currentWorkspaceState); const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState); - if (!settingsPermission) { + if (!permissionFlag) { return true; } if ( - settingsPermission === SettingPermissionType.WORKSPACE && + permissionFlag === PermissionFlagType.WORKSPACE && currentWorkspace?.activationStatus === WorkspaceActivationStatus.PENDING_CREATION ) { @@ -28,5 +28,5 @@ export const useHasSettingsPermission = ( return false; } - return currentUserWorkspaceSetting.includes(settingsPermission); + return currentUserWorkspaceSetting.includes(permissionFlag); }; diff --git a/packages/twenty-front/src/modules/settings/roles/hooks/useSettingsPermissionMap.ts b/packages/twenty-front/src/modules/settings/roles/hooks/useSettingsPermissionMap.ts index 50e2bd804..fbf0df912 100644 --- a/packages/twenty-front/src/modules/settings/roles/hooks/useSettingsPermissionMap.ts +++ b/packages/twenty-front/src/modules/settings/roles/hooks/useSettingsPermissionMap.ts @@ -1,10 +1,10 @@ import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState'; import { useRecoilValue } from 'recoil'; -import { SettingPermissionType } from '~/generated/graphql'; +import { PermissionFlagType } from '~/generated/graphql'; import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue'; export const useSettingsPermissionMap = (): Record< - SettingPermissionType, + PermissionFlagType, boolean > => { const currentUserWorkspace = useRecoilValue(currentUserWorkspaceState); @@ -13,7 +13,7 @@ export const useSettingsPermissionMap = (): Record< currentUserWorkspace?.settingsPermissions; const initialPermissions = buildRecordFromKeysWithSameValue( - Object.values(SettingPermissionType), + Object.values(PermissionFlagType), false, ); diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx index cff402c54..bf5a323e5 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx @@ -1,6 +1,7 @@ import { SettingsRolePermissionsObjectLevelSection } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection'; import { SettingsRolePermissionsObjectsSection } from '@/settings/roles/role-permissions/objects-permissions/components/SettingsRolePermissionsObjectsSection'; -import { SettingsRolePermissionsSettingsSection } from '@/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsSection'; +import { SettingsRolePermissionsSettingsSection } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection'; +import { SettingsRolePermissionsToolSection } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsToolSection'; import styled from '@emotion/styled'; const StyledRolePermissionsContainer = styled.div` @@ -32,6 +33,10 @@ export const SettingsRolePermissions = ({ roleId={roleId} isEditable={isEditable} /> + ); }; diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsSection.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx similarity index 84% rename from packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsSection.tsx rename to packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx index 7b4cc6970..c2474b7fd 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsSection.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx @@ -1,7 +1,7 @@ import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle'; -import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsTableHeader'; -import { SettingsRolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsTableRow'; -import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/settings-permissions/types/SettingsRolePermissionsSettingPermission'; +import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableHeader'; +import { SettingsRolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableRow'; +import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/permission-flags/types/SettingsRolePermissionsSettingPermission'; import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; @@ -17,7 +17,7 @@ import { IconUsers, } from 'twenty-ui/display'; import { AnimatedExpandableContainer, Card, Section } from 'twenty-ui/layout'; -import { SettingPermissionType } from '~/generated-metadata/graphql'; +import { PermissionFlagType } from '~/generated-metadata/graphql'; const StyledTable = styled.div` border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; @@ -48,43 +48,43 @@ export const SettingsRolePermissionsSettingsSection = ({ const settingsPermissionsConfig: SettingsRolePermissionsSettingPermission[] = [ { - key: SettingPermissionType.API_KEYS_AND_WEBHOOKS, + key: PermissionFlagType.API_KEYS_AND_WEBHOOKS, name: t`API Keys & Webhooks`, description: t`Manage API keys and webhooks`, Icon: IconCode, }, { - key: SettingPermissionType.WORKSPACE, + key: PermissionFlagType.WORKSPACE, name: t`Workspace`, description: t`Set global workspace preferences`, Icon: IconSettings, }, { - key: SettingPermissionType.WORKSPACE_MEMBERS, + key: PermissionFlagType.WORKSPACE_MEMBERS, name: t`Users`, description: t`Add or remove users`, Icon: IconUsers, }, { - key: SettingPermissionType.ROLES, + key: PermissionFlagType.ROLES, name: t`Roles`, description: t`Define user roles and access levels`, Icon: IconLockOpen, }, { - key: SettingPermissionType.DATA_MODEL, + key: PermissionFlagType.DATA_MODEL, name: t`Data Model`, description: t`Edit CRM data structure and fields`, Icon: IconHierarchy, }, { - key: SettingPermissionType.SECURITY, + key: PermissionFlagType.SECURITY, name: t`Security`, description: t`Manage security policies`, Icon: IconKey, }, { - key: SettingPermissionType.WORKFLOWS, + key: PermissionFlagType.WORKFLOWS, name: t`Workflows`, description: t`Manage workflows`, Icon: IconSettingsAutomation, diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsTableHeader.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableHeader.tsx similarity index 83% rename from packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsTableHeader.tsx rename to packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableHeader.tsx index ea457b9f9..522e64084 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsTableHeader.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableHeader.tsx @@ -4,7 +4,7 @@ import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; import { Checkbox } from 'twenty-ui/input'; -import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/settings-permissions/types/SettingsRolePermissionsSettingPermission'; +import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/permission-flags/types/SettingsRolePermissionsSettingPermission'; import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; import { useRecoilState } from 'recoil'; import { v4 } from 'uuid'; @@ -32,15 +32,15 @@ export const SettingsRolePermissionsSettingsTableHeader = ({ ); const allSettingsPermissionsEnabled = settingsPermissionsConfig.every( (permission) => - settingsDraftRole.settingPermissions?.some( - (settingPermission) => settingPermission.setting === permission.key, + settingsDraftRole.permissionFlags?.some( + (permissionFlag) => permissionFlag.flag === permission.key, ), ); const someSettingsPermissionsEnabled = settingsPermissionsConfig.some( (permission) => - settingsDraftRole.settingPermissions?.some( - (settingPermission) => settingPermission.setting === permission.key, + settingsDraftRole.permissionFlags?.some( + (permissionFlag) => permissionFlag.flag === permission.key, ), ); @@ -61,10 +61,10 @@ export const SettingsRolePermissionsSettingsTableHeader = ({ setSettingsDraftRole({ ...settingsDraftRole, - settingPermissions: newValue + permissionFlags: newValue ? settingsPermissionsConfig.map((permission) => ({ id: v4(), - setting: permission.key, + flag: permission.key, roleId, })) : [], diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsTableRow.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableRow.tsx similarity index 76% rename from packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsTableRow.tsx rename to packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableRow.tsx index bfbdffb3a..20b4391fa 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableRow.tsx @@ -1,4 +1,4 @@ -import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/settings-permissions/types/SettingsRolePermissionsSettingPermission'; +import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/permission-flags/types/SettingsRolePermissionsSettingPermission'; import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; @@ -44,6 +44,7 @@ type SettingsRolePermissionsSettingsTableRowProps = { roleId: string; permission: SettingsRolePermissionsSettingPermission; isEditable: boolean; + isToolPermission?: boolean; }; export const SettingsRolePermissionsSettingsTableRow = ({ @@ -55,27 +56,35 @@ export const SettingsRolePermissionsSettingsTableRow = ({ const [settingsDraftRole, setSettingsDraftRole] = useRecoilState( settingsDraftRoleFamilyState(roleId), ); - const canUpdateAllSettings = settingsDraftRole.canUpdateAllSettings; - const isSettingPermissionEnabled = - settingsDraftRole.settingPermissions?.some( - (settingPermission) => settingPermission.setting === permission.key, + const isPermissionEnabled = + settingsDraftRole.permissionFlags?.some( + (permissionFlag) => permissionFlag.flag === permission.key, ) ?? false; - const isChecked = isSettingPermissionEnabled || canUpdateAllSettings; - const isDisabled = !isEditable || canUpdateAllSettings; + const isAllSettingsOverride = + !permission.isToolPermission && + settingsDraftRole.canUpdateAllSettings === true; + const isAllToolsOverride = + permission.isToolPermission && settingsDraftRole.canAccessAllTools === true; + const isChecked = Boolean( + isPermissionEnabled || isAllSettingsOverride || isAllToolsOverride, + ); + const isDisabled = Boolean( + !isEditable || isAllSettingsOverride || isAllToolsOverride, + ); const handleChange = (value: boolean) => { - const currentPermissions = settingsDraftRole.settingPermissions ?? []; + const currentPermissions = settingsDraftRole.permissionFlags ?? []; if (value === true) { setSettingsDraftRole({ ...settingsDraftRole, - settingPermissions: [ + permissionFlags: [ ...currentPermissions, { id: v4(), - setting: permission.key, + flag: permission.key, roleId, }, ], @@ -83,8 +92,8 @@ export const SettingsRolePermissionsSettingsTableRow = ({ } else { setSettingsDraftRole({ ...settingsDraftRole, - settingPermissions: currentPermissions.filter( - (p) => p.setting !== permission.key, + permissionFlags: currentPermissions.filter( + (p) => p.flag !== permission.key, ), }); } diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsToolSection.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsToolSection.tsx new file mode 100644 index 000000000..12e6c1d3f --- /dev/null +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsToolSection.tsx @@ -0,0 +1,101 @@ +import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsOptions/SettingsOptionCardContentToggle'; +import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableHeader'; +import { SettingsRolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsTableRow'; +import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/permission-flags/types/SettingsRolePermissionsSettingPermission'; +import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState'; +import styled from '@emotion/styled'; +import { t } from '@lingui/core/macro'; +import { useRecoilState } from 'recoil'; + +import { H2Title, IconMail, IconTool } from 'twenty-ui/display'; +import { AnimatedExpandableContainer, Card, Section } from 'twenty-ui/layout'; +import { PermissionFlagType } from '~/generated-metadata/graphql'; + +const StyledTable = styled.div` + border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; +`; + +const StyledTableRows = styled.div` + padding-bottom: ${({ theme }) => theme.spacing(2)}; + padding-top: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledCard = styled(Card)` + margin-bottom: ${({ theme }) => theme.spacing(4)}; +`; + +type SettingsRolePermissionsToolSectionProps = { + roleId: string; + isEditable: boolean; +}; + +export const SettingsRolePermissionsToolSection = ({ + roleId, + isEditable, +}: SettingsRolePermissionsToolSectionProps) => { + const [settingsDraftRole, setSettingsDraftRole] = useRecoilState( + settingsDraftRoleFamilyState(roleId), + ); + + const toolPermissionsConfig: SettingsRolePermissionsSettingPermission[] = [ + { + key: PermissionFlagType.SEND_EMAIL_TOOL, + name: t`Send Email`, + description: t`Allow sending emails using connected accounts`, + Icon: IconMail, + isToolPermission: true, + }, + ]; + + return ( +
+ + + { + setSettingsDraftRole({ + ...settingsDraftRole, + canAccessAllTools: !settingsDraftRole.canAccessAllTools, + }); + }} + /> + + + + + + {toolPermissionsConfig.map((permission) => ( + + ))} + + + +
+ ); +}; diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/types/SettingsRolePermissionsSettingPermission.ts b/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/types/SettingsRolePermissionsSettingPermission.ts similarity index 58% rename from packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/types/SettingsRolePermissionsSettingPermission.ts rename to packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/types/SettingsRolePermissionsSettingPermission.ts index 8ccb79ca4..fb1025ab6 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/settings-permissions/types/SettingsRolePermissionsSettingPermission.ts +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/types/SettingsRolePermissionsSettingPermission.ts @@ -1,9 +1,10 @@ import { IconComponent } from 'twenty-ui/display'; -import { SettingPermissionType } from '~/generated-metadata/graphql'; +import { PermissionFlagType } from '~/generated-metadata/graphql'; export type SettingsRolePermissionsSettingPermission = { - key: SettingPermissionType; + key: PermissionFlagType; name: string; description: string; Icon: IconComponent; + isToolPermission?: boolean; }; diff --git a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx index 15d96b8b9..6851dda31 100644 --- a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx @@ -28,7 +28,7 @@ import { useCreateOneRoleMutation, useUpdateOneRoleMutation, useUpsertObjectPermissionsMutation, - useUpsertSettingPermissionsMutation, + useUpsertPermissionFlagsMutation, } from '~/generated-metadata/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { getDirtyFields } from '~/utils/getDirtyFields'; @@ -45,6 +45,7 @@ const ROLE_BASIC_KEYS: Array = [ 'description', 'icon', 'canUpdateAllSettings', + 'canAccessAllTools', 'canReadAllObjectRecords', 'canUpdateAllObjectRecords', 'canSoftDeleteAllObjectRecords', @@ -61,7 +62,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => { const [createRole] = useCreateOneRoleMutation(); const [updateRole] = useUpdateOneRoleMutation(); - const [upsertSettingPermissions] = useUpsertSettingPermissionsMutation(); + const [upsertPermissionFlags] = useUpsertPermissionFlagsMutation(); const [upsertObjectPermissions] = useUpsertObjectPermissionsMutation(); const [isSaving, setIsSaving] = useState(false); @@ -144,6 +145,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => { description: settingsDraftRole.description, icon: settingsDraftRole.icon, canUpdateAllSettings: settingsDraftRole.canUpdateAllSettings, + canAccessAllTools: settingsDraftRole.canAccessAllTools, canReadAllObjectRecords: settingsDraftRole.canReadAllObjectRecords, canUpdateAllObjectRecords: @@ -161,14 +163,14 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => { return; } - if (isDefined(dirtyFields.settingPermissions)) { - await upsertSettingPermissions({ + if (isDefined(dirtyFields.permissionFlags)) { + await upsertPermissionFlags({ variables: { - upsertSettingPermissionsInput: { + upsertPermissionFlagsInput: { roleId: data.createOneRole.id, - settingPermissionKeys: - settingsDraftRole.settingPermissions?.map( - (settingPermission) => settingPermission.setting, + permissionFlagKeys: + settingsDraftRole.permissionFlags?.map( + (permissionFlag) => permissionFlag.flag, ) ?? [], }, }, @@ -214,14 +216,14 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => { roleId: data.createOneRole.id, }); } else { - if (isDefined(dirtyFields.settingPermissions)) { - await upsertSettingPermissions({ + if (isDefined(dirtyFields.permissionFlags)) { + await upsertPermissionFlags({ variables: { - upsertSettingPermissionsInput: { + upsertPermissionFlagsInput: { roleId: roleId, - settingPermissionKeys: - settingsDraftRole.settingPermissions?.map( - (settingPermission) => settingPermission.setting, + permissionFlagKeys: + settingsDraftRole.permissionFlags?.map( + (permissionFlag) => permissionFlag.flag, ) ?? [], }, }, @@ -239,6 +241,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => { description: settingsDraftRole.description, icon: settingsDraftRole.icon, canUpdateAllSettings: settingsDraftRole.canUpdateAllSettings, + canAccessAllTools: settingsDraftRole.canAccessAllTools, canReadAllObjectRecords: settingsDraftRole.canReadAllObjectRecords, canUpdateAllObjectRecords: diff --git a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRoleCreateEffect.tsx b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRoleCreateEffect.tsx index 0e5b689eb..be66c6b4a 100644 --- a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRoleCreateEffect.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRoleCreateEffect.tsx @@ -42,6 +42,7 @@ export const SettingsRoleCreateEffect = ({ description: '', icon: 'IconUser', canUpdateAllSettings: true, + canAccessAllTools: true, canReadAllObjectRecords: true, canUpdateAllObjectRecords: true, canSoftDeleteAllObjectRecords: true, diff --git a/packages/twenty-front/src/modules/settings/roles/states/settingsDraftRoleFamilyState.ts b/packages/twenty-front/src/modules/settings/roles/states/settingsDraftRoleFamilyState.ts index f6a808d3b..412288270 100644 --- a/packages/twenty-front/src/modules/settings/roles/states/settingsDraftRoleFamilyState.ts +++ b/packages/twenty-front/src/modules/settings/roles/states/settingsDraftRoleFamilyState.ts @@ -13,9 +13,10 @@ export const settingsDraftRoleFamilyState = createFamilyState({ canSoftDeleteAllObjectRecords: false, canUpdateAllObjectRecords: false, canUpdateAllSettings: false, + canAccessAllTools: false, isEditable: false, workspaceMembers: [], - settingPermissions: [], + permissionFlags: [], objectPermissions: [], }, }); diff --git a/packages/twenty-front/src/testing/mock-data/roles.ts b/packages/twenty-front/src/testing/mock-data/roles.ts index da750e09f..8c2692996 100644 --- a/packages/twenty-front/src/testing/mock-data/roles.ts +++ b/packages/twenty-front/src/testing/mock-data/roles.ts @@ -11,6 +11,7 @@ const rolesMock: Role[] = [ canSoftDeleteAllObjectRecords: true, canUpdateAllObjectRecords: true, canUpdateAllSettings: true, + canAccessAllTools: true, isEditable: false, workspaceMembers: [mockWorkspaceMembers[0]], }, @@ -23,6 +24,7 @@ const rolesMock: Role[] = [ canSoftDeleteAllObjectRecords: false, canUpdateAllObjectRecords: false, canUpdateAllSettings: false, + canAccessAllTools: false, isEditable: true, workspaceMembers: [mockWorkspaceMembers[1]], }, diff --git a/packages/twenty-front/src/testing/mock-data/users.ts b/packages/twenty-front/src/testing/mock-data/users.ts index 9023ef261..d595b5e30 100644 --- a/packages/twenty-front/src/testing/mock-data/users.ts +++ b/packages/twenty-front/src/testing/mock-data/users.ts @@ -3,7 +3,7 @@ import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; import { FeatureFlagKey, OnboardingStatus, - SettingPermissionType, + PermissionFlagType, SubscriptionInterval, SubscriptionStatus, User, @@ -125,7 +125,7 @@ export const mockedUserData: MockedUser = { workspaceMember: mockedWorkspaceMemberData, currentWorkspace: mockCurrentWorkspace, currentUserWorkspace: { - settingsPermissions: [SettingPermissionType.WORKSPACE_MEMBERS], + settingsPermissions: [PermissionFlagType.WORKSPACE_MEMBERS], objectPermissions: generatedMockObjectMetadataItems.map((item) => ({ objectMetadataId: item.id, canReadObjectRecords: true, diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1753149175945-renameSettingPermissionToPermissionFlag.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1753149175945-renameSettingPermissionToPermissionFlag.ts new file mode 100644 index 000000000..1a9bbfcd7 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1753149175945-renameSettingPermissionToPermissionFlag.ts @@ -0,0 +1,46 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RenameSettingPermissionToPermissionFlag1753149175945 + implements MigrationInterface +{ + name = 'RenameSettingPermissionToPermissionFlag1753149175945'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."settingPermission" RENAME TO "permissionFlag"`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."permissionFlag" RENAME COLUMN "setting" TO "flag"`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."permissionFlag" RENAME CONSTRAINT "IDX_SETTING_PERMISSION_SETTING_ROLE_ID_UNIQUE" TO "IDX_PERMISSION_FLAG_FLAG_ROLE_ID_UNIQUE"`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."permissionFlag" DROP CONSTRAINT "FK_b327aadd9fd189f33d2c5237833"`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."permissionFlag" ADD CONSTRAINT "FK_13f8ca9c517976733a1ce4c10eb" FOREIGN KEY ("roleId") REFERENCES "core"."role"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."permissionFlag" RENAME COLUMN "flag" TO "setting"`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."permissionFlag" RENAME TO "settingPermission"`, + ); + + await queryRunner.query( + `ALTER TABLE "core"."permissionFlag" DROP CONSTRAINT "FK_13f8ca9c517976733a1ce4c10eb"`, + ); + await queryRunner.query( + `ALTER TABLE "core"."permissionFlag" ADD CONSTRAINT "FK_b327aadd9fd189f33d2c5237833" FOREIGN KEY ("roleId") REFERENCES "core"."role"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } +} diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1753318977613-AddCanAccessAllToolsColumnToRole.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1753318977613-AddCanAccessAllToolsColumnToRole.ts new file mode 100644 index 000000000..074658c57 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1753318977613-AddCanAccessAllToolsColumnToRole.ts @@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddCanAccessAllToolsColumnToRole1753318977613 + implements MigrationInterface +{ + name = 'AddCanAccessAllToolsColumnToRole1753318977613'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."role" ADD "canAccessAllTools" boolean NOT NULL DEFAULT false`, + ); + + await queryRunner.query( + `UPDATE "core"."role" SET "canAccessAllTools" = true WHERE "label" = 'Admin'`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."role" DROP COLUMN "canAccessAllTools"`, + ); + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/constants/objects-with-settings-permissions-requirements.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/constants/objects-with-settings-permissions-requirements.ts index 603fad32d..b709deaeb 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/constants/objects-with-settings-permissions-requirements.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/constants/objects-with-settings-permissions-requirements.ts @@ -1,6 +1,6 @@ -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; export const OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS = { - apiKey: SettingPermissionType.API_KEYS_AND_WEBHOOKS, - webhook: SettingPermissionType.API_KEYS_AND_WEBHOOKS, + apiKey: PermissionFlagType.API_KEYS_AND_WEBHOOKS, + webhook: PermissionFlagType.API_KEYS_AND_WEBHOOKS, } as const; diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts index 27aaab4d3..8668c85de 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts @@ -26,7 +26,7 @@ import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-quer import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsException, PermissionsExceptionCode, @@ -191,7 +191,7 @@ export abstract class GraphqlQueryBaseResolverService< objectMetadataItemWithFieldMaps.nameSingular, ) ) { - const permissionRequired: SettingPermissionType = + const permissionRequired: PermissionFlagType = // @ts-expect-error legacy noImplicitAny OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS[ objectMetadataItemWithFieldMaps.nameSingular diff --git a/packages/twenty-server/src/engine/core-modules/ai/ai.module.ts b/packages/twenty-server/src/engine/core-modules/ai/ai.module.ts index 0305e08c4..de82ba5cc 100644 --- a/packages/twenty-server/src/engine/core-modules/ai/ai.module.ts +++ b/packages/twenty-server/src/engine/core-modules/ai/ai.module.ts @@ -11,11 +11,16 @@ import { ToolAdapterService } from 'src/engine/core-modules/ai/services/tool-ada import { ToolService } from 'src/engine/core-modules/ai/services/tool.service'; import { TokenModule } from 'src/engine/core-modules/auth/token/token.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; +import { ToolRegistryService } from 'src/engine/core-modules/tool/services/tool-registry.service'; +import { SendEmailTool } from 'src/engine/core-modules/tool/tools/send-email-tool/send-email-tool'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; +import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module'; import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module'; +import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; +import { MessagingModule } from 'src/modules/messaging/messaging.module'; @Global() @Module({ @@ -27,6 +32,9 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/ WorkspacePermissionsCacheModule, WorkspaceCacheStorageModule, UserRoleModule, + TwentyORMModule, + MessagingModule, + PermissionsModule, ], controllers: [AiController, McpController], providers: [ @@ -34,8 +42,10 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/ AiModelRegistryService, ToolService, ToolAdapterService, + ToolRegistryService, AIBillingService, McpService, + SendEmailTool, ], exports: [ AiService, @@ -43,7 +53,9 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/ AIBillingService, ToolService, ToolAdapterService, + ToolRegistryService, McpService, + SendEmailTool, ], }) export class AiModule {} diff --git a/packages/twenty-server/src/engine/core-modules/ai/services/tool-adapter.service.ts b/packages/twenty-server/src/engine/core-modules/ai/services/tool-adapter.service.ts index 3ebbe182a..6a3559937 100644 --- a/packages/twenty-server/src/engine/core-modules/ai/services/tool-adapter.service.ts +++ b/packages/twenty-server/src/engine/core-modules/ai/services/tool-adapter.service.ts @@ -2,28 +2,49 @@ import { Injectable } from '@nestjs/common'; import { ToolSet } from 'ai'; -import { TOOLS } from 'src/engine/core-modules/tool/constants/tools.const'; +import { ToolRegistryService } from 'src/engine/core-modules/tool/services/tool-registry.service'; +import { ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type'; +import { Tool } from 'src/engine/core-modules/tool/types/tool.type'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; +import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; @Injectable() export class ToolAdapterService { - getCoreTools(): ToolSet { - const tools = Array.from(TOOLS.entries()).reduce( - (acc, [toolType, tool]) => { - acc[toolType.toLowerCase()] = { - description: tool.description, - parameters: tool.parameters, - execute: async (parameters) => { - const response = await tool.execute(parameters.input); + constructor( + private readonly toolRegistry: ToolRegistryService, + private readonly permissionsService: PermissionsService, + ) {} - return response; - }, - }; + async getTools(roleId?: string, workspaceId?: string): Promise { + const tools: ToolSet = {}; - return acc; - }, - {}, - ); + for (const toolType of this.toolRegistry.getAllToolTypes()) { + const tool = this.toolRegistry.getTool(toolType); + + if (!tool.flag) { + tools[toolType.toLowerCase()] = this.createToolSet(tool); + } else if (roleId && workspaceId) { + const hasPermission = await this.permissionsService.hasToolPermission( + roleId, + workspaceId, + tool.flag as PermissionFlagType, + ); + + if (hasPermission) { + tools[toolType.toLowerCase()] = this.createToolSet(tool); + } + } + } return tools; } + + private createToolSet(tool: Tool) { + return { + description: tool.description, + parameters: tool.parameters, + execute: async (parameters: { input: ToolInput }) => + tool.execute(parameters.input), + }; + } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts b/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts index 7b835ccfb..3467cbc89 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts @@ -65,7 +65,7 @@ import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard'; import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { TwoFactorAuthenticationExceptionFilter } from 'src/engine/core-modules/two-factor-authentication/two-factor-authentication-exception.filter'; @@ -548,7 +548,7 @@ export class AuthResolver { @UseGuards( WorkspaceAuthGuard, - SettingsPermissionsGuard(SettingPermissionType.API_KEYS_AND_WEBHOOKS), + SettingsPermissionsGuard(PermissionFlagType.API_KEYS_AND_WEBHOOKS), ) @Mutation(() => ApiKeyToken) async generateApiKeyToken( diff --git a/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts b/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts index 4a6adaca0..703b30cfa 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts @@ -32,7 +32,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsException, PermissionsExceptionCode, @@ -60,7 +60,7 @@ export class BillingResolver { @Query(() => BillingSessionOutput) @UseGuards( WorkspaceAuthGuard, - SettingsPermissionsGuard(SettingPermissionType.WORKSPACE), + SettingsPermissionsGuard(PermissionFlagType.WORKSPACE), ) async billingPortalSession( @AuthWorkspace() workspace: Workspace, @@ -124,7 +124,7 @@ export class BillingResolver { @Mutation(() => BillingUpdateOutput) @UseGuards( WorkspaceAuthGuard, - SettingsPermissionsGuard(SettingPermissionType.WORKSPACE), + SettingsPermissionsGuard(PermissionFlagType.WORKSPACE), ) async switchToYearlyInterval(@AuthWorkspace() workspace: Workspace) { await this.billingSubscriptionService.switchToYearlyInterval(workspace); @@ -135,7 +135,7 @@ export class BillingResolver { @Mutation(() => BillingUpdateOutput) @UseGuards( WorkspaceAuthGuard, - SettingsPermissionsGuard(SettingPermissionType.WORKSPACE), + SettingsPermissionsGuard(PermissionFlagType.WORKSPACE), ) async switchToEnterprisePlan(@AuthWorkspace() workspace: Workspace) { await this.billingSubscriptionService.switchToEnterprisePlan(workspace); @@ -154,7 +154,7 @@ export class BillingResolver { @Mutation(() => BillingEndTrialPeriodOutput) @UseGuards( WorkspaceAuthGuard, - SettingsPermissionsGuard(SettingPermissionType.WORKSPACE), + SettingsPermissionsGuard(PermissionFlagType.WORKSPACE), ) async endSubscriptionTrialPeriod( @AuthWorkspace() workspace: Workspace, @@ -165,7 +165,7 @@ export class BillingResolver { @Query(() => [BillingMeteredProductUsageOutput]) @UseGuards( WorkspaceAuthGuard, - SettingsPermissionsGuard(SettingPermissionType.WORKSPACE), + SettingsPermissionsGuard(PermissionFlagType.WORKSPACE), ) async getMeteredProductsUsage( @AuthWorkspace() workspace: Workspace, @@ -199,7 +199,7 @@ export class BillingResolver { await this.permissionsService.userHasWorkspaceSettingPermission({ userWorkspaceId, workspaceId, - setting: SettingPermissionType.WORKSPACE, + setting: PermissionFlagType.WORKSPACE, isExecutedByApiKey, }); diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts index a9c91e20a..1cf158027 100644 --- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts @@ -23,7 +23,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { ImapSmtpCalDavAPIService } from 'src/modules/connected-account/services/imap-smtp-caldav-apis.service'; @@ -32,7 +32,7 @@ import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/s @Resolver() @UsePipes(ResolverValidationPipe) @UseFilters(AuthGraphqlApiExceptionFilter, PermissionsGraphqlApiExceptionFilter) -@UseGuards(SettingsPermissionsGuard(SettingPermissionType.WORKSPACE)) +@UseGuards(SettingsPermissionsGuard(PermissionFlagType.WORKSPACE)) export class ImapSmtpCaldavResolver { constructor( private readonly twentyORMGlobalManager: TwentyORMGlobalManager, diff --git a/packages/twenty-server/src/engine/core-modules/lab/lab.resolver.ts b/packages/twenty-server/src/engine/core-modules/lab/lab.resolver.ts index e1ac254ca..e9fdde506 100644 --- a/packages/twenty-server/src/engine/core-modules/lab/lab.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/lab/lab.resolver.ts @@ -13,7 +13,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; @Resolver() @@ -23,7 +23,7 @@ import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-module PermissionsGraphqlApiExceptionFilter, PreventNestToAutoLogGraphqlErrorsFilter, ) -@UseGuards(SettingsPermissionsGuard(SettingPermissionType.WORKSPACE)) +@UseGuards(SettingsPermissionsGuard(PermissionFlagType.WORKSPACE)) export class LabResolver { constructor(private featureFlagService: FeatureFlagService) {} diff --git a/packages/twenty-server/src/engine/core-modules/sso/sso.resolver.ts b/packages/twenty-server/src/engine/core-modules/sso/sso.resolver.ts index 08760a7c9..b71aaca3d 100644 --- a/packages/twenty-server/src/engine/core-modules/sso/sso.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/sso/sso.resolver.ts @@ -22,7 +22,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; @Resolver() @@ -31,7 +31,7 @@ import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-module PreventNestToAutoLogGraphqlErrorsFilter, ) @UsePipes(ResolverValidationPipe) -@UseGuards(SettingsPermissionsGuard(SettingPermissionType.SECURITY)) +@UseGuards(SettingsPermissionsGuard(PermissionFlagType.SECURITY)) export class SSOResolver { constructor(private readonly sSOService: SSOService) {} diff --git a/packages/twenty-server/src/engine/core-modules/tool/constants/tools.const.ts b/packages/twenty-server/src/engine/core-modules/tool/constants/tools.const.ts deleted file mode 100644 index 32132f999..000000000 --- a/packages/twenty-server/src/engine/core-modules/tool/constants/tools.const.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ToolType } from 'src/engine/core-modules/tool/enums/tool-type.enum'; -import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool'; -import { Tool } from 'src/engine/core-modules/tool/types/tool.type'; - -export const TOOLS: Map = new Map([ - [ToolType.HTTP_REQUEST, new HttpTool()], -]); diff --git a/packages/twenty-server/src/engine/core-modules/tool/enums/tool-type.enum.ts b/packages/twenty-server/src/engine/core-modules/tool/enums/tool-type.enum.ts index 3e75af4aa..2c7e8c738 100644 --- a/packages/twenty-server/src/engine/core-modules/tool/enums/tool-type.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/tool/enums/tool-type.enum.ts @@ -1,3 +1,4 @@ export enum ToolType { HTTP_REQUEST = 'HTTP_REQUEST', + SEND_EMAIL = 'SEND_EMAIL', } diff --git a/packages/twenty-server/src/engine/core-modules/tool/services/tool-registry.service.ts b/packages/twenty-server/src/engine/core-modules/tool/services/tool-registry.service.ts new file mode 100644 index 000000000..c5c694acb --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/tool/services/tool-registry.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@nestjs/common'; + +import { ToolType } from 'src/engine/core-modules/tool/enums/tool-type.enum'; +import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool'; +import { SendEmailTool } from 'src/engine/core-modules/tool/tools/send-email-tool/send-email-tool'; +import { SendEmailInput } from 'src/engine/core-modules/tool/tools/send-email-tool/types/send-email-input.type'; +import { Tool } from 'src/engine/core-modules/tool/types/tool.type'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; + +@Injectable() +export class ToolRegistryService { + private readonly toolFactories: Map Tool>; + + constructor(private readonly sendEmailTool: SendEmailTool) { + this.toolFactories = new Map Tool>([ + [ToolType.HTTP_REQUEST, () => new HttpTool()], + [ + ToolType.SEND_EMAIL, + () => ({ + description: this.sendEmailTool.description, + parameters: this.sendEmailTool.parameters, + execute: (params) => + this.sendEmailTool.execute(params as SendEmailInput), + flag: PermissionFlagType.SEND_EMAIL_TOOL, + }), + ], + ]); + } + + getTool(toolType: ToolType): Tool { + const factory = this.toolFactories.get(toolType); + + if (!factory) { + throw new Error(`Unknown tool type: ${toolType}`); + } + + return factory(); + } + + getAllToolTypes(): ToolType[] { + return Array.from(this.toolFactories.keys()); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/exceptions/send-email-tool.exception.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/exceptions/send-email-tool.exception.ts new file mode 100644 index 000000000..75824fcca --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/exceptions/send-email-tool.exception.ts @@ -0,0 +1,14 @@ +import { CustomException } from 'src/utils/custom-exception'; + +export class SendEmailToolException extends CustomException { + constructor(message: string, code: SendEmailToolExceptionCode) { + super(message, code); + } +} + +export enum SendEmailToolExceptionCode { + INVALID_CONNECTED_ACCOUNT_ID = 'INVALID_CONNECTED_ACCOUNT_ID', + CONNECTED_ACCOUNT_NOT_FOUND = 'CONNECTED_ACCOUNT_NOT_FOUND', + INVALID_EMAIL = 'INVALID_EMAIL', + WORKSPACE_ID_NOT_FOUND = 'WORKSPACE_ID_NOT_FOUND', +} diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.schema.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.schema.ts new file mode 100644 index 000000000..f2ff90eaf --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.schema.ts @@ -0,0 +1,23 @@ +import { z } from 'zod'; + +export const SendEmailInputZodSchema = z.object({ + email: z.string().email().describe('The recipient email address'), + subject: z.string().describe('The email subject line'), + body: z.string().describe('The email body content (HTML or plain text)'), + connectedAccountId: z + .string() + .uuid() + .describe( + 'The UUID of the connected account to send the email from. Provide this only if you have it; otherwise, leave blank.', + ) + .optional(), +}); + +export const SendEmailToolParametersZodSchema = z.object({ + toolDescription: z + .string() + .describe( + "A clear, human-readable status message describing the email being sent. This will be shown to the user while the tool is being called, so phrase it as a present-tense status update (e.g., 'Sending email to customer about order status'). Explain what email you are sending and to whom in natural language.", + ), + input: SendEmailInputZodSchema, +}); diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.ts new file mode 100644 index 000000000..ac8f52cf0 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.ts @@ -0,0 +1,155 @@ +import { Injectable, Logger } from '@nestjs/common'; + +import DOMPurify from 'dompurify'; +import { JSDOM } from 'jsdom'; +import { isDefined, isValidUuid } from 'twenty-shared/utils'; +import { z } from 'zod'; + +import { + SendEmailToolException, + SendEmailToolExceptionCode, +} from 'src/engine/core-modules/tool/tools/send-email-tool/exceptions/send-email-tool.exception'; +import { SendEmailToolParametersZodSchema } from 'src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.schema'; +import { SendEmailInput } from 'src/engine/core-modules/tool/tools/send-email-tool/types/send-email-input.type'; +import { ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type'; +import { Tool } from 'src/engine/core-modules/tool/types/tool.type'; +import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { MessagingSendMessageService } from 'src/modules/messaging/message-import-manager/services/messaging-send-message.service'; + +@Injectable() +export class SendEmailTool implements Tool { + private readonly logger = new Logger(SendEmailTool.name); + + description = + 'Send an email using a connected account. Requires SEND_EMAIL_TOOL permission.'; + parameters = SendEmailToolParametersZodSchema; + + constructor( + private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + private readonly sendMessageService: MessagingSendMessageService, + ) {} + + private async getConnectedAccount( + connectedAccountId: string, + workspaceId: string, + ) { + if (!isValidUuid(connectedAccountId)) { + throw new SendEmailToolException( + `Connected Account ID is not a valid UUID`, + SendEmailToolExceptionCode.INVALID_CONNECTED_ACCOUNT_ID, + ); + } + + const connectedAccountRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'connectedAccount', + ); + const connectedAccount = await connectedAccountRepository.findOneBy({ + id: connectedAccountId, + }); + + if (!isDefined(connectedAccount)) { + throw new SendEmailToolException( + `Connected Account '${connectedAccountId}' not found`, + SendEmailToolExceptionCode.CONNECTED_ACCOUNT_NOT_FOUND, + ); + } + + return connectedAccount; + } + + private async getOrThrowFirstConnectedAccountId( + workspaceId: string, + ): Promise { + const connectedAccountRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'connectedAccount', + ); + const allAccounts = await connectedAccountRepository.find(); + + if (!allAccounts || allAccounts.length === 0) { + throw new SendEmailToolException( + 'No connected accounts found for this workspace', + SendEmailToolExceptionCode.CONNECTED_ACCOUNT_NOT_FOUND, + ); + } + + return allAccounts[0].id; + } + + async execute(parameters: SendEmailInput): Promise { + const { workspaceId } = this.scopedWorkspaceContextFactory.create(); + + const { email, subject, body } = parameters; + let { connectedAccountId } = parameters; + + try { + const emailSchema = z.string().trim().email('Invalid email'); + const emailValidation = emailSchema.safeParse(email); + + if (!emailValidation.success) { + throw new SendEmailToolException( + `Email '${email}' is invalid`, + SendEmailToolExceptionCode.INVALID_EMAIL, + ); + } + + if (!workspaceId) { + throw new SendEmailToolException( + 'Workspace ID not found', + SendEmailToolExceptionCode.WORKSPACE_ID_NOT_FOUND, + ); + } + + if (!connectedAccountId) { + connectedAccountId = + await this.getOrThrowFirstConnectedAccountId(workspaceId); + } + + const connectedAccount = await this.getConnectedAccount( + connectedAccountId, + workspaceId, + ); + + const window = new JSDOM('').window; + const purify = DOMPurify(window); + const safeBody = purify.sanitize(body || ''); + const safeSubject = purify.sanitize(subject || ''); + + await this.sendMessageService.sendMessage( + { + to: email, + subject: safeSubject, + body: safeBody, + }, + connectedAccount, + ); + + this.logger.log(`Email sent successfully to ${email}`); + + return { + result: { + success: true, + message: `Email sent successfully to ${email}`, + }, + }; + } catch (error) { + if (error instanceof SendEmailToolException) { + return { + error: error.message, + }; + } + + this.logger.error(`Failed to send email: ${error}`); + + return { + error: error instanceof Error ? error.message : 'Failed to send email', + }; + } + } +} diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/types/send-email-input.type.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/types/send-email-input.type.ts new file mode 100644 index 000000000..dc250a29a --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/types/send-email-input.type.ts @@ -0,0 +1,5 @@ +import { z } from 'zod'; + +import { SendEmailInputZodSchema } from 'src/engine/core-modules/tool/tools/send-email-tool/send-email-tool.schema'; + +export type SendEmailInput = z.infer; diff --git a/packages/twenty-server/src/engine/core-modules/tool/types/tool.type.ts b/packages/twenty-server/src/engine/core-modules/tool/types/tool.type.ts index 42c1239a9..0cf8ab45d 100644 --- a/packages/twenty-server/src/engine/core-modules/tool/types/tool.type.ts +++ b/packages/twenty-server/src/engine/core-modules/tool/types/tool.type.ts @@ -3,9 +3,11 @@ import { ZodType } from 'zod'; import { ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type'; import { ToolOutput } from 'src/engine/core-modules/tool/types/tool-output.type'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; export type Tool = { description: string; parameters: JSONSchema7 | ZodType; execute(input: ToolInput): Promise; + flag?: PermissionFlagType; }; diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.entity.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.entity.ts index 2a3a3d9f5..a9b0f63e0 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.entity.ts @@ -19,15 +19,15 @@ import { } from 'typeorm'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { TwoFactorAuthenticationMethodSummaryDto } from 'src/engine/core-modules/two-factor-authentication/dto/two-factor-authentication-method.dto'; +import { TwoFactorAuthenticationMethod } from 'src/engine/core-modules/two-factor-authentication/entities/two-factor-authentication-method.entity'; import { User } from 'src/engine/core-modules/user/user.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; -import { TwoFactorAuthenticationMethod } from 'src/engine/core-modules/two-factor-authentication/entities/two-factor-authentication-method.entity'; -import { TwoFactorAuthenticationMethodSummaryDto } from 'src/engine/core-modules/two-factor-authentication/dto/two-factor-authentication-method.dto'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; -registerEnumType(SettingPermissionType, { - name: 'SettingPermissionType', +registerEnumType(PermissionFlagType, { + name: 'PermissionFlagType', }); registerEnumType(PermissionsOnAllObjectRecords, { @@ -96,8 +96,8 @@ export class UserWorkspace { ) twoFactorAuthenticationMethods: Relation; - @Field(() => [SettingPermissionType], { nullable: true }) - settingsPermissions?: SettingPermissionType[]; + @Field(() => [PermissionFlagType], { nullable: true }) + settingsPermissions?: PermissionFlagType[]; @Field(() => [PermissionsOnAllObjectRecords], { nullable: true, diff --git a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-builder.resolver.ts b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-builder.resolver.ts index 7dbe1d99c..a2cfee869 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-builder.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-builder.resolver.ts @@ -12,7 +12,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type'; import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service'; @@ -21,7 +21,7 @@ import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-bu @UseGuards( WorkspaceAuthGuard, UserAuthGuard, - SettingsPermissionsGuard(SettingPermissionType.WORKFLOWS), + SettingsPermissionsGuard(PermissionFlagType.WORKFLOWS), ) @UsePipes(ResolverValidationPipe) @UseFilters( diff --git a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-step.resolver.ts b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-step.resolver.ts index 2cf2708e5..c1bd40d8a 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-step.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-step.resolver.ts @@ -16,7 +16,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-step/workflow-version-step.workspace-service'; import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; @@ -27,7 +27,7 @@ import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runne @UseGuards( WorkspaceAuthGuard, UserAuthGuard, - SettingsPermissionsGuard(SettingPermissionType.WORKFLOWS), + SettingsPermissionsGuard(PermissionFlagType.WORKFLOWS), ) @UseFilters( PermissionsGraphqlApiExceptionFilter, diff --git a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts index 9825f6669..813502bfc 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-trigger.resolver.ts @@ -14,7 +14,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service'; @@ -24,7 +24,7 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta @UseGuards( WorkspaceAuthGuard, UserAuthGuard, - SettingsPermissionsGuard(SettingPermissionType.WORKFLOWS), + SettingsPermissionsGuard(PermissionFlagType.WORKFLOWS), ) @UsePipes(ResolverValidationPipe) @UseFilters( diff --git a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version.resolver.ts b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version.resolver.ts index e6e304d4d..50b2e48ff 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version.resolver.ts @@ -10,7 +10,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { WorkflowVersionWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service'; @@ -19,7 +19,7 @@ import { WorkflowVersionWorkspaceService } from 'src/modules/workflow/workflow-b @UseGuards( WorkspaceAuthGuard, UserAuthGuard, - SettingsPermissionsGuard(SettingPermissionType.WORKFLOWS), + SettingsPermissionsGuard(PermissionFlagType.WORKFLOWS), ) @UseFilters( PermissionsGraphqlApiExceptionFilter, diff --git a/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts b/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts index 5958767ea..dfadf8479 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts @@ -14,7 +14,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard'; import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @@ -23,7 +23,7 @@ import { SendInvitationsInput } from './dtos/send-invitations.input'; @UseGuards( WorkspaceAuthGuard, - SettingsPermissionsGuard(SettingPermissionType.WORKSPACE_MEMBERS), + SettingsPermissionsGuard(PermissionFlagType.WORKSPACE_MEMBERS), ) @UsePipes(ResolverValidationPipe) @UseFilters( diff --git a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts index fd649cf34..23a2e4ec8 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts @@ -36,7 +36,7 @@ import { WorkspaceExceptionCode, } from 'src/engine/core-modules/workspace/workspace.exception'; import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsException, PermissionsExceptionCode, @@ -457,7 +457,7 @@ export class WorkspaceService extends TypeOrmQueryService { const userHasPermission = await this.permissionsService.userHasWorkspaceSettingPermission({ userWorkspaceId, - setting: SettingPermissionType.SECURITY, + setting: PermissionFlagType.SECURITY, workspaceId: workspaceId, isExecutedByApiKey: isDefined(apiKey), }); @@ -504,7 +504,7 @@ export class WorkspaceService extends TypeOrmQueryService { await this.permissionsService.userHasWorkspaceSettingPermission({ userWorkspaceId, workspaceId, - setting: SettingPermissionType.WORKSPACE, + setting: PermissionFlagType.WORKSPACE, isExecutedByApiKey: isDefined(apiKey), }); diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts index 55b88eb67..ce2e6f965 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts @@ -58,7 +58,7 @@ import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { AgentService } from 'src/engine/metadata-modules/agent/agent.service'; import { AgentDTO } from 'src/engine/metadata-modules/agent/dtos/agent.dto'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto'; import { RoleService } from 'src/engine/metadata-modules/role/role.service'; @@ -144,7 +144,7 @@ export class WorkspaceResolver { @Mutation(() => SignedFileDTO) @UseGuards( WorkspaceAuthGuard, - SettingsPermissionsGuard(SettingPermissionType.WORKSPACE), + SettingsPermissionsGuard(PermissionFlagType.WORKSPACE), ) async uploadWorkspaceLogo( @AuthWorkspace() { id }: Workspace, @@ -190,7 +190,7 @@ export class WorkspaceResolver { @Mutation(() => Workspace) @UseGuards( WorkspaceAuthGuard, - SettingsPermissionsGuard(SettingPermissionType.WORKSPACE), + SettingsPermissionsGuard(PermissionFlagType.WORKSPACE), ) async deleteCurrentWorkspace(@AuthWorkspace() { id }: Workspace) { return this.workspaceService.deleteWorkspace(id); diff --git a/packages/twenty-server/src/engine/guards/settings-permissions.guard.ts b/packages/twenty-server/src/engine/guards/settings-permissions.guard.ts index 42d9e58da..de000810d 100644 --- a/packages/twenty-server/src/engine/guards/settings-permissions.guard.ts +++ b/packages/twenty-server/src/engine/guards/settings-permissions.guard.ts @@ -10,7 +10,7 @@ import { GqlExecutionContext } from '@nestjs/graphql'; import { isDefined } from 'twenty-shared/utils'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsException, PermissionsExceptionCode, @@ -19,7 +19,7 @@ import { import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; export const SettingsPermissionsGuard = ( - requiredPermission: SettingPermissionType, + requiredPermission: PermissionFlagType, ): Type => { @Injectable() class SettingsPermissionsMixin implements CanActivate { diff --git a/packages/twenty-server/src/engine/metadata-modules/agent/agent-tool.service.ts b/packages/twenty-server/src/engine/metadata-modules/agent/agent-tool.service.ts index 0ce793f2d..d98675553 100644 --- a/packages/twenty-server/src/engine/metadata-modules/agent/agent-tool.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/agent/agent-tool.service.ts @@ -26,9 +26,9 @@ export class AgentToolService { try { const agent = await this.agentService.findOneAgent(agentId, workspaceId); - const actionTools = this.toolAdapterService.getCoreTools(); - if (!agent.roleId) { + const actionTools = await this.toolAdapterService.getTools(); + return actionTools; } @@ -43,6 +43,11 @@ export class AgentToolService { return {}; } + const actionTools = await this.toolAdapterService.getTools( + role.id, + workspaceId, + ); + const databaseTools = await this.toolService.listTools( role.id, workspaceId, diff --git a/packages/twenty-server/src/engine/metadata-modules/agent/constants/agent-system-prompts.const.ts b/packages/twenty-server/src/engine/metadata-modules/agent/constants/agent-system-prompts.const.ts index 624aecb16..58ee569f6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/agent/constants/agent-system-prompts.const.ts +++ b/packages/twenty-server/src/engine/metadata-modules/agent/constants/agent-system-prompts.const.ts @@ -1,63 +1,24 @@ export const AGENT_SYSTEM_PROMPTS = { - AGENT_EXECUTION: `You are an AI agent node in a workflow builder system with access to comprehensive database operations and the ability to make HTTP requests. Your role is to process inputs, execute actions using available tools, and provide structured outputs that can be used by subsequent workflow nodes. + AGENT_EXECUTION: `You are an AI agent with access to various tools that will be provided to you dynamically. The available tools and their descriptions are passed to you through the tools property, so you should only use tools that are actually available to you. -AVAILABLE TOOLS: -You have access to: -- DATABASE OPERATIONS: Full CRUD operations for all standard objects in the system (see below) -- HTTP REQUESTS: Use the http_request tool to make HTTP calls to external APIs or services - -DATABASE OPERATIONS: -- CREATE: create_[object] - Create new records (e.g., create_person, create_company, create_opportunity) -- READ: find_[object] and find_one_[object] - Search and retrieve records -- UPDATE: update_[object] - Modify existing records -- DELETE: soft_delete_[object] and destroy_[object] - Remove records (soft or permanent) - -Common objects include: person, company, opportunity, task, note etc. and any custom objects. - -HTTP REQUEST TOOL: -- Use the http_request tool when the user asks you to call an external API, fetch data from a web service, or interact with a remote endpoint. -- You must provide a clear toolDescription and specify the input (url, method, headers, body) as required by the tool schema. -- Only use the http_request tool for actual HTTP/API calls. Do not simulate or describe them if the tool is not available. -- Always verify tool results and handle errors appropriately. If an HTTP request fails, explain the issue and suggest alternatives if possible. +TOOL USAGE GUIDELINES (applies to all tools): +- Only use a tool if it is available and you have permission. +- Always verify tool results and handle errors appropriately. +- If a tool operation fails, explain the issue and suggest alternatives. +- If you lack permission for a tool, respond: "I cannot perform this operation because I don't have the necessary permissions. Please check that I have been assigned the appropriate role for this workspace." Your responsibilities: 1. Analyze the input context and prompt carefully -2. If the request involves database operations (create, read, update, delete), check if you have the required tools available -3. If the request involves making an HTTP request, check if the http_request tool is available -4. If a requested tool is NOT available, state that you lack permissions for that specific operation. You can respond with: - "I cannot perform this operation because I don't have the necessary permissions. Please check that I have been assigned the appropriate role for this workspace." -5. If tools ARE available, use them to perform the requested operations -6. If no tool operations are needed, process the request directly with your analysis -7. Provide comprehensive responses that include all relevant information and context +2. If a requested tool is not available, state the limitation as above +3. If no tool operations are needed, process the request directly +4. Provide comprehensive, structured responses for workflow consumption Workflow context: - - You are part of a larger workflow system where your output may be used by other nodes - - Maintain consistency and reliability in your responses - - Consider the broader workflow context when making decisions - - If you encounter data or perform actions, document them clearly in your response +- You are part of a larger workflow system; your output may be used by other nodes +- Maintain consistency and reliability in your responses +- Document any data or actions clearly -Tool usage guidelines: - - Use tools for database operations or HTTP requests when requested - do not simulate or describe them - - Use create_[object] tools when asked to create new records - - Use find_[object] tools when asked to search or retrieve records - - Use update_[object] tools when asked to modify existing records - - Use soft_delete_[object] or destroy_[object] when asked to remove records - - Use the http_request tool when asked to call an external API or perform an HTTP operation - - Always verify tool results and handle errors appropriately - - Provide context about what tools you used and why - - If a tool fails, explain the issue and suggest alternatives - -Permission handling: - - Only check for permissions when tool operations are actually requested - - If you don't have the necessary tools for a requested operation, clearly state the limitation - - For non-tool requests, proceed normally without permission checks - -Important: After your response, the system will call generateObject to convert your output into a structured format according to a specific schema. Therefore: - - Provide comprehensive information in your response - - Include all relevant data you've gathered or processed - - Structure your response logically so it can be easily parsed - - Mention any important context, decisions, or actions taken - - Include tool execution results in your response`, +Important: After your response, the system will call generateObject to convert your output into a structured format. Ensure your response is comprehensive, logically structured, and includes all relevant data and tool results.`, OUTPUT_GENERATOR: `You are a structured output generator for a workflow system. Your role is to convert the provided execution results into a structured format according to a specific schema. @@ -84,14 +45,14 @@ Guidelines: - Provide insights, support, and updates about people, companies, opportunities, tasks, notes, and other business objects. - Access and summarize information you have permission to see - Help users understand how to use the system and its features -- Make HTTP requests to external APIs or services using the http_request tool when asked +- Use various tools that are provided to you dynamically when needed Permissions and capabilities: - You can only perform actions and access data that your assigned role and permissions allow - If a user requests something you do not have permission for, politely explain the limitation (e.g., "I cannot perform this operation because I don't have the necessary permissions. Please check your role or contact an admin.") - If you are unsure about your permissions for a specific action, ask the user for clarification or suggest they check with an administrator - Do not attempt to simulate or fake actions you cannot perform -- If you do not have access to the http_request tool, explain that you cannot make HTTP requests +- Only use tools that are actually available to you through the tools property If you need more information to answer a question, ask follow-up questions. Always be transparent about your capabilities and limitations. diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts index ebad2db19..9b1021c42 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts @@ -41,7 +41,7 @@ import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util'; import { fromFieldMetadataEntityToFieldMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-field-metadata-dto.util'; import { fromObjectMetadataEntityToObjectMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-object-metadata-entity-to-object-metadata-dto.util'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { isMorphRelationFieldMetadataType } from 'src/engine/utils/is-morph-relation-field-metadata-type.util'; import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; @@ -59,7 +59,7 @@ export class FieldMetadataResolver { private readonly beforeUpdateOneField: BeforeUpdateOneField, ) {} - @UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL)) + @UseGuards(SettingsPermissionsGuard(PermissionFlagType.DATA_MODEL)) @Mutation(() => FieldMetadataDTO) async createOneField( @Args('input') input: CreateOneFieldMetadataInput, @@ -75,7 +75,7 @@ export class FieldMetadataResolver { } } - @UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL)) + @UseGuards(SettingsPermissionsGuard(PermissionFlagType.DATA_MODEL)) @Mutation(() => FieldMetadataDTO) async updateOneField( @Args('input') input: UpdateOneFieldMetadataInput, @@ -97,7 +97,7 @@ export class FieldMetadataResolver { } } - @UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL)) + @UseGuards(SettingsPermissionsGuard(PermissionFlagType.DATA_MODEL)) @Mutation(() => FieldMetadataDTO) async deleteOneField( @Args('input') input: DeleteOneFieldInput, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts index 0cb9ab9e5..b2102d4fa 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.module.ts @@ -21,7 +21,7 @@ import { ObjectMetadataResolver } from 'src/engine/metadata-modules/object-metad import { ObjectMetadataFieldRelationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service'; import { ObjectMetadataMigrationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-migration.service'; import { ObjectMetadataRelatedRecordsService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-related-records.service'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { RemoteTableRelationsModule } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.module'; @@ -83,9 +83,7 @@ import { UpdateObjectPayload } from './dtos/update-object.input'; }, create: { many: { disabled: true }, - guards: [ - SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL), - ], + guards: [SettingsPermissionsGuard(PermissionFlagType.DATA_MODEL)], }, update: { disabled: true }, delete: { disabled: true }, diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts index a9f6157b8..7cc575bc9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts @@ -28,7 +28,7 @@ import { BeforeUpdateOneObject } from 'src/engine/metadata-modules/object-metada import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { objectMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util'; import { resolveObjectMetadataStandardOverride } from 'src/engine/metadata-modules/object-metadata/utils/resolve-object-metadata-standard-override.util'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; @UseGuards(WorkspaceAuthGuard) @@ -80,7 +80,7 @@ export class ObjectMetadataResolver { ); } - @UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL)) + @UseGuards(SettingsPermissionsGuard(PermissionFlagType.DATA_MODEL)) @ResolveField(() => String, { nullable: true }) async icon( @Parent() objectMetadata: ObjectMetadataDTO, @@ -93,7 +93,7 @@ export class ObjectMetadataResolver { ); } - @UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL)) + @UseGuards(SettingsPermissionsGuard(PermissionFlagType.DATA_MODEL)) @Mutation(() => ObjectMetadataDTO) async deleteOneObject( @Args('input') input: DeleteOneObjectInput, @@ -109,7 +109,7 @@ export class ObjectMetadataResolver { } } - @UseGuards(SettingsPermissionsGuard(SettingPermissionType.DATA_MODEL)) + @UseGuards(SettingsPermissionsGuard(PermissionFlagType.DATA_MODEL)) @Mutation(() => ObjectMetadataDTO) async updateOneObject( @Args('input') input: UpdateOneObjectInput, diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto.ts new file mode 100644 index 000000000..592f18c6e --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto.ts @@ -0,0 +1,15 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; + +@ObjectType('PermissionFlag') +export class PermissionFlagDTO { + @Field({ nullable: false }) + id: string; + + @Field({ nullable: false }) + roleId: string; + + @Field({ nullable: false }) + flag: PermissionFlagType; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/upsert-permission-flag-input.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/upsert-permission-flag-input.ts new file mode 100644 index 000000000..f84b8b3d9 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/dtos/upsert-permission-flag-input.ts @@ -0,0 +1,18 @@ +import { Field, InputType } from '@nestjs/graphql'; + +import { IsArray, IsEnum, IsNotEmpty, IsUUID } from 'class-validator'; + +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; + +@InputType() +export class UpsertPermissionFlagsInput { + @IsUUID() + @IsNotEmpty() + @Field() + roleId: string; + + @IsArray() + @IsEnum(PermissionFlagType, { each: true }) + @Field(() => [PermissionFlagType]) + permissionFlagKeys: PermissionFlagType[]; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/setting-permission/setting-permission.entity.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.entity.ts similarity index 64% rename from packages/twenty-server/src/engine/metadata-modules/setting-permission/setting-permission.entity.ts rename to packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.entity.ts index dd135b77f..8619798b7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/setting-permission/setting-permission.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.entity.ts @@ -10,26 +10,26 @@ import { UpdateDateColumn, } from 'typeorm'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; -@Entity('settingPermission') -@Unique('IDX_SETTING_PERMISSION_SETTING_ROLE_ID_UNIQUE', ['setting', 'roleId']) -export class SettingPermissionEntity { +@Entity('permissionFlag') +@Unique('IDX_PERMISSION_FLAG_FLAG_ROLE_ID_UNIQUE', ['flag', 'roleId']) +export class PermissionFlagEntity { @PrimaryGeneratedColumn('uuid') id: string; @Column({ nullable: false, type: 'uuid' }) roleId: string; - @ManyToOne(() => RoleEntity, (role) => role.settingPermissions, { + @ManyToOne(() => RoleEntity, (role) => role.permissionFlags, { onDelete: 'CASCADE', }) @JoinColumn({ name: 'roleId' }) role: Relation; @Column({ nullable: false, type: 'varchar' }) - setting: SettingPermissionType; + flag: PermissionFlagType; @Column({ nullable: false, type: 'uuid' }) workspaceId: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.module.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.module.ts new file mode 100644 index 000000000..9b8be9e04 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; +import { PermissionFlagService } from 'src/engine/metadata-modules/permission-flag/permission-flag.service'; +import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; +import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([PermissionFlagEntity, RoleEntity], 'core'), + WorkspacePermissionsCacheModule, + ], + + providers: [PermissionFlagService], + exports: [PermissionFlagService], +}) +export class PermissionFlagModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/setting-permission/setting-permission.service.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.service.ts similarity index 65% rename from packages/twenty-server/src/engine/metadata-modules/setting-permission/setting-permission.service.ts rename to packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.service.ts index 4d995cfca..2acb01a5a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/setting-permission/setting-permission.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.service.ts @@ -3,21 +3,21 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; import { DataSource, In, Repository } from 'typeorm'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { UpsertPermissionFlagsInput } from 'src/engine/metadata-modules/permission-flag/dtos/upsert-permission-flag-input'; +import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsException, PermissionsExceptionCode, PermissionsExceptionMessage, } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; -import { UpsertSettingPermissionsInput } from 'src/engine/metadata-modules/setting-permission/dtos/upsert-setting-permission-input'; -import { SettingPermissionEntity } from 'src/engine/metadata-modules/setting-permission/setting-permission.entity'; import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service'; -export class SettingPermissionService { +export class PermissionFlagService { constructor( - @InjectRepository(SettingPermissionEntity, 'core') - private readonly settingPermissionRepository: Repository, + @InjectRepository(PermissionFlagEntity, 'core') + private readonly permissionFlagRepository: Repository, @InjectRepository(RoleEntity, 'core') private readonly roleRepository: Repository, @InjectDataSource('core') @@ -25,25 +25,25 @@ export class SettingPermissionService { private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService, ) {} - public async upsertSettingPermissions({ + public async upsertPermissionFlags({ workspaceId, input, }: { workspaceId: string; - input: UpsertSettingPermissionsInput; - }): Promise { + input: UpsertPermissionFlagsInput; + }): Promise { await this.validateRoleIsEditableOrThrow({ roleId: input.roleId, workspaceId, }); - const invalidSettings = input.settingPermissionKeys.filter( - (setting) => !Object.values(SettingPermissionType).includes(setting), + const invalidFlags = input.permissionFlagKeys.filter( + (flag) => !Object.values(PermissionFlagType).includes(flag), ); - if (invalidSettings.length > 0) { + if (invalidFlags.length > 0) { throw new PermissionsException( - `${PermissionsExceptionMessage.INVALID_SETTING}: ${invalidSettings.join(', ')}`, + `${PermissionsExceptionMessage.INVALID_SETTING}: ${invalidFlags.join(', ')}`, PermissionsExceptionCode.INVALID_SETTING, ); } @@ -55,7 +55,7 @@ export class SettingPermissionService { try { const existingPermissions = await queryRunner.manager.find( - SettingPermissionEntity, + PermissionFlagEntity, { where: { roleId: input.roleId, @@ -63,41 +63,39 @@ export class SettingPermissionService { }, }, ); - const existingSettings = new Set( - existingPermissions.map((p) => p.setting), - ); - const inputSettings = new Set(input.settingPermissionKeys); + const existingSettings = new Set(existingPermissions.map((p) => p.flag)); + const inputSettings = new Set(input.permissionFlagKeys); - const settingsToAdd = input.settingPermissionKeys.filter( + const flagsToAdd = input.permissionFlagKeys.filter( (setting) => !existingSettings.has(setting), ); const permissionsToRemove = existingPermissions.filter( - (permission) => !inputSettings.has(permission.setting), + (permission) => !inputSettings.has(permission.flag), ); if (permissionsToRemove.length > 0) { - await queryRunner.manager.delete(SettingPermissionEntity, { + await queryRunner.manager.delete(PermissionFlagEntity, { id: In(permissionsToRemove.map((p) => p.id)), }); } - if (settingsToAdd.length > 0) { - const newPermissions = settingsToAdd.map((setting) => - queryRunner.manager.create(SettingPermissionEntity, { + if (flagsToAdd.length > 0) { + const newPermissions = flagsToAdd.map((flag) => + queryRunner.manager.create(PermissionFlagEntity, { workspaceId, roleId: input.roleId, - setting, + flag, }), ); - await queryRunner.manager.save(SettingPermissionEntity, newPermissions); + await queryRunner.manager.save(PermissionFlagEntity, newPermissions); } await queryRunner.commitTransaction(); - return queryRunner.manager.find(SettingPermissionEntity, { + return queryRunner.manager.find(PermissionFlagEntity, { where: { roleId: input.roleId, workspaceId }, - order: { setting: 'ASC' }, + order: { flag: 'ASC' }, }); } catch (error) { await queryRunner.rollbackTransaction(); diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/constants/setting-permission-type.constants.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/constants/permission-flag-type.constants.ts similarity index 67% rename from packages/twenty-server/src/engine/metadata-modules/permissions/constants/setting-permission-type.constants.ts rename to packages/twenty-server/src/engine/metadata-modules/permissions/constants/permission-flag-type.constants.ts index 2368d8675..128d2245e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/constants/setting-permission-type.constants.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/constants/permission-flag-type.constants.ts @@ -1,4 +1,5 @@ -export enum SettingPermissionType { +export enum PermissionFlagType { + // Settings permissions API_KEYS_AND_WEBHOOKS = 'API_KEYS_AND_WEBHOOKS', WORKSPACE = 'WORKSPACE', WORKSPACE_MEMBERS = 'WORKSPACE_MEMBERS', @@ -7,4 +8,7 @@ export enum SettingPermissionType { ADMIN_PANEL = 'ADMIN_PANEL', SECURITY = 'SECURITY', WORKFLOWS = 'WORKFLOWS', + + // Tool permissions + SEND_EMAIL_TOOL = 'SEND_EMAIL_TOOL', } diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts index 58e8d8814..f408ca0f9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts @@ -1,19 +1,22 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants'; import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; import { AuthException, AuthExceptionCode, } from 'src/engine/core-modules/auth/auth.exception'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsException, PermissionsExceptionCode, PermissionsExceptionMessage, } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { UserWorkspacePermissions } from 'src/engine/metadata-modules/permissions/types/user-workspace-permissions'; +import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service'; @@ -22,6 +25,8 @@ export class PermissionsService { constructor( private readonly userRoleService: UserRoleService, private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService, + @InjectRepository(RoleEntity, 'core') + private readonly roleRepository: Repository, ) {} public async getUserWorkspacePermissions({ @@ -51,17 +56,17 @@ export class PermissionsService { hasPermissionOnSettingFeature = true; } - const settingPermissions = roleOfUserWorkspace.settingPermissions ?? []; + const permissionFlags = roleOfUserWorkspace.permissionFlags ?? []; const defaultSettingsPermissions = this.getDefaultUserWorkspacePermissions().settingsPermissions; - const settingsPermissions = Object.keys(SettingPermissionType).reduce( + const settingsPermissions = Object.keys(PermissionFlagType).reduce( (acc, feature) => ({ ...acc, [feature]: hasPermissionOnSettingFeature || - settingPermissions.some( - (settingPermission) => settingPermission.setting === feature, + permissionFlags.some( + (permissionFlag) => permissionFlag.flag === feature, ), }), defaultSettingsPermissions, @@ -102,14 +107,15 @@ export class PermissionsService { [PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS]: false, }, settingsPermissions: { - [SettingPermissionType.API_KEYS_AND_WEBHOOKS]: false, - [SettingPermissionType.WORKSPACE]: false, - [SettingPermissionType.WORKSPACE_MEMBERS]: false, - [SettingPermissionType.ROLES]: false, - [SettingPermissionType.DATA_MODEL]: false, - [SettingPermissionType.ADMIN_PANEL]: false, - [SettingPermissionType.SECURITY]: false, - [SettingPermissionType.WORKFLOWS]: false, + [PermissionFlagType.API_KEYS_AND_WEBHOOKS]: false, + [PermissionFlagType.WORKSPACE]: false, + [PermissionFlagType.WORKSPACE_MEMBERS]: false, + [PermissionFlagType.ROLES]: false, + [PermissionFlagType.DATA_MODEL]: false, + [PermissionFlagType.ADMIN_PANEL]: false, + [PermissionFlagType.SECURITY]: false, + [PermissionFlagType.WORKFLOWS]: false, + [PermissionFlagType.SEND_EMAIL_TOOL]: false, }, objectPermissions: {}, }) as const satisfies UserWorkspacePermissions; @@ -122,7 +128,7 @@ export class PermissionsService { }: { userWorkspaceId?: string; workspaceId: string; - setting: SettingPermissionType; + setting: PermissionFlagType; isExecutedByApiKey: boolean; }): Promise { if (isExecutedByApiKey) { @@ -154,10 +160,39 @@ export class PermissionsService { return true; } - const settingPermissions = roleOfUserWorkspace.settingPermissions ?? []; + const permissionFlags = roleOfUserWorkspace.permissionFlags ?? []; - return settingPermissions.some( - (settingPermission) => settingPermission.setting === setting, + return permissionFlags.some( + (permissionFlag) => permissionFlag.flag === setting, ); } + + public async hasToolPermission( + roleId: string, + workspaceId: string, + flag: PermissionFlagType, + ): Promise { + try { + const role = await this.roleRepository.findOne({ + where: { id: roleId, workspaceId }, + relations: ['permissionFlags'], + }); + + if (!role) { + return false; + } + + if (role.canAccessAllTools === true) { + return true; + } + + const permissionFlags = role.permissionFlags ?? []; + + return permissionFlags.some( + (permissionFlag) => permissionFlag.flag === flag, + ); + } catch (error) { + return false; + } + } } diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/types/user-workspace-permissions.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/types/user-workspace-permissions.ts index 473873c3f..731ac7084 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/types/user-workspace-permissions.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/types/user-workspace-permissions.ts @@ -1,10 +1,10 @@ import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants'; import { ObjectRecordsPermissions } from 'twenty-shared/types'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; export type UserWorkspacePermissions = { - settingsPermissions: Record; + settingsPermissions: Record; objectRecordsPermissions: Record; objectPermissions: ObjectRecordsPermissions; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/role/dtos/create-role-input.dto.ts b/packages/twenty-server/src/engine/metadata-modules/role/dtos/create-role-input.dto.ts index eaf68cb50..0426e9141 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/dtos/create-role-input.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/dtos/create-role-input.dto.ts @@ -28,6 +28,11 @@ export class CreateRoleInput { @Field({ nullable: true }) canUpdateAllSettings?: boolean; + @IsBoolean() + @IsOptional() + @Field({ nullable: true }) + canAccessAllTools?: boolean; + @IsBoolean() @IsOptional() @Field({ nullable: true }) diff --git a/packages/twenty-server/src/engine/metadata-modules/role/dtos/role.dto.ts b/packages/twenty-server/src/engine/metadata-modules/role/dtos/role.dto.ts index e499a9be8..3b527a3b0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/dtos/role.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/dtos/role.dto.ts @@ -4,8 +4,8 @@ import { Relation } from 'typeorm'; import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto'; import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto'; +import { PermissionFlagDTO } from 'src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto'; import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity'; -import { SettingPermissionDTO } from 'src/engine/metadata-modules/setting-permission/dtos/setting-permission.dto'; @ObjectType('Role') export class RoleDTO { @@ -33,6 +33,9 @@ export class RoleDTO { @Field({ nullable: false }) canUpdateAllSettings: boolean; + @Field({ nullable: false }) + canAccessAllTools: boolean; + @Field({ nullable: false }) canReadAllObjectRecords: boolean; @@ -45,8 +48,8 @@ export class RoleDTO { @Field({ nullable: false }) canDestroyAllObjectRecords: boolean; - @Field(() => [SettingPermissionDTO], { nullable: true }) - settingPermissions?: SettingPermissionDTO[]; + @Field(() => [PermissionFlagDTO], { nullable: true }) + permissionFlags?: PermissionFlagDTO[]; @Field(() => [ObjectPermissionDTO], { nullable: true }) objectPermissions?: ObjectPermissionDTO[]; diff --git a/packages/twenty-server/src/engine/metadata-modules/role/dtos/update-role-input.dto.ts b/packages/twenty-server/src/engine/metadata-modules/role/dtos/update-role-input.dto.ts index c355f77c4..b10d9490d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/dtos/update-role-input.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/dtos/update-role-input.dto.ts @@ -32,6 +32,11 @@ export class UpdateRolePayload { @Field({ nullable: true }) canUpdateAllSettings?: boolean; + @IsBoolean() + @IsOptional() + @Field({ nullable: true }) + canAccessAllTools?: boolean; + @IsBoolean() @IsOptional() @Field({ nullable: true }) diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.entity.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.entity.ts index 18951be97..016fe1877 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.entity.ts @@ -11,8 +11,8 @@ import { import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity'; import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity'; +import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity'; -import { SettingPermissionEntity } from 'src/engine/metadata-modules/setting-permission/setting-permission.entity'; @Entity('role') @Unique('IDX_ROLE_LABEL_WORKSPACE_ID_UNIQUE', ['label', 'workspaceId']) @@ -26,6 +26,9 @@ export class RoleEntity { @Column({ nullable: false, default: false }) canUpdateAllSettings: boolean; + @Column({ nullable: false, default: false }) + canAccessAllTools: boolean; + @Column({ nullable: false, default: false }) canReadAllObjectRecords: boolean; @@ -69,10 +72,10 @@ export class RoleEntity { objectPermissions: Relation; @OneToMany( - () => SettingPermissionEntity, - (settingPermission: SettingPermissionEntity) => settingPermission.role, + () => PermissionFlagEntity, + (permissionFlag: PermissionFlagEntity) => permissionFlag.role, ) - settingPermissions: Relation; + permissionFlags: Relation; @OneToMany( () => FieldPermissionEntity, diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts index d4e7e8c3f..f5b03a26c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts @@ -7,12 +7,12 @@ import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { AgentRoleModule } from 'src/engine/metadata-modules/agent-role/agent-role.module'; import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permission/object-permission.module'; +import { PermissionFlagModule } from 'src/engine/metadata-modules/permission-flag/permission-flag.module'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { RoleResolver } from 'src/engine/metadata-modules/role/role.resolver'; import { RoleService } from 'src/engine/metadata-modules/role/role.service'; -import { SettingPermissionModule } from 'src/engine/metadata-modules/setting-permission/setting-permission.module'; import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module'; import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module'; @@ -25,7 +25,7 @@ import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/wor PermissionsModule, UserWorkspaceModule, ObjectPermissionModule, - SettingPermissionModule, + PermissionFlagModule, WorkspacePermissionsCacheModule, FileModule, ], diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts index 23f9fb39f..b443755e2 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts @@ -28,7 +28,10 @@ import { UpsertFieldPermissionsInput } from 'src/engine/metadata-modules/object- import { UpsertObjectPermissionsInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-object-permissions.input'; import { FieldPermissionService } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.service'; import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagDTO } from 'src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto'; +import { UpsertPermissionFlagsInput } from 'src/engine/metadata-modules/permission-flag/dtos/upsert-permission-flag-input'; +import { PermissionFlagService } from 'src/engine/metadata-modules/permission-flag/permission-flag.service'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsException, PermissionsExceptionCode, @@ -39,9 +42,6 @@ import { CreateRoleInput } from 'src/engine/metadata-modules/role/dtos/create-ro import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto'; import { UpdateRoleInput } from 'src/engine/metadata-modules/role/dtos/update-role-input.dto'; import { RoleService } from 'src/engine/metadata-modules/role/role.service'; -import { SettingPermissionDTO } from 'src/engine/metadata-modules/setting-permission/dtos/setting-permission.dto'; -import { UpsertSettingPermissionsInput } from 'src/engine/metadata-modules/setting-permission/dtos/upsert-setting-permission-input'; -import { SettingPermissionService } from 'src/engine/metadata-modules/setting-permission/setting-permission.service'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; @@ -49,7 +49,7 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta @UsePipes(ResolverValidationPipe) @UseGuards( WorkspaceAuthGuard, - SettingsPermissionsGuard(SettingPermissionType.ROLES), + SettingsPermissionsGuard(PermissionFlagType.ROLES), ) @UseFilters( PermissionsGraphqlApiExceptionFilter, @@ -61,7 +61,7 @@ export class RoleResolver { private readonly roleService: RoleService, private readonly userWorkspaceService: UserWorkspaceService, private readonly objectPermissionService: ObjectPermissionService, - private readonly settingPermissionService: SettingPermissionService, + private readonly settingPermissionService: PermissionFlagService, private readonly agentRoleService: AgentRoleService, private readonly fieldPermissionService: FieldPermissionService, ) {} @@ -171,15 +171,15 @@ export class RoleResolver { }); } - @Mutation(() => [SettingPermissionDTO]) - async upsertSettingPermissions( + @Mutation(() => [PermissionFlagDTO]) + async upsertPermissionFlags( @AuthWorkspace() workspace: Workspace, - @Args('upsertSettingPermissionsInput') - upsertSettingPermissionsInput: UpsertSettingPermissionsInput, - ): Promise { - return this.settingPermissionService.upsertSettingPermissions({ + @Args('upsertPermissionFlagsInput') + upsertPermissionFlagsInput: UpsertPermissionFlagsInput, + ): Promise { + return this.settingPermissionService.upsertPermissionFlags({ workspaceId: workspace.id, - input: upsertSettingPermissionsInput, + input: upsertPermissionFlagsInput, }); } diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts index 47e609e80..b53fd0730 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts @@ -40,7 +40,7 @@ export class RoleService { where: { workspaceId, }, - relations: ['roleTargets', 'settingPermissions', 'objectPermissions'], + relations: ['roleTargets', 'permissionFlags', 'objectPermissions'], }); } @@ -53,7 +53,7 @@ export class RoleService { id, workspaceId, }, - relations: ['roleTargets', 'settingPermissions'], + relations: ['roleTargets', 'permissionFlags'], }); } @@ -72,6 +72,7 @@ export class RoleService { description: input.description, icon: input.icon, canUpdateAllSettings: input.canUpdateAllSettings, + canAccessAllTools: input.canAccessAllTools, canReadAllObjectRecords: input.canReadAllObjectRecords, canUpdateAllObjectRecords: input.canUpdateAllObjectRecords, canSoftDeleteAllObjectRecords: input.canSoftDeleteAllObjectRecords, @@ -143,6 +144,7 @@ export class RoleService { description: 'Admin role', icon: 'IconUserCog', canUpdateAllSettings: true, + canAccessAllTools: true, canReadAllObjectRecords: true, canUpdateAllObjectRecords: true, canSoftDeleteAllObjectRecords: true, @@ -209,6 +211,7 @@ export class RoleService { description: 'Member role', icon: 'IconUser', canUpdateAllSettings: false, + canAccessAllTools: false, canReadAllObjectRecords: true, canUpdateAllObjectRecords: true, canSoftDeleteAllObjectRecords: true, @@ -229,6 +232,7 @@ export class RoleService { description: 'Guest role', icon: 'IconUser', canUpdateAllSettings: false, + canAccessAllTools: false, canReadAllObjectRecords: true, canUpdateAllObjectRecords: false, canSoftDeleteAllObjectRecords: false, @@ -250,6 +254,7 @@ export class RoleService { const keysToValidate = [ 'label', 'canUpdateAllSettings', + 'canAccessAllTools', 'canReadAllObjectRecords', 'canUpdateAllObjectRecords', 'canSoftDeleteAllObjectRecords', diff --git a/packages/twenty-server/src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util.ts b/packages/twenty-server/src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util.ts index 7b5f23241..cc8a300ee 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util.ts @@ -6,6 +6,7 @@ export const fromRoleEntityToRoleDto = (role: RoleEntity): RoleDTO => { id: role.id, label: role.label, canUpdateAllSettings: role.canUpdateAllSettings, + canAccessAllTools: role.canAccessAllTools, description: role.description, icon: role.icon, isEditable: role.isEditable, diff --git a/packages/twenty-server/src/engine/metadata-modules/role/utils/fromUserWorkspacePermissionsToUserWorkspacePermissionsDto.ts b/packages/twenty-server/src/engine/metadata-modules/role/utils/fromUserWorkspacePermissionsToUserWorkspacePermissionsDto.ts index b73b773ec..a6d64cf12 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/utils/fromUserWorkspacePermissionsToUserWorkspacePermissionsDto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/utils/fromUserWorkspacePermissionsToUserWorkspacePermissionsDto.ts @@ -1,6 +1,6 @@ import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { UserWorkspacePermissions } from 'src/engine/metadata-modules/permissions/types/user-workspace-permissions'; import { UserWorkspacePermissionsDto } from 'src/engine/metadata-modules/role/dtos/user-workspace-permissions.dto'; @@ -20,7 +20,7 @@ export const fromUserWorkspacePermissionsToUserWorkspacePermissionsDto = ({ ); const settingsPermissions = ( - Object.keys(rawSettingsPermissions) as SettingPermissionType[] + Object.keys(rawSettingsPermissions) as PermissionFlagType[] ).filter((feature) => rawSettingsPermissions[feature] === true); const objectRecordsPermissions = ( diff --git a/packages/twenty-server/src/engine/metadata-modules/setting-permission/dtos/setting-permission.dto.ts b/packages/twenty-server/src/engine/metadata-modules/setting-permission/dtos/setting-permission.dto.ts deleted file mode 100644 index ed2cf2894..000000000 --- a/packages/twenty-server/src/engine/metadata-modules/setting-permission/dtos/setting-permission.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Field, ObjectType } from '@nestjs/graphql'; - -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; - -@ObjectType('SettingPermission') -export class SettingPermissionDTO { - @Field({ nullable: false }) - id: string; - - @Field({ nullable: false }) - roleId: string; - - @Field({ nullable: false }) - setting: SettingPermissionType; -} diff --git a/packages/twenty-server/src/engine/metadata-modules/setting-permission/dtos/upsert-setting-permission-input.ts b/packages/twenty-server/src/engine/metadata-modules/setting-permission/dtos/upsert-setting-permission-input.ts deleted file mode 100644 index c7f679e4f..000000000 --- a/packages/twenty-server/src/engine/metadata-modules/setting-permission/dtos/upsert-setting-permission-input.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Field, InputType } from '@nestjs/graphql'; - -import { IsArray, IsEnum, IsNotEmpty, IsUUID } from 'class-validator'; - -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; - -@InputType() -export class UpsertSettingPermissionsInput { - @IsUUID() - @IsNotEmpty() - @Field() - roleId: string; - - @IsArray() - @IsEnum(SettingPermissionType, { each: true }) - @Field(() => [SettingPermissionType]) - settingPermissionKeys: SettingPermissionType[]; -} diff --git a/packages/twenty-server/src/engine/metadata-modules/setting-permission/setting-permission.module.ts b/packages/twenty-server/src/engine/metadata-modules/setting-permission/setting-permission.module.ts deleted file mode 100644 index 3fa3192b6..000000000 --- a/packages/twenty-server/src/engine/metadata-modules/setting-permission/setting-permission.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; -import { SettingPermissionEntity } from 'src/engine/metadata-modules/setting-permission/setting-permission.entity'; -import { SettingPermissionService } from 'src/engine/metadata-modules/setting-permission/setting-permission.service'; -import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module'; - -@Module({ - imports: [ - TypeOrmModule.forFeature([SettingPermissionEntity, RoleEntity], 'core'), - WorkspacePermissionsCacheModule, - ], - - providers: [SettingPermissionService], - exports: [SettingPermissionService], -}) -export class SettingPermissionModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts b/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts index 015a9deb7..7d9459d01 100644 --- a/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts @@ -105,7 +105,7 @@ export class UserRoleService { }, relations: { role: { - settingPermissions: true, + permissionFlags: true, }, }, }); diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service.ts index 583ac4861..b5fdc2ba7 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service.ts @@ -11,7 +11,7 @@ import { In, Repository } from 'typeorm'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { WorkspaceFeatureFlagsMapCacheService } from 'src/engine/metadata-modules/workspace-feature-flags-map-cache/workspace-feature-flags-map-cache.service'; @@ -184,7 +184,7 @@ export class WorkspacePermissionsCacheService { }, relations: [ 'objectPermissions', - 'settingPermissions', + 'permissionFlags', ...(isFieldPermissionsEnabled ? ['fieldPermissions'] : []), ], }); @@ -317,9 +317,9 @@ export class WorkspacePermissionsCacheService { private hasWorkflowsPermissions(role: RoleEntity): boolean { const hasWorkflowsPermissionFromRole = role.canUpdateAllSettings; const hasWorkflowsPermissionsFromSettingPermissions = isDefined( - role.settingPermissions.find( - (settingPermission) => - settingPermission.setting === SettingPermissionType.WORKFLOWS, + role.permissionFlags.find( + (permissionFlag) => + permissionFlag.flag === PermissionFlagType.WORKFLOWS, ), ); diff --git a/packages/twenty-server/src/engine/twenty-orm/twenty-orm.module.ts b/packages/twenty-server/src/engine/twenty-orm/twenty-orm.module.ts index 36a671931..9e2860c6e 100644 --- a/packages/twenty-server/src/engine/twenty-orm/twenty-orm.module.ts +++ b/packages/twenty-server/src/engine/twenty-orm/twenty-orm.module.ts @@ -13,6 +13,7 @@ import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/worksp import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module'; import { entitySchemaFactories } from 'src/engine/twenty-orm/factories'; import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory'; +import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; @@ -46,6 +47,7 @@ import { PgPoolSharedModule } from './pg-shared-pool/pg-shared-pool.module'; TwentyORMManager, TwentyORMGlobalManager, PgPoolSharedModule, + ScopedWorkspaceContextFactory, ], }) export class TwentyORMModule {} diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/messaging-import-manager.module.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/messaging-import-manager.module.ts index f0a0aa971..d4aa2a0ae 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/messaging-import-manager.module.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/messaging-import-manager.module.ts @@ -1,7 +1,6 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; @@ -54,7 +53,6 @@ import { MessagingMonitoringModule } from 'src/modules/messaging/monitoring/mess [Workspace, DataSourceEntity, ObjectMetadataEntity], 'core', ), - BillingModule, EmailAliasManagerModule, FeatureFlagModule, MessageParticipantManagerModule, diff --git a/packages/twenty-server/src/modules/messaging/messaging.module.ts b/packages/twenty-server/src/modules/messaging/messaging.module.ts index 24376567c..698f1ef99 100644 --- a/packages/twenty-server/src/modules/messaging/messaging.module.ts +++ b/packages/twenty-server/src/modules/messaging/messaging.module.ts @@ -15,6 +15,6 @@ import { MessagingMonitoringModule } from 'src/modules/messaging/monitoring/mess MessagingMonitoringModule, ], providers: [], - exports: [], + exports: [MessagingImportManagerModule], }) export class MessagingModule {} diff --git a/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts b/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts index 6d2cb7e6a..cde67c7ff 100644 --- a/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts +++ b/packages/twenty-server/src/modules/messaging/monitoring/messaging-monitoring.module.ts @@ -2,7 +2,6 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AuditModule } from 'src/engine/core-modules/audit/audit.module'; -import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module'; @@ -14,7 +13,6 @@ import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/ser imports: [ AuditModule, MessagingCommonModule, - BillingModule, TypeOrmModule.forFeature([Workspace], 'core'), TypeOrmModule.forFeature([DataSourceEntity], 'core'), ], diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/factories/workflow-action.factory.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/factories/workflow-action.factory.ts index a009e4728..dc282d6a8 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/factories/workflow-action.factory.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/factories/workflow-action.factory.ts @@ -10,26 +10,24 @@ import { AiAgentWorkflowAction } from 'src/modules/workflow/workflow-executor/wo import { CodeWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/code/code.workflow-action'; import { FilterWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/filter.workflow-action'; import { FormWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/form/form.workflow-action'; -import { SendEmailWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email.workflow-action'; import { CreateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action'; import { DeleteRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action'; import { FindRecordsWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action'; import { UpdateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action'; +import { ToolExecutorWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/tool-executor-workflow-action'; import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; -import { WorkflowActionAdapter } from 'src/modules/workflow/workflow-executor/workflow-actions/workflow-action-adapter'; @Injectable() export class WorkflowActionFactory { constructor( private readonly codeWorkflowAction: CodeWorkflowAction, - private readonly sendEmailWorkflowAction: SendEmailWorkflowAction, private readonly createRecordWorkflowAction: CreateRecordWorkflowAction, private readonly updateRecordWorkflowAction: UpdateRecordWorkflowAction, private readonly deleteRecordWorkflowAction: DeleteRecordWorkflowAction, private readonly findRecordsWorkflowAction: FindRecordsWorkflowAction, private readonly formWorkflowAction: FormWorkflowAction, private readonly filterWorkflowAction: FilterWorkflowAction, - private readonly workflowActionAdapter: WorkflowActionAdapter, + private readonly toolExecutorWorkflowAction: ToolExecutorWorkflowAction, private readonly aiAgentWorkflowAction: AiAgentWorkflowAction, ) {} @@ -38,7 +36,7 @@ export class WorkflowActionFactory { case WorkflowActionType.CODE: return this.codeWorkflowAction; case WorkflowActionType.SEND_EMAIL: - return this.sendEmailWorkflowAction; + return this.toolExecutorWorkflowAction; case WorkflowActionType.CREATE_RECORD: return this.createRecordWorkflowAction; case WorkflowActionType.UPDATE_RECORD: @@ -52,7 +50,7 @@ export class WorkflowActionFactory { case WorkflowActionType.FILTER: return this.filterWorkflowAction; case WorkflowActionType.HTTP_REQUEST: - return this.workflowActionAdapter; + return this.toolExecutorWorkflowAction; case WorkflowActionType.AI_AGENT: return this.aiAgentWorkflowAction; default: diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email-action.module.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email-action.module.ts deleted file mode 100644 index 79ed34926..000000000 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email-action.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; -import { MessagingImportManagerModule } from 'src/modules/messaging/message-import-manager/messaging-import-manager.module'; -import { SendEmailWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email.workflow-action'; - -@Module({ - imports: [MessagingImportManagerModule], - providers: [ScopedWorkspaceContextFactory, SendEmailWorkflowAction], - exports: [SendEmailWorkflowAction], -}) -export class SendEmailActionModule {} diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email.workflow-action.ts deleted file mode 100644 index c6e264f87..000000000 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email.workflow-action.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; - -import DOMPurify from 'dompurify'; -import { JSDOM } from 'jsdom'; -import { isDefined, isValidUuid } from 'twenty-shared/utils'; -import { z } from 'zod'; - -import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface'; - -import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; -import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { MessagingSendMessageService } from 'src/modules/messaging/message-import-manager/services/messaging-send-message.service'; -import { - WorkflowStepExecutorException, - WorkflowStepExecutorExceptionCode, -} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception'; -import { WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input'; -import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type'; -import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util'; -import { - SendEmailActionException, - SendEmailActionExceptionCode, -} from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/exceptions/send-email-action.exception'; -import { isWorkflowSendEmailAction } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/guards/is-workflow-send-email-action.guard'; -import { WorkflowSendEmailActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/types/workflow-send-email-action-input.type'; - -export type WorkflowSendEmailStepOutputSchema = { - success: boolean; -}; - -@Injectable() -export class SendEmailWorkflowAction implements WorkflowAction { - private readonly logger = new Logger(SendEmailWorkflowAction.name); - constructor( - private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, - private readonly twentyORMGlobalManager: TwentyORMGlobalManager, - private readonly sendMessageService: MessagingSendMessageService, - ) {} - - private async getConnectedAccount(connectedAccountId: string) { - if (!isValidUuid(connectedAccountId)) { - throw new SendEmailActionException( - `Connected Account ID is not a valid UUID`, - SendEmailActionExceptionCode.INVALID_CONNECTED_ACCOUNT_ID, - ); - } - - const { workspaceId } = this.scopedWorkspaceContextFactory.create(); - - if (!workspaceId) { - throw new WorkflowStepExecutorException( - 'Scoped workspace not found', - WorkflowStepExecutorExceptionCode.SCOPED_WORKSPACE_NOT_FOUND, - ); - } - - const connectedAccountRepository = - await this.twentyORMGlobalManager.getRepositoryForWorkspace( - workspaceId, - 'connectedAccount', - ); - const connectedAccount = await connectedAccountRepository.findOneBy({ - id: connectedAccountId, - }); - - if (!isDefined(connectedAccount)) { - throw new SendEmailActionException( - `Connected Account '${connectedAccountId}' not found`, - SendEmailActionExceptionCode.CONNECTED_ACCOUNT_NOT_FOUND, - ); - } - - return connectedAccount; - } - - async execute({ - currentStepId, - steps, - context, - }: WorkflowActionInput): Promise { - const step = steps.find((step) => step.id === currentStepId); - - if (!step) { - throw new WorkflowStepExecutorException( - 'Step not found', - WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND, - ); - } - - if (!isWorkflowSendEmailAction(step)) { - throw new WorkflowStepExecutorException( - 'Step is not a send email action', - WorkflowStepExecutorExceptionCode.INVALID_STEP_TYPE, - ); - } - - const connectedAccount = await this.getConnectedAccount( - step.settings.input.connectedAccountId, - ); - - const workflowActionInput = resolveInput( - step.settings.input, - context, - ) as WorkflowSendEmailActionInput; - - const { email, body, subject } = workflowActionInput; - - const emailSchema = z.string().trim().email('Invalid email'); - - const result = emailSchema.safeParse(email); - - if (!result.success) { - throw new SendEmailActionException( - `Email '${email}' invalid`, - SendEmailActionExceptionCode.INVALID_EMAIL, - ); - } - - const window = new JSDOM('').window; - const purify = DOMPurify(window); - const safeBody = purify.sanitize(body || ''); - const safeSubject = purify.sanitize(subject || ''); - - await this.sendMessageService.sendMessage( - { - to: email, - subject: safeSubject, - body: safeBody, - }, - connectedAccount, - ); - - this.logger.log(`Email sent successfully`); - - return { - result: { success: true } satisfies WorkflowSendEmailStepOutputSchema, - }; - } -} diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/workflow-action-adapter.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/tool-executor-workflow-action.ts similarity index 84% rename from packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/workflow-action-adapter.ts rename to packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/tool-executor-workflow-action.ts index b1251bd7f..6542323ef 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/workflow-action-adapter.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/tool-executor-workflow-action.ts @@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common'; import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface'; -import { TOOLS } from 'src/engine/core-modules/tool/constants/tools.const'; import { ToolType } from 'src/engine/core-modules/tool/enums/tool-type.enum'; +import { ToolRegistryService } from 'src/engine/core-modules/tool/services/tool-registry.service'; import { ToolInput } from 'src/engine/core-modules/tool/types/tool-input.type'; import { WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input'; import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type'; @@ -11,7 +11,9 @@ import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/varia import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; @Injectable() -export class WorkflowActionAdapter implements WorkflowAction { +export class ToolExecutorWorkflowAction implements WorkflowAction { + constructor(private readonly toolRegistry: ToolRegistryService) {} + async execute({ currentStepId, steps, @@ -31,7 +33,7 @@ export class WorkflowActionAdapter implements WorkflowAction { ); } - const tool = TOOLS.get(toolType); + const tool = this.toolRegistry.getTool(toolType); if (!tool) { throw new Error( @@ -54,6 +56,7 @@ export class WorkflowActionAdapter implements WorkflowAction { ): ToolType | null { const mapping: Partial> = { [WorkflowActionType.HTTP_REQUEST]: ToolType.HTTP_REQUEST, + [WorkflowActionType.SEND_EMAIL]: ToolType.SEND_EMAIL, }; return mapping[actionType] || null; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts index e3e12e24f..1cba6cae8 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-executor.module.ts @@ -1,5 +1,6 @@ import { Module } from '@nestjs/common'; +import { AiModule } from 'src/engine/core-modules/ai/ai.module'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; @@ -9,9 +10,8 @@ import { AiAgentActionModule } from 'src/modules/workflow/workflow-executor/work import { CodeActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/code/code-action.module'; import { FilterActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/filter/filter-action.module'; import { FormActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/form/form-action.module'; -import { SendEmailActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email-action.module'; import { RecordCRUDActionModule } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module'; -import { WorkflowActionAdapter } from 'src/modules/workflow/workflow-executor/workflow-actions/workflow-action-adapter'; +import { ToolExecutorWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/tool-executor-workflow-action'; import { WorkflowExecutorWorkspaceService } from 'src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service'; import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.module'; @@ -19,7 +19,6 @@ import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow imports: [ WorkflowCommonModule, CodeActionModule, - SendEmailActionModule, RecordCRUDActionModule, FormActionModule, WorkflowRunModule, @@ -27,12 +26,13 @@ import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow FilterActionModule, AiAgentActionModule, FeatureFlagModule, + AiModule, ], providers: [ WorkflowExecutorWorkspaceService, ScopedWorkspaceContextFactory, WorkflowActionFactory, - WorkflowActionAdapter, + ToolExecutorWorkflowAction, ], exports: [WorkflowExecutorWorkspaceService], }) diff --git a/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service.ts b/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service.ts index 885006244..738c93709 100644 --- a/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service.ts +++ b/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common'; import { isDefined } from 'twenty-shared/utils'; import { ApiKey } from 'src/engine/core-modules/api-key/api-key.entity'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsException, PermissionsExceptionCode, @@ -50,7 +50,7 @@ export class WorkspaceMemberPreQueryHookService { await this.permissionsService.userHasWorkspaceSettingPermission({ userWorkspaceId, workspaceId, - setting: SettingPermissionType.WORKSPACE_MEMBERS, + setting: PermissionFlagType.WORKSPACE_MEMBERS, isExecutedByApiKey: isDefined(apiKey), }) ) { diff --git a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.integration-spec.ts index 7de00cb7f..33ad84e1e 100644 --- a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/granular-settings-permissions.integration-spec.ts @@ -7,7 +7,7 @@ import { createOneObjectMetadataQueryFactory } from 'test/integration/metadata/s import { deleteOneObjectMetadataQueryFactory } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-query-factory.util'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { WORKSPACE_MEMBER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant'; @@ -70,13 +70,13 @@ describe('Granular settings permissions', () => { // Assign specific setting permissions to the custom role const upsertSettingPermissionsQuery = { query: ` - mutation UpsertSettingPermissions { - upsertSettingPermissions(upsertSettingPermissionsInput: { + mutation UpsertPermissionFlags { + upsertPermissionFlags(upsertPermissionFlagsInput: { roleId: "${customRoleId}" - settingPermissionKeys: [${SettingPermissionType.DATA_MODEL}, ${SettingPermissionType.WORKSPACE}, ${SettingPermissionType.WORKFLOWS}] + permissionFlagKeys: [${PermissionFlagType.DATA_MODEL}, ${PermissionFlagType.WORKSPACE}, ${PermissionFlagType.WORKFLOWS}] }) { id - setting + flag roleId } } @@ -357,8 +357,8 @@ describe('Granular settings permissions', () => { id label canUpdateAllSettings - settingPermissions { - setting + permissionFlags { + flag } } } @@ -376,13 +376,13 @@ describe('Granular settings permissions', () => { expect(customRole).toBeDefined(); expect(customRole.canUpdateAllSettings).toBe(false); - expect(customRole.settingPermissions).toHaveLength(3); - expect( - customRole.settingPermissions.map((p: any) => p.setting), - ).toContain(SettingPermissionType.DATA_MODEL); - expect( - customRole.settingPermissions.map((p: any) => p.setting), - ).toContain(SettingPermissionType.WORKSPACE); + expect(customRole.permissionFlags).toHaveLength(3); + expect(customRole.permissionFlags.map((p: any) => p.flag)).toContain( + PermissionFlagType.DATA_MODEL, + ); + expect(customRole.permissionFlags.map((p: any) => p.flag)).toContain( + PermissionFlagType.WORKSPACE, + ); }); }); @@ -391,13 +391,13 @@ describe('Granular settings permissions', () => { // Add SECURITY permission to the custom role const upsertSecurityPermissionQuery = { query: ` - mutation UpsertSettingPermissions { - upsertSettingPermissions(upsertSettingPermissionsInput: { + mutation UpsertPermissionFlags { + upsertPermissionFlags(upsertPermissionFlagsInput: { roleId: "${customRoleId}" - settingPermissionKeys: [${SettingPermissionType.DATA_MODEL}, ${SettingPermissionType.WORKSPACE}, ${SettingPermissionType.SECURITY}] + permissionFlagKeys: [${PermissionFlagType.DATA_MODEL}, ${PermissionFlagType.WORKSPACE}, ${PermissionFlagType.SECURITY}] }) { id - setting + flag roleId } } @@ -411,7 +411,7 @@ describe('Granular settings permissions', () => { expect(response.status).toBe(200); expect(response.body.errors).toBeUndefined(); - expect(response.body.data.upsertSettingPermissions).toHaveLength(3); + expect(response.body.data.upsertPermissionFlags).toHaveLength(3); // Verify the user now has access to security operations // Note: This would require a specific security operation to test @@ -421,8 +421,8 @@ describe('Granular settings permissions', () => { query GetRole { getRoles { id - settingPermissions { - setting + permissionFlags { + flag } } } @@ -438,23 +438,23 @@ describe('Granular settings permissions', () => { (role: any) => role.id === customRoleId, ); - expect(updatedRole.settingPermissions).toHaveLength(3); - expect( - updatedRole.settingPermissions.map((p: any) => p.setting), - ).toContain(SettingPermissionType.SECURITY); + expect(updatedRole.permissionFlags).toHaveLength(3); + expect(updatedRole.permissionFlags.map((p: any) => p.flag)).toContain( + PermissionFlagType.SECURITY, + ); }); it('should allow removing setting permissions from existing role', async () => { // Remove SECURITY permission, keep only DATA_MODEL and WORKSPACE const upsertReducedPermissionsQuery = { query: ` - mutation UpsertSettingPermissions { - upsertSettingPermissions(upsertSettingPermissionsInput: { + mutation UpsertPermissionFlags { + upsertPermissionFlags(upsertPermissionFlagsInput: { roleId: "${customRoleId}" - settingPermissionKeys: [${SettingPermissionType.DATA_MODEL}, ${SettingPermissionType.WORKSPACE}] + permissionFlagKeys: [${PermissionFlagType.DATA_MODEL}, ${PermissionFlagType.WORKSPACE}] }) { id - setting + flag roleId } } @@ -468,7 +468,7 @@ describe('Granular settings permissions', () => { expect(response.status).toBe(200); expect(response.body.errors).toBeUndefined(); - expect(response.body.data.upsertSettingPermissions).toHaveLength(2); + expect(response.body.data.upsertPermissionFlags).toHaveLength(2); // Verify SECURITY permission was removed const getRoleQuery = { @@ -476,8 +476,8 @@ describe('Granular settings permissions', () => { query GetRole { getRoles { id - settingPermissions { - setting + permissionFlags { + flag } } } @@ -493,10 +493,10 @@ describe('Granular settings permissions', () => { (role: any) => role.id === customRoleId, ); - expect(updatedRole.settingPermissions).toHaveLength(2); - expect( - updatedRole.settingPermissions.map((p: any) => p.setting), - ).not.toContain(SettingPermissionType.SECURITY); + expect(updatedRole.permissionFlags).toHaveLength(2); + expect(updatedRole.permissionFlags.map((p: any) => p.flag)).not.toContain( + PermissionFlagType.SECURITY, + ); }); }); }); diff --git a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts index 9693d2a54..3114d93a2 100644 --- a/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/settings-permissions/roles.integration-spec.ts @@ -5,7 +5,7 @@ import { deleteOneObjectMetadata } from 'test/integration/metadata/suites/object import { fieldTextMock } from 'src/engine/api/__mocks__/object-metadata-item.mock'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; -import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; +import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { WORKSPACE_MEMBER_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/workspace-member-data-seeds.constant'; @@ -561,17 +561,17 @@ describe('roles permissions', () => { }); }); - describe('upsertSettingPermissions', () => { + describe('upsertPermissionFlags', () => { const upsertSettingPermissionsMutation = ({ roleId, }: { roleId: string; }) => ` - mutation UpsertSettingPermissions { - upsertSettingPermissions(upsertSettingPermissionsInput: {roleId: "${roleId}", settingPermissionKeys: [${SettingPermissionType.DATA_MODEL}]}) { + mutation UpsertPermissionFlags { + upsertPermissionFlags(upsertPermissionFlagsInput: {roleId: "${roleId}", permissionFlagKeys: [${PermissionFlagType.DATA_MODEL}]}) { id roleId - setting + flag } } `; @@ -625,11 +625,11 @@ describe('roles permissions', () => { .expect((res) => { expect(res.body.data).toBeDefined(); expect(res.body.errors).toBeUndefined(); - expect(res.body.data.upsertSettingPermissions).toEqual( + expect(res.body.data.upsertPermissionFlags).toEqual( expect.arrayContaining([ expect.objectContaining({ roleId: createdEditableRoleId, - setting: SettingPermissionType.DATA_MODEL, + flag: PermissionFlagType.DATA_MODEL, }), ]), ); diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts b/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts index 48ea47921..7da2f66f9 100644 --- a/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts +++ b/packages/twenty-server/test/integration/metadata/suites/agent/utils/agent-tool-test-utils.ts @@ -5,14 +5,19 @@ import { Repository } from 'typeorm'; import { ToolAdapterService } from 'src/engine/core-modules/ai/services/tool-adapter.service'; import { ToolService } from 'src/engine/core-modules/ai/services/tool.service'; +import { ToolRegistryService } from 'src/engine/core-modules/tool/services/tool-registry.service'; +import { SendEmailTool } from 'src/engine/core-modules/tool/tools/send-email-tool/send-email-tool'; import { AgentToolService } from 'src/engine/metadata-modules/agent/agent-tool.service'; import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity'; import { AgentService } from 'src/engine/metadata-modules/agent/agent.service'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; +import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service'; +import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { MessagingSendMessageService } from 'src/modules/messaging/message-import-manager/services/messaging-send-message.service'; import { getMockObjectMetadataEntity } from 'src/utils/__test__/get-object-metadata-entity.mock'; export interface AgentToolTestContext { @@ -79,6 +84,34 @@ export const createAgentToolTestModule = provide: ToolAdapterService, useClass: ToolAdapterService, }, + { + provide: ToolRegistryService, + useClass: ToolRegistryService, + }, + { + provide: SendEmailTool, + useValue: { + description: 'mock', + parameters: {}, + execute: jest.fn(), + }, + }, + { + provide: ScopedWorkspaceContextFactory, + useValue: { + create: jest.fn(() => ({ workspaceId: 'test-workspace-id' })), + }, + }, + { + provide: MessagingSendMessageService, + useValue: { sendMessage: jest.fn() }, + }, + { + provide: PermissionsService, + useValue: { + hasToolPermission: jest.fn(), + }, + }, ], }).compile();