diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index f0b9964a63..4134d0e67e 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -3062,6 +3062,7 @@ type Query { getViewFieldGroups(viewId: String!): [ViewFieldGroup!]! getViewFieldGroup(id: String!): ViewFieldGroup apiKeys: [ApiKey!]! + getApiKeyRoles: [Role!]! apiKey(input: GetApiKeyInput!): ApiKey getInviteSuggestions: [InviteSuggestion!]! applicationConnectionProviders(applicationId: UUID!): [ApplicationConnectionProvider!]! diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index b6cbda603a..56728c9221 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -2720,6 +2720,7 @@ export interface Query { getViewFieldGroups: ViewFieldGroup[] getViewFieldGroup?: ViewFieldGroup apiKeys: ApiKey[] + getApiKeyRoles: Role[] apiKey?: ApiKey getInviteSuggestions: InviteSuggestion[] applicationConnectionProviders: ApplicationConnectionProvider[] @@ -5913,6 +5914,7 @@ export interface QueryGenqlSelection{ getViewFieldGroups?: (ViewFieldGroupGenqlSelection & { __args: {viewId: Scalars['String']} }) getViewFieldGroup?: (ViewFieldGroupGenqlSelection & { __args: {id: Scalars['String']} }) apiKeys?: ApiKeyGenqlSelection + getApiKeyRoles?: RoleGenqlSelection apiKey?: (ApiKeyGenqlSelection & { __args: {input: GetApiKeyInput} }) getInviteSuggestions?: InviteSuggestionGenqlSelection applicationConnectionProviders?: (ApplicationConnectionProviderGenqlSelection & { __args: {applicationId: Scalars['UUID']} }) diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index f01348b7e0..3f5ed57ebd 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -6199,6 +6199,9 @@ export default { "apiKeys": [ 2 ], + "getApiKeyRoles": [ + 29 + ], "apiKey": [ 2, { diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 2ad93994ec..5ea53486e8 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -4387,6 +4387,7 @@ export type Query = { frontComponents: Array; getAddressDetails: PlaceDetailsResult; getAiSystemPromptPreview: AiSystemPromptPreview; + getApiKeyRoles: Array; getApprovedAccessDomains: Array; getAutoCompleteAddress: Array; getAvailablePackages: Scalars['JSON']['output']; @@ -8070,6 +8071,11 @@ export type GetApiKeyQueryVariables = Exact<{ export type GetApiKeyQuery = { __typename?: 'Query', apiKey?: { __typename?: 'ApiKey', createdAt: string, id: string, name: string, expiresAt: string, revokedAt?: string | null, role: { __typename?: 'Role', id: string, label: string, icon?: string | null } } | null }; +export type GetApiKeyRolesQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetApiKeyRolesQuery = { __typename?: 'Query', getApiKeyRoles: Array<{ __typename?: 'Role', id: string, label: string, icon?: string | null, canBeAssignedToApiKeys: boolean }> }; + export type GetApiKeysQueryVariables = Exact<{ [key: string]: never; }>; @@ -9073,6 +9079,7 @@ export const RevokeApiKeyDocument = {"kind":"Document","definitions":[{"kind":"O export const UpdateApiKeyDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateApiKey"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateApiKeyInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateApiKey"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApiKeyFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApiKeyFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApiKey"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}},{"kind":"Field","name":{"kind":"Name","value":"revokedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}}]}}]}}]} as unknown as DocumentNode; export const UpdateWebhookDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWebhook"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateWebhookInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateWebhook"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WebhookFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WebhookFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Webhook"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"targetUrl"}},{"kind":"Field","name":{"kind":"Name","value":"operations"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"secret"}}]}}]} as unknown as DocumentNode; export const GetApiKeyDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetApiKey"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"GetApiKeyInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"apiKey"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApiKeyFragment"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApiKeyFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApiKey"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}},{"kind":"Field","name":{"kind":"Name","value":"revokedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}}]}}]}}]} as unknown as DocumentNode; +export const GetApiKeyRolesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetApiKeyRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getApiKeyRoles"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"canBeAssignedToApiKeys"}}]}}]}}]} as unknown as DocumentNode; export const GetApiKeysDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetApiKeys"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"apiKeys"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApiKeyFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApiKeyFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApiKey"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}},{"kind":"Field","name":{"kind":"Name","value":"revokedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}}]}}]}}]} as unknown as DocumentNode; export const GetWebhookDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWebhook"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"webhook"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WebhookFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WebhookFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Webhook"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"targetUrl"}},{"kind":"Field","name":{"kind":"Name","value":"operations"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"secret"}}]}}]} as unknown as DocumentNode; export const GetWebhooksDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetWebhooks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"webhooks"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WebhookFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WebhookFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Webhook"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"targetUrl"}},{"kind":"Field","name":{"kind":"Name","value":"operations"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"secret"}}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersRoleSelector.tsx b/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersRoleSelector.tsx index acddde4749..d87cccda66 100644 --- a/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersRoleSelector.tsx +++ b/packages/twenty-front/src/modules/settings/developers/components/SettingsDevelopersRoleSelector.tsx @@ -1,13 +1,18 @@ -import { type RoleWithPartialMembers } from '@/settings/roles/types/RoleWithPartialMembers'; import { Select } from '@/ui/input/components/Select'; +import { type Role } from '~/generated-metadata/graphql'; import { type IconComponent, useIcons } from 'twenty-ui/icon'; +type ApiKeyAssignableRole = Pick< + Role, + 'id' | 'label' | 'icon' | 'canBeAssignedToApiKeys' +>; + type SettingsDevelopersRoleSelectorProps = { value?: string | null | undefined; onChange: (roleId: string) => void; label?: string; description?: string; - roles: RoleWithPartialMembers[]; + roles: ApiKeyAssignableRole[]; }; export const SettingsDevelopersRoleSelector = ({ diff --git a/packages/twenty-front/src/modules/settings/developers/graphql/queries/getApiKeyRoles.ts b/packages/twenty-front/src/modules/settings/developers/graphql/queries/getApiKeyRoles.ts new file mode 100644 index 0000000000..4c1758ad5f --- /dev/null +++ b/packages/twenty-front/src/modules/settings/developers/graphql/queries/getApiKeyRoles.ts @@ -0,0 +1,12 @@ +import gql from 'graphql-tag'; + +export const GET_API_KEY_ROLES = gql` + query GetApiKeyRoles { + getApiKeyRoles { + id + label + icon + canBeAssignedToApiKeys + } + } +`; diff --git a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx index 778c0f5f8f..1e1d006ef6 100644 --- a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx +++ b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx @@ -32,7 +32,7 @@ import { CreateApiKeyDocument, GenerateApiKeyTokenDocument, GetApiKeyDocument, - GetRolesDocument, + GetApiKeyRolesDocument, RevokeApiKeyDocument, } from '~/generated-metadata/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; @@ -100,9 +100,11 @@ export const SettingsDevelopersApiKeyDetail = () => { } }, [apiKeyData]); - const { data: rolesData, loading: rolesLoading } = useQuery(GetRolesDocument); + const { data: rolesData, loading: rolesLoading } = useQuery( + GetApiKeyRolesDocument, + ); - const roles = rolesData?.getRoles ?? []; + const roles = rolesData?.getApiKeyRoles ?? []; const apiKey = apiKeyData?.apiKey; const [apiKeyName, setApiKeyName] = useState(''); diff --git a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx index 9496890b14..86e7c37c6a 100644 --- a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx +++ b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx @@ -21,8 +21,8 @@ import { Section } from 'twenty-ui/layout'; import { CreateApiKeyDocument, GenerateApiKeyTokenDocument, + GetApiKeyRolesDocument, GetApiKeysDocument, - GetRolesDocument, } from '~/generated-metadata/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { SETTINGS_API_WEBHOOKS_TABS } from '~/pages/settings/api-webhooks/constants/SettingsApiWebhooksTabs'; @@ -31,8 +31,10 @@ export const SettingsDevelopersApiKeysNew = () => { const { t } = useLingui(); const [generateOneApiKeyToken] = useMutation(GenerateApiKeyTokenDocument); const navigateSettings = useNavigateSettings(); - const { data: rolesData, loading: rolesLoading } = useQuery(GetRolesDocument); - const roles = rolesData?.getRoles ?? []; + const { data: rolesData, loading: rolesLoading } = useQuery( + GetApiKeyRolesDocument, + ); + const roles = rolesData?.getApiKeyRoles ?? []; const [formValues, setFormValues] = useState<{ name: string; @@ -45,8 +47,8 @@ export const SettingsDevelopersApiKeysNew = () => { }); useEffect(() => { - if (isDefined(rolesData?.getRoles)) { - const apiKeyAssignableRoles = rolesData.getRoles.filter( + if (isDefined(rolesData?.getApiKeyRoles)) { + const apiKeyAssignableRoles = rolesData.getApiKeyRoles.filter( (role) => role.canBeAssignedToApiKeys, ); if (apiKeyAssignableRoles.length > 0) { diff --git a/packages/twenty-server/src/engine/core-modules/api-key/api-key.resolver.ts b/packages/twenty-server/src/engine/core-modules/api-key/api-key.resolver.ts index e838b33c79..bd13f69b2b 100644 --- a/packages/twenty-server/src/engine/core-modules/api-key/api-key.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/api-key/api-key.resolver.ts @@ -43,6 +43,13 @@ export class ApiKeyResolver { return this.apiKeyService.findActiveByWorkspaceId(workspace.id); } + @Query(() => [RoleDTO]) + async getApiKeyRoles( + @AuthWorkspace() workspace: WorkspaceEntity, + ): Promise { + return this.apiKeyRoleService.getApiKeyAssignableRoles(workspace.id); + } + @Query(() => ApiKeyEntity, { nullable: true }) async apiKey( @Args('input') input: GetApiKeyInput, diff --git a/packages/twenty-server/src/engine/core-modules/api-key/services/api-key-role.service.ts b/packages/twenty-server/src/engine/core-modules/api-key/services/api-key-role.service.ts index 314a7bddae..cf271f69d8 100644 --- a/packages/twenty-server/src/engine/core-modules/api-key/services/api-key-role.service.ts +++ b/packages/twenty-server/src/engine/core-modules/api-key/services/api-key-role.service.ts @@ -14,7 +14,10 @@ import { RoleTargetService } from 'src/engine/metadata-modules/role-target/servi import { type RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { fromFlatRoleToRoleDto } from 'src/engine/metadata-modules/role/utils/fromFlatRoleToRoleDto.util'; -import { fromRoleEntityToRoleDto } from 'src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util'; +import { + fromRoleEntitiesToRoleDtos, + fromRoleEntityToRoleDto, +} from 'src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util'; import { InjectWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/inject-workspace-scoped-repository.decorator'; import { WorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/workspace-scoped-repository'; import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; @@ -173,6 +176,17 @@ export class ApiKeyRoleService { }; } + public async getApiKeyAssignableRoles( + workspaceId: string, + ): Promise { + const roles = await this.roleRepository.find(workspaceId, { + where: { canBeAssignedToApiKeys: true }, + order: { label: 'ASC' }, + }); + + return fromRoleEntitiesToRoleDtos(roles); + } + public async getRolesByApiKeys({ apiKeyIds, workspaceId,