diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index 2e77608a53..d9e920a674 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -55,10 +55,11 @@ type ApplicationRegistration { isListed: Boolean! isVetted: Boolean! isPreInstalled: Boolean! - logoUrl: String createdAt: DateTime! updatedAt: DateTime! isConfigured: Boolean! + logoUrl: String + galleryImagesUrls: [String!]! } enum ApplicationRegistrationSourceType { diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index ceaf9b4816..10be86991f 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -54,10 +54,11 @@ export interface ApplicationRegistration { isListed: Scalars['Boolean'] isVetted: Scalars['Boolean'] isPreInstalled: Scalars['Boolean'] - logoUrl?: Scalars['String'] createdAt: Scalars['DateTime'] updatedAt: Scalars['DateTime'] isConfigured: Scalars['Boolean'] + logoUrl?: Scalars['String'] + galleryImagesUrls: Scalars['String'][] __typename: 'ApplicationRegistration' } @@ -3093,10 +3094,11 @@ export interface ApplicationRegistrationGenqlSelection{ isListed?: boolean | number isVetted?: boolean | number isPreInstalled?: boolean | number - logoUrl?: boolean | number createdAt?: boolean | number updatedAt?: boolean | number isConfigured?: boolean | number + logoUrl?: boolean | number + galleryImagesUrls?: boolean | number __typename?: boolean | number __scalar?: boolean | number } diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index d7f54cd205..0d5e3eec8f 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -214,9 +214,6 @@ export default { "isPreInstalled": [ 6 ], - "logoUrl": [ - 1 - ], "createdAt": [ 4 ], @@ -226,6 +223,12 @@ export default { "isConfigured": [ 6 ], + "logoUrl": [ + 1 + ], + "galleryImagesUrls": [ + 1 + ], "__typename": [ 1 ] diff --git a/packages/twenty-front/src/generated-admin/graphql.ts b/packages/twenty-front/src/generated-admin/graphql.ts index 7d9cf00b9d..b343699e89 100644 --- a/packages/twenty-front/src/generated-admin/graphql.ts +++ b/packages/twenty-front/src/generated-admin/graphql.ts @@ -184,6 +184,7 @@ export enum AiModelRole { export type ApplicationRegistration = { __typename?: 'ApplicationRegistration'; createdAt: Scalars['DateTime']['output']; + galleryImagesUrls: Array; id: Scalars['UUID']['output']; isConfigured: Scalars['Boolean']['output']; isListed: Scalars['Boolean']['output']; @@ -1082,7 +1083,7 @@ export type UpdateAdminApplicationRegistrationMutationVariables = Exact<{ }>; -export type UpdateAdminApplicationRegistrationMutation = { __typename?: 'Mutation', updateAdminApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } }; +export type UpdateAdminApplicationRegistrationMutation = { __typename?: 'Mutation', updateAdminApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, galleryImagesUrls: Array, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } }; export type UpdateAdminApplicationRegistrationVariableMutationVariables = Exact<{ input: UpdateApplicationRegistrationVariableInput; @@ -1120,7 +1121,7 @@ export type FindAllApplicationRegistrationsQueryVariables = Exact<{ }>; -export type FindAllApplicationRegistrationsQuery = { __typename?: 'Query', findAllApplicationRegistrations: { __typename?: 'PaginatedApplicationRegistrations', hasMore: boolean, registrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> } }; +export type FindAllApplicationRegistrationsQuery = { __typename?: 'Query', findAllApplicationRegistrations: { __typename?: 'PaginatedApplicationRegistrations', hasMore: boolean, registrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, galleryImagesUrls: Array, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> } }; export type CreateDatabaseConfigVariableMutationVariables = Exact<{ key: Scalars['String']['input']; @@ -1197,7 +1198,7 @@ export type FindOneAdminApplicationRegistrationQueryVariables = Exact<{ }>; -export type FindOneAdminApplicationRegistrationQuery = { __typename?: 'Query', findOneAdminApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } }; +export type FindOneAdminApplicationRegistrationQuery = { __typename?: 'Query', findOneAdminApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, galleryImagesUrls: Array, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } }; export type GetAdminChatThreadMessagesQueryVariables = Exact<{ threadId: Scalars['UUID']['input']; @@ -1338,10 +1339,10 @@ export type GetSigningKeysQueryVariables = Exact<{ [key: string]: never; }>; export type GetSigningKeysQuery = { __typename?: 'Query', getSigningKeys: { __typename?: 'SigningKeysAdminPanelDTO', legacyVerifyCountInWindow: number, verifyWindowDays: number, signingKeys: Array<{ __typename?: 'SigningKeyDTO', id: string, publicKey: string, isCurrent: boolean, createdAt: string, revokedAt?: string | null, verifyCountInWindow: number }> } }; -export type ApplicationRegistrationFragmentFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }; +export type ApplicationRegistrationFragmentFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, galleryImagesUrls: Array, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }; export const UserInfoFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserInfoFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"UserInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]} as unknown as DocumentNode; -export const ApplicationRegistrationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const ApplicationRegistrationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"galleryImagesUrls"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const AddAiProviderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddAiProvider"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerConfig"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addAiProvider"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"providerName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}}},{"kind":"Argument","name":{"kind":"Name","value":"providerConfig"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerConfig"}}}]}]}}]} as unknown as DocumentNode; export const AddModelToProviderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AddModelToProvider"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"modelConfig"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"addModelToProvider"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"providerName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}}},{"kind":"Argument","name":{"kind":"Name","value":"modelConfig"},"value":{"kind":"Variable","name":{"kind":"Name","value":"modelConfig"}}}]}]}}]} as unknown as DocumentNode; export const RemoveAiProviderDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveAiProvider"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeAiProvider"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"providerName"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerName"}}}]}]}}]} as unknown as DocumentNode; @@ -1358,12 +1359,12 @@ export const GetModelsDevProvidersDocument = {"kind":"Document","definitions":[{ export const GetModelsDevSuggestionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetModelsDevSuggestions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerType"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getModelsDevSuggestions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"providerType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerType"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"inputCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"outputCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"cachedInputCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"cacheCreationCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"contextWindowTokens"}},{"kind":"Field","name":{"kind":"Name","value":"maxOutputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"modalities"}},{"kind":"Field","name":{"kind":"Name","value":"supportsReasoning"}}]}}]}}]} as unknown as DocumentNode; export const BackfillApplicationInstallationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"BackfillApplicationInstallation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"backfillApplicationInstallation"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"applicationRegistrationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}}}]}]}}]} as unknown as DocumentNode; export const SyncMarketplaceCatalogDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SyncMarketplaceCatalog"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"syncMarketplaceCatalog"}}]}}]} as unknown as DocumentNode; -export const UpdateAdminApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateAdminApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateApplicationRegistrationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateAdminApplicationRegistration"},"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":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const UpdateAdminApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateAdminApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateApplicationRegistrationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateAdminApplicationRegistration"},"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":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"galleryImagesUrls"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const UpdateAdminApplicationRegistrationVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateAdminApplicationRegistrationVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateApplicationRegistrationVariableInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateAdminApplicationRegistrationVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"isSecret"}},{"kind":"Field","name":{"kind":"Name","value":"isRequired"}},{"kind":"Field","name":{"kind":"Name","value":"isFilled"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; export const FindAdminApplicationRegistrationInstalledWorkspacesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAdminApplicationRegistrationInstalledWorkspaces"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"FindApplicationRegistrationInstalledWorkspacesInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAdminApplicationRegistrationInstalledWorkspaces"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"hasMore"}},{"kind":"Field","name":{"kind":"Name","value":"workspaces"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"version"}}]}}]}}]}}]} as unknown as DocumentNode; export const FindAdminApplicationRegistrationStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAdminApplicationRegistrationStats"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAdminApplicationRegistrationStats"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeInstalls"}},{"kind":"Field","name":{"kind":"Name","value":"mostInstalledVersion"}},{"kind":"Field","name":{"kind":"Name","value":"versionDistribution"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]} as unknown as DocumentNode; export const FindAdminApplicationRegistrationVariablesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAdminApplicationRegistrationVariables"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAdminApplicationRegistrationVariables"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"applicationRegistrationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"isSecret"}},{"kind":"Field","name":{"kind":"Name","value":"isRequired"}},{"kind":"Field","name":{"kind":"Name","value":"isFilled"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"options"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; -export const FindAllApplicationRegistrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAllApplicationRegistrations"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isPreInstalledOnly"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAllApplicationRegistrations"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}},{"kind":"Argument","name":{"kind":"Name","value":"searchTerm"},"value":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}}},{"kind":"Argument","name":{"kind":"Name","value":"isPreInstalledOnly"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isPreInstalledOnly"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"registrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"hasMore"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const FindAllApplicationRegistrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAllApplicationRegistrations"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"offset"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"isPreInstalledOnly"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAllApplicationRegistrations"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"offset"},"value":{"kind":"Variable","name":{"kind":"Name","value":"offset"}}},{"kind":"Argument","name":{"kind":"Name","value":"searchTerm"},"value":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}}},{"kind":"Argument","name":{"kind":"Name","value":"isPreInstalledOnly"},"value":{"kind":"Variable","name":{"kind":"Name","value":"isPreInstalledOnly"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"registrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}},{"kind":"Field","name":{"kind":"Name","value":"hasMore"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"galleryImagesUrls"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const CreateDatabaseConfigVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateDatabaseConfigVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createDatabaseConfigVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}}]}]}}]} as unknown as DocumentNode; export const DeleteDatabaseConfigVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteDatabaseConfigVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteDatabaseConfigVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}}]}]}}]} as unknown as DocumentNode; export const UpdateDatabaseConfigVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateDatabaseConfigVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateDatabaseConfigVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}}]}]}}]} as unknown as DocumentNode; @@ -1373,7 +1374,7 @@ export const UpdateServerAdminAccessDocument = {"kind":"Document","definitions": export const UpdateWorkspaceFeatureFlagDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkspaceFeatureFlag"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"featureFlag"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Boolean"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateWorkspaceFeatureFlag"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"featureFlag"},"value":{"kind":"Variable","name":{"kind":"Name","value":"featureFlag"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}}]}]}}]} as unknown as DocumentNode; export const AdminPanelRecentUsersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AdminPanelRecentUsers"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminPanelRecentUsers"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"searchTerm"},"value":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"avatarUrl"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceLogo"}}]}}]}}]} as unknown as DocumentNode; export const AdminPanelTopWorkspacesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AdminPanelTopWorkspaces"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"adminPanelTopWorkspaces"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"searchTerm"},"value":{"kind":"Variable","name":{"kind":"Name","value":"searchTerm"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"totalUsers"}},{"kind":"Field","name":{"kind":"Name","value":"subdomain"}}]}}]}}]} as unknown as DocumentNode; -export const FindOneAdminApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneAdminApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findOneAdminApplicationRegistration"},"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":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const FindOneAdminApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneAdminApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findOneAdminApplicationRegistration"},"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":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"galleryImagesUrls"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const GetAdminChatThreadMessagesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAdminChatThreadMessages"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getAdminChatThreadMessages"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"threadId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"thread"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"totalInputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"totalOutputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"conversationSize"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"messages"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"parts"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"textContent"}},{"kind":"Field","name":{"kind":"Name","value":"toolName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]}}]} as unknown as DocumentNode; export const GetAdminWorkspaceChatThreadsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAdminWorkspaceChatThreads"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getAdminWorkspaceChatThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"totalInputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"totalOutputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"conversationSize"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; export const GetServerAdminsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetServerAdmins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getServerAdmins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"canAccessFullAdminPanel"}},{"kind":"Field","name":{"kind":"Name","value":"canImpersonate"}}]}}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index f72afdd17c..8d3f470eb7 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -327,6 +327,7 @@ export type ApplicationConnectionProviderOAuthConfig = { export type ApplicationRegistration = { __typename?: 'ApplicationRegistration'; createdAt: Scalars['DateTime']['output']; + galleryImagesUrls: Array; id: Scalars['UUID']['output']; isConfigured: Scalars['Boolean']['output']; isListed: Scalars['Boolean']['output']; @@ -7714,7 +7715,7 @@ export type MyMessageFoldersQueryVariables = Exact<{ export type MyMessageFoldersQuery = { __typename?: 'Query', myMessageFolders: Array<{ __typename?: 'MessageFolder', id: string, name?: string | null, isSynced: boolean, isSentFolder: boolean, parentFolderId?: string | null, externalId?: string | null, pendingSyncAction: MessageFolderPendingSyncAction, messageChannelId: string, createdAt: string, updatedAt: string }> }; -export type ApplicationRegistrationFragmentFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }; +export type ApplicationRegistrationFragmentFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, galleryImagesUrls: Array, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }; export type ClaimApplicationRegistrationOwnershipMutationVariables = Exact<{ applicationRegistrationId: Scalars['String']['input']; @@ -7750,7 +7751,7 @@ export type UpdateApplicationRegistrationMutationVariables = Exact<{ }>; -export type UpdateApplicationRegistrationMutation = { __typename?: 'Mutation', updateApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } }; +export type UpdateApplicationRegistrationMutation = { __typename?: 'Mutation', updateApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, galleryImagesUrls: Array, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } }; export type UpdateApplicationRegistrationVariableMutationVariables = Exact<{ input: UpdateApplicationRegistrationVariableInput; @@ -7780,19 +7781,19 @@ export type FindApplicationRegistrationVariablesQueryVariables = Exact<{ export type FindApplicationRegistrationVariablesQuery = { __typename?: 'Query', findApplicationRegistrationVariables: Array<{ __typename?: 'ApplicationRegistrationVariableDTO', id: string, key: string, value?: string | null, description: string, isSecret: boolean, isRequired: boolean, isFilled: boolean, type: string, options?: any | null, createdAt: string, updatedAt: string }> }; -export type ApplicationRegistrationListItemFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, sourceType: ApplicationRegistrationSourceType }; +export type ApplicationRegistrationListItemFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, sourceType: ApplicationRegistrationSourceType, logoUrl?: string | null }; export type FindManyApplicationRegistrationsQueryVariables = Exact<{ [key: string]: never; }>; -export type FindManyApplicationRegistrationsQuery = { __typename?: 'Query', findManyApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, sourceType: ApplicationRegistrationSourceType }> }; +export type FindManyApplicationRegistrationsQuery = { __typename?: 'Query', findManyApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, sourceType: ApplicationRegistrationSourceType, logoUrl?: string | null }> }; export type FindOneApplicationRegistrationQueryVariables = Exact<{ id: Scalars['String']['input']; }>; -export type FindOneApplicationRegistrationQuery = { __typename?: 'Query', findOneApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } }; +export type FindOneApplicationRegistrationQuery = { __typename?: 'Query', findOneApplicationRegistration: { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, logoUrl?: string | null, galleryImagesUrls: Array, oAuthClientId: string, oAuthRedirectUris: Array, oAuthScopes: Array, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isVetted: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string } }; export type UninstallApplicationMutationVariables = Exact<{ universalIdentifier: Scalars['String']['input']; @@ -8772,8 +8773,8 @@ export const MarketplaceAppDetailFieldsFragmentDoc = {"kind":"Document","definit export const MarketplaceAppFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MarketplaceAppFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MarketplaceApp"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"author"}},{"kind":"Field","name":{"kind":"Name","value":"category"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}}]}}]} as unknown as DocumentNode; export const NavigationMenuItemFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const NavigationMenuItemQueryFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemQueryFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NavigationMenuItemFields"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordIdentifier"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"labelIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"imageIdentifier"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NavigationMenuItemFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"NavigationMenuItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"userWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"targetRecordId"}},{"kind":"Field","name":{"kind":"Name","value":"targetObjectMetadataId"}},{"kind":"Field","name":{"kind":"Name","value":"viewId"}},{"kind":"Field","name":{"kind":"Name","value":"folderId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"color"}},{"kind":"Field","name":{"kind":"Name","value":"pageLayoutId"}},{"kind":"Field","name":{"kind":"Name","value":"position"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; -export const ApplicationRegistrationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; -export const ApplicationRegistrationListItemFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationListItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}}]}}]} as unknown as DocumentNode; +export const ApplicationRegistrationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"galleryImagesUrls"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const ApplicationRegistrationListItemFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationListItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}}]}}]} as unknown as DocumentNode; export const BillingPriceLicensedFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingPriceLicensedFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingPriceLicensed"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"unitAmount"}},{"kind":"Field","name":{"kind":"Name","value":"recurringInterval"}},{"kind":"Field","name":{"kind":"Name","value":"priceUsageType"}},{"kind":"Field","name":{"kind":"Name","value":"creditAmount"}}]}}]} as unknown as DocumentNode; export const BillingPriceMeteredFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BillingPriceMeteredFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BillingPriceMetered"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"priceUsageType"}},{"kind":"Field","name":{"kind":"Name","value":"recurringInterval"}},{"kind":"Field","name":{"kind":"Name","value":"stripePriceId"}},{"kind":"Field","name":{"kind":"Name","value":"tiers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"flatAmount"}},{"kind":"Field","name":{"kind":"Name","value":"unitAmount"}},{"kind":"Field","name":{"kind":"Name","value":"upTo"}}]}}]}}]} as unknown as DocumentNode; export const ApiKeyFragmentFragmentDoc = {"kind":"Document","definitions":[{"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; @@ -8950,13 +8951,13 @@ export const ClaimApplicationRegistrationOwnershipDocument = {"kind":"Document", export const DeleteApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteApplicationRegistration"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]} as unknown as DocumentNode; export const RotateApplicationRegistrationClientSecretDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RotateApplicationRegistrationClientSecret"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"rotateApplicationRegistrationClientSecret"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"clientSecret"}}]}}]}}]} as unknown as DocumentNode; export const TransferApplicationRegistrationOwnershipDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"TransferApplicationRegistrationOwnership"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"targetWorkspaceSubdomain"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"transferApplicationRegistrationOwnership"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"applicationRegistrationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}}},{"kind":"Argument","name":{"kind":"Name","value":"targetWorkspaceSubdomain"},"value":{"kind":"Variable","name":{"kind":"Name","value":"targetWorkspaceSubdomain"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; -export const UpdateApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateApplicationRegistrationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateApplicationRegistration"},"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":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const UpdateApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateApplicationRegistrationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateApplicationRegistration"},"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":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"galleryImagesUrls"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const UpdateApplicationRegistrationVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateApplicationRegistrationVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateApplicationRegistrationVariableInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateApplicationRegistrationVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"isSecret"}},{"kind":"Field","name":{"kind":"Name","value":"isRequired"}},{"kind":"Field","name":{"kind":"Name","value":"isFilled"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; export const ApplicationRegistrationTarballUrlDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ApplicationRegistrationTarballUrl"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"applicationRegistrationTarballUrl"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}]}]}}]} as unknown as DocumentNode; export const FindApplicationRegistrationStatsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindApplicationRegistrationStats"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findApplicationRegistrationStats"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeInstalls"}},{"kind":"Field","name":{"kind":"Name","value":"mostInstalledVersion"}},{"kind":"Field","name":{"kind":"Name","value":"versionDistribution"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]} as unknown as DocumentNode; export const FindApplicationRegistrationVariablesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindApplicationRegistrationVariables"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findApplicationRegistrationVariables"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"applicationRegistrationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"isSecret"}},{"kind":"Field","name":{"kind":"Name","value":"isRequired"}},{"kind":"Field","name":{"kind":"Name","value":"isFilled"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"options"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; -export const FindManyApplicationRegistrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindManyApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findManyApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationListItem"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationListItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}}]}}]} as unknown as DocumentNode; -export const FindOneApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findOneApplicationRegistration"},"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":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; +export const FindManyApplicationRegistrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindManyApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findManyApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationListItem"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationListItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}}]}}]} as unknown as DocumentNode; +export const FindOneApplicationRegistrationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneApplicationRegistration"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findOneApplicationRegistration"},"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":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"galleryImagesUrls"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isVetted"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const UninstallApplicationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UninstallApplication"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"universalIdentifier"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uninstallApplication"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"universalIdentifier"},"value":{"kind":"Variable","name":{"kind":"Name","value":"universalIdentifier"}}}]}]}}]} as unknown as DocumentNode; export const UpdateOneApplicationVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateOneApplicationVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateOneApplicationVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}},{"kind":"Argument","name":{"kind":"Name","value":"applicationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationId"}}}]}]}}]} as unknown as DocumentNode; export const ApplicationConnectionProvidersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ApplicationConnectionProviders"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"applicationConnectionProviders"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"applicationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"oauth"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"scopes"}},{"kind":"Field","name":{"kind":"Name","value":"isClientCredentialsConfigured"}}]}}]}}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/modules/applications/components/AppChip.tsx b/packages/twenty-front/src/modules/applications/components/AppChip.tsx index b3dd16e605..9307140ce3 100644 --- a/packages/twenty-front/src/modules/applications/components/AppChip.tsx +++ b/packages/twenty-front/src/modules/applications/components/AppChip.tsx @@ -8,6 +8,9 @@ import { themeCssVariables } from 'twenty-ui/theme-constants'; type AppChipProps = { size?: AvatarSize; applicationId?: string | null; + // Resolved display url (e.g. the registration's logoUrl); takes precedence + // over the logo computed from the installed application. + logoUrl?: string | null; fallbackApplicationData?: { logo?: string | null; name?: string | null; @@ -31,6 +34,7 @@ const StyledContainer = styled.div` export const AppChip = ({ applicationId, size = 'sm', + logoUrl, fallbackApplicationData, className, chipOnly = false, @@ -45,7 +49,7 @@ export const AppChip = ({ { icon={ { + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" ADD COLUMN IF NOT EXISTS "logoFileId" uuid', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" DROP CONSTRAINT IF EXISTS "UQ_796819fb23559c233e6ebd49f34"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" ADD CONSTRAINT "UQ_796819fb23559c233e6ebd49f34" UNIQUE ("logoFileId")', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" DROP CONSTRAINT IF EXISTS "FK_796819fb23559c233e6ebd49f34"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" ADD CONSTRAINT "FK_796819fb23559c233e6ebd49f34" FOREIGN KEY ("logoFileId") REFERENCES "core"."file"("id") ON DELETE SET NULL ON UPDATE NO ACTION', + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" DROP CONSTRAINT IF EXISTS "FK_796819fb23559c233e6ebd49f34"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" DROP CONSTRAINT IF EXISTS "UQ_796819fb23559c233e6ebd49f34"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" DROP COLUMN IF EXISTS "logoFileId"', + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts index c219c8da66..1bfbcf48d2 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts @@ -109,6 +109,7 @@ import { AddGalleryImagesToApplicationRegistrationFastInstanceCommand } from './ import { BackfillGalleryImagesOnApplicationRegistrationSlowInstanceCommand } from './2-20/2-20-instance-command-slow-1783615890056-backfill-gallery-images-on-application-registration'; import { AddWorkflowVersionSyncableColumnsFastInstanceCommand } from './2-20/2-20-instance-command-fast-1783603454480-add-workflow-version-syncable-columns'; import { BackfillWorkspaceDatabaseSchemaSlowInstanceCommand } from './2-21/2-21-instance-command-slow-1783934147089-backfill-workspace-database-schema'; +import { AddLogoFileIdToApplicationRegistrationFastInstanceCommand } from './2-21/2-21-instance-command-fast-1783945979243-add-logo-file-id-to-application-registration'; export const INSTANCE_COMMANDS = [ AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand, @@ -220,4 +221,5 @@ export const INSTANCE_COMMANDS = [ BackfillGalleryImagesOnApplicationRegistrationSlowInstanceCommand, AddWorkflowVersionSyncableColumnsFastInstanceCommand, BackfillWorkspaceDatabaseSchemaSlowInstanceCommand, + AddLogoFileIdToApplicationRegistrationFastInstanceCommand, ]; diff --git a/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel-application-registration.resolver.ts b/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel-application-registration.resolver.ts index 97263393fa..fc4e2e016b 100644 --- a/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel-application-registration.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel-application-registration.resolver.ts @@ -1,11 +1,16 @@ import { Context, Parent, ResolveField } from '@nestjs/graphql'; import { AdminResolver } from 'src/engine/api/graphql/graphql-config/decorators/admin-resolver.decorator'; +import { ApplicationRegistrationAssetUrlService } from 'src/engine/core-modules/application/application-registration/application-registration-asset-url.service'; import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity'; import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface'; @AdminResolver(() => ApplicationRegistrationEntity) export class AdminPanelApplicationRegistrationResolver { + constructor( + private readonly applicationRegistrationAssetUrlService: ApplicationRegistrationAssetUrlService, + ) {} + @ResolveField(() => Boolean) async isConfigured( @Parent() registration: ApplicationRegistrationEntity, @@ -15,4 +20,22 @@ export class AdminPanelApplicationRegistrationResolver { applicationRegistrationId: registration.id, }); } + + @ResolveField(() => String, { nullable: true }) + logoUrl( + @Parent() registration: ApplicationRegistrationEntity, + ): string | null { + return this.applicationRegistrationAssetUrlService.buildLogoUrl( + registration, + ); + } + + @ResolveField(() => [String]) + galleryImagesUrls( + @Parent() registration: ApplicationRegistrationEntity, + ): string[] { + return this.applicationRegistrationAssetUrlService.buildGalleryImageUrls( + registration, + ); + } } diff --git a/packages/twenty-server/src/engine/core-modules/application/application-development/application-development.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-development/application-development.service.ts index 677c11bbc3..45d0e5f3e5 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-development/application-development.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-development/application-development.service.ts @@ -7,10 +7,10 @@ import { type ApplicationInput } from 'src/engine/core-modules/application/appli import { type DevelopmentApplicationDTO } from 'src/engine/core-modules/application/application-development/dtos/development-application.dto'; import { type WorkspaceMigrationDTO } from 'src/engine/core-modules/application/application-development/dtos/workspace-migration.dto'; import { ApplicationSyncService } from 'src/engine/core-modules/application/application-manifest/application-sync.service'; -import { resolveManifestAssetUrls } from 'src/engine/core-modules/application/application-marketplace/utils/resolve-manifest-asset-urls.util'; import { ApplicationVersionValidationService } from 'src/engine/core-modules/application/application-package/application-version-validation.service'; import { VERSION_REASON_TO_APPLICATION_EXCEPTION_CODE } from 'src/engine/core-modules/application/application-package/constants/version-reason-to-exception-code.constant'; import { ApplicationRegistrationVariableService } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.service'; +import { ApplicationRegistrationAssetService } from 'src/engine/core-modules/application/application-registration/application-registration-asset.service'; import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service'; import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum'; import { @@ -24,7 +24,7 @@ import { validateFilePath } from 'src/engine/core-modules/file-storage/utils/val import { type FileDTO } from 'src/engine/core-modules/file/dtos/file.dto'; import { SdkClientGenerationService } from 'src/engine/core-modules/sdk-client/sdk-client-generation.service'; import { ThrottlerService } from 'src/engine/core-modules/throttler/throttler.service'; -import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; +import { streamToBuffer } from 'src/utils/stream-to-buffer'; const APP_DEV_RATE_LIMIT_MAX = 30; const APP_DEV_RATE_LIMIT_WINDOW_MS = 30_000; @@ -46,10 +46,10 @@ export class ApplicationDevelopmentService { private readonly applicationSyncService: ApplicationSyncService, private readonly applicationRegistrationService: ApplicationRegistrationService, private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService, + private readonly applicationRegistrationAssetService: ApplicationRegistrationAssetService, private readonly applicationVersionValidationService: ApplicationVersionValidationService, private readonly fileStorageService: FileStorageService, private readonly sdkClientGenerationService: SdkClientGenerationService, - private readonly twentyConfigService: TwentyConfigService, private readonly throttlerService: ThrottlerService, private readonly cacheLockService: CacheLockService, ) {} @@ -253,7 +253,6 @@ export class ApplicationDevelopmentService { applicationRegistrationId, manifest, workspaceId, - application.id, ); return { @@ -297,7 +296,6 @@ export class ApplicationDevelopmentService { applicationRegistrationId: string, manifest: ApplicationInput['manifest'], workspaceId: string, - applicationId: string, ): Promise { const registration = await this.applicationRegistrationService.findOneByIdGlobal( @@ -316,20 +314,27 @@ export class ApplicationDevelopmentService { return; } - const serverUrl = this.twentyConfigService.get('SERVER_URL'); - - const manifestWithResolvedUrls = resolveManifestAssetUrls( - manifest, - (filePath) => - `${serverUrl}/public-assets/${workspaceId}/${applicationId}/${filePath}`, - ); - await this.applicationRegistrationService.updateFromManifest({ applicationRegistrationId, - manifest: manifestWithResolvedUrls, + manifest, sourceType: ApplicationRegistrationSourceType.LOCAL, }); + // Public assets are uploaded to workspace storage before the sync, so the + // logo and gallery images can be copied into the registration's + // instance-global server files here. + await this.applicationRegistrationAssetService.storeRegistrationAssets({ + applicationRegistrationId, + manifestApplication: manifest.application, + readAsset: (path) => + this.readPublicAssetFromWorkspaceStorage({ + workspaceId, + applicationUniversalIdentifier: + manifest.application.universalIdentifier, + path, + }), + }); + if (manifest.application.serverVariables) { await this.applicationRegistrationVariableService.syncVariableSchemas( applicationRegistrationId, @@ -337,4 +342,27 @@ export class ApplicationDevelopmentService { ); } } + + private async readPublicAssetFromWorkspaceStorage({ + workspaceId, + applicationUniversalIdentifier, + path, + }: { + workspaceId: string; + applicationUniversalIdentifier: string; + path: string; + }): Promise { + try { + const stream = await this.fileStorageService.readFile({ + workspaceId, + applicationUniversalIdentifier, + fileFolder: FileFolder.PublicAsset, + resourcePath: path, + }); + + return await streamToBuffer(stream); + } catch { + return null; + } + } } diff --git a/packages/twenty-server/src/engine/core-modules/application/application-install/application-install.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-install/application-install.service.ts index 40459b7780..67f07b5bc7 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-install/application-install.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-install/application-install.service.ts @@ -18,7 +18,6 @@ import { isImageFilePath } from 'src/engine/core-modules/application/application import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity'; import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service'; import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum'; -import { ManifestAssetUrlResolverService } from 'src/engine/core-modules/application/application-registration/manifest-asset-url-resolver.service'; import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { ApplicationService } from 'src/engine/core-modules/application/application.service'; import { ApplicationPackageFetcherService } from 'src/engine/core-modules/application/application-package/application-package-fetcher.service'; @@ -57,7 +56,6 @@ export class ApplicationInstallService { @InjectMessageQueue(MessageQueue.logicFunctionQueue) private readonly messageQueueService: MessageQueueService, private readonly workspaceCacheService: WorkspaceCacheService, - private readonly manifestAssetUrlResolverService: ManifestAssetUrlResolverService, ) {} async installApplication(params: { @@ -306,12 +304,7 @@ export class ApplicationInstallService { await this.applicationRegistrationService.updateFromManifest({ applicationRegistrationId: appRegistration.id, - manifest: this.manifestAssetUrlResolverService.resolveFromRegistration({ - sourceType: appRegistration.sourceType, - sourcePackage: appRegistration.sourcePackage, - manifest, - version: installedVersion, - }), + manifest, latestAvailableVersion: installedVersion, preventVersionDowngrade: true, }); diff --git a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace-catalog-sync.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace-catalog-sync.service.ts index c1e8fd55ec..58f6cbcbd8 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace-catalog-sync.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace-catalog-sync.service.ts @@ -1,9 +1,12 @@ import { Injectable, Logger } from '@nestjs/common'; +import { isDefined } from 'twenty-shared/utils'; + +import { MarketplaceService } from 'src/engine/core-modules/application/application-marketplace/marketplace.service'; +import { ApplicationRegistrationAssetService } from 'src/engine/core-modules/application/application-registration/application-registration-asset.service'; import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service'; import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum'; -import { MarketplaceService } from 'src/engine/core-modules/application/application-marketplace/marketplace.service'; -import { ManifestAssetUrlResolverService } from 'src/engine/core-modules/application/application-registration/manifest-asset-url-resolver.service'; +import { areRegistrationAssetsStored } from 'src/engine/core-modules/application/application-registration/utils/are-registration-assets-stored.util'; @Injectable() export class MarketplaceCatalogSyncService { @@ -11,8 +14,8 @@ export class MarketplaceCatalogSyncService { constructor( private readonly applicationRegistrationService: ApplicationRegistrationService, + private readonly applicationRegistrationAssetService: ApplicationRegistrationAssetService, private readonly marketplaceService: MarketplaceService, - private readonly manifestAssetUrlResolverService: ManifestAssetUrlResolverService, ) {} async syncCatalog(): Promise { @@ -43,12 +46,11 @@ export class MarketplaceCatalogSyncService { const universalIdentifier = fetchedManifest.application.universalIdentifier; - const manifestWithResolvedUrls = - this.manifestAssetUrlResolverService.resolveFromRegistrySource({ - manifest: fetchedManifest, - packageName: pkg.name, - version: pkg.version, - }); + const previousVersion = ( + await this.applicationRegistrationService.findOneByUniversalIdentifier( + universalIdentifier, + ) + )?.latestAvailableVersion; await this.applicationRegistrationService.upsertFromCatalog({ universalIdentifier, @@ -56,8 +58,42 @@ export class MarketplaceCatalogSyncService { sourceType: ApplicationRegistrationSourceType.NPM, sourcePackage: pkg.name, latestAvailableVersion: pkg.version ?? null, - manifest: manifestWithResolvedUrls, + manifest: fetchedManifest, }); + + const registration = + await this.applicationRegistrationService.findOneByUniversalIdentifier( + universalIdentifier, + ); + + if (!isDefined(registration)) { + continue; + } + + // Rehost the logo and gallery images from the registry CDN so display + // urls are served from fileIds like every other source. Skipped when + // the version is unchanged and the files are already stored; the + // query-time url builder falls back to CDN urls until they are. + if ( + previousVersion !== pkg.version || + !areRegistrationAssetsStored( + registration, + fetchedManifest.application, + ) + ) { + await this.applicationRegistrationAssetService.storeRegistrationAssets( + { + applicationRegistrationId: registration.id, + manifestApplication: fetchedManifest.application, + readAsset: (path) => + this.marketplaceService.fetchAssetFromRegistryCdn( + pkg.name, + pkg.version, + path, + ), + }, + ); + } } catch (error) { this.logger.error( `Failed to sync registry app "${pkg.name}": ${error instanceof Error ? error.message : String(error)}`, diff --git a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace-query.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace-query.service.ts index 0f68cc816e..640c6ccab7 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace-query.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace-query.service.ts @@ -8,18 +8,19 @@ import { MARKETPLACE_CATALOG_CACHE_ENTITY_ID } from 'src/engine/core-modules/app import { MarketplaceAppDTO } from 'src/engine/core-modules/application/application-marketplace/dtos/marketplace-app.dto'; import { MarketplaceAppDetailDTO } from 'src/engine/core-modules/application/application-marketplace/dtos/marketplace-app-detail.dto'; import { MarketplaceAppRoleDTO } from 'src/engine/core-modules/application/application-marketplace/dtos/marketplace-app-role.dto'; +import { ApplicationRegistrationAssetUrlService } from 'src/engine/core-modules/application/application-registration/application-registration-asset-url.service'; import { type ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity'; import { ApplicationRegistrationException, ApplicationRegistrationExceptionCode, } from 'src/engine/core-modules/application/application-registration/application-registration.exception'; -import { toGalleryImagePaths } from 'src/engine/core-modules/application/application-registration/utils/to-gallery-image-paths.util'; import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service'; @Injectable() export class MarketplaceQueryService { constructor( private readonly applicationRegistrationService: ApplicationRegistrationService, + private readonly applicationRegistrationAssetUrlService: ApplicationRegistrationAssetUrlService, private readonly coreEntityCacheService: CoreEntityCacheService, ) {} @@ -82,13 +83,10 @@ export class MarketplaceQueryService { private toMarketplaceAppDetailDTO( registration: ApplicationRegistrationEntity, ): MarketplaceAppDetailDTO { - // TODO: simplify in a follow-up PR to read galleryImages only, once the - // deprecated screenshots column and manifest fallback are backfilled away. - const galleryImagePaths = isNonEmptyArray(registration.galleryImages) - ? registration.galleryImages.map((galleryImage) => galleryImage.path) - : isNonEmptyArray(registration.screenshots) - ? registration.screenshots - : toGalleryImagePaths(registration.manifest?.application); + const galleryImageUrls = + this.applicationRegistrationAssetUrlService.buildGalleryImageUrls( + registration, + ); return { id: registration.id, @@ -111,7 +109,10 @@ export class MarketplaceQueryService { registration.category ?? registration.manifest?.application?.category ?? undefined, - logo: registration.logoUrl ?? undefined, + logo: + this.applicationRegistrationAssetUrlService.buildLogoUrl( + registration, + ) ?? undefined, websiteUrl: registration.websiteUrl ?? registration.manifest?.application?.websiteUrl ?? @@ -132,8 +133,8 @@ export class MarketplaceQueryService { registration.issueReportUrl ?? registration.manifest?.application?.issueReportUrl ?? undefined, - screenshots: galleryImagePaths, - galleryImages: galleryImagePaths, + screenshots: galleryImageUrls, + galleryImages: galleryImageUrls, defaultRoleUniversalIdentifier: registration.manifest?.application?.defaultRoleUniversalIdentifier, roles: registration.manifest?.roles?.map((role) => diff --git a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace.service.ts index 5dd7f24a0e..ac26a120be 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace.service.ts @@ -7,6 +7,8 @@ import { z } from 'zod'; import { buildRegistryCdnUrl } from 'src/engine/core-modules/application/application-marketplace/utils/build-registry-cdn-url.util'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; +const MAX_REGISTRY_ASSET_SIZE_BYTES = 100 * 1024 * 1024; // 100Mb + export type RegistryPackageInfo = { name: string; version: string; @@ -73,6 +75,37 @@ export class MarketplaceService { } } + async fetchAssetFromRegistryCdn( + packageName: string, + version: string, + filePath: string, + ): Promise { + const cdnBaseUrl = this.twentyConfigService.get('APP_REGISTRY_CDN_URL'); + const url = buildRegistryCdnUrl({ + cdnBaseUrl, + packageName, + version, + filePath, + }); + + try { + const { data } = await axios.get(url, { + headers: { 'User-Agent': 'Twenty-Marketplace' }, + timeout: 10_000, + responseType: 'arraybuffer', + maxContentLength: MAX_REGISTRY_ASSET_SIZE_BYTES, + }); + + return Buffer.from(data); + } catch { + this.logger.debug( + `Could not fetch asset "${filePath}" from CDN for ${packageName}@${version}`, + ); + + return null; + } + } + async fetchAppsFromRegistry(): Promise { const registryUrl = this.twentyConfigService.get('APP_REGISTRY_URL'); diff --git a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/utils/__tests__/resolve-manifest-asset-urls.util.spec.ts b/packages/twenty-server/src/engine/core-modules/application/application-marketplace/utils/__tests__/resolve-manifest-asset-urls.util.spec.ts deleted file mode 100644 index 9f5952e1ae..0000000000 --- a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/utils/__tests__/resolve-manifest-asset-urls.util.spec.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { type Manifest } from 'twenty-shared/application'; - -import { resolveManifestAssetUrls } from 'src/engine/core-modules/application/application-marketplace/utils/resolve-manifest-asset-urls.util'; - -const buildMinimalManifest = ( - overrides: Partial = {}, -): Manifest => ({ - application: { - universalIdentifier: 'app-1', - defaultRoleUniversalIdentifier: 'role-1', - displayName: 'Test App', - description: 'A test app', - packageJsonChecksum: null, - yarnLockChecksum: null, - ...overrides, - }, - objects: [], - fields: [], - logicFunctions: [], - frontComponents: [], - permissionFlags: [], - roles: [], - skills: [], - agents: [], - publicAssets: [], - views: [], - viewFields: [], - navigationMenuItems: [], - pageLayouts: [], - pageLayoutTabs: [], - commandMenuItems: [], -}); - -describe('resolveManifestAssetUrls', () => { - const urlBuilder = (filePath: string) => - `https://cdn.example.com/pkg/${filePath}`; - - it('should resolve a relative logoUrl', () => { - const manifest = buildMinimalManifest({ logoUrl: 'logo.png' }); - - const result = resolveManifestAssetUrls(manifest, urlBuilder); - - expect(result.application.logoUrl).toBe( - 'https://cdn.example.com/pkg/logo.png', - ); - }); - - it('should not modify an absolute logoUrl', () => { - const manifest = buildMinimalManifest({ - logoUrl: 'https://example.com/logo.png', - }); - - const result = resolveManifestAssetUrls(manifest, urlBuilder); - - expect(result.application.logoUrl).toBe('https://example.com/logo.png'); - }); - - it('should not modify an http logoUrl', () => { - const manifest = buildMinimalManifest({ - logoUrl: 'http://example.com/logo.png', - }); - - const result = resolveManifestAssetUrls(manifest, urlBuilder); - - expect(result.application.logoUrl).toBe('http://example.com/logo.png'); - }); - - it('should leave logoUrl undefined when not set', () => { - const manifest = buildMinimalManifest(); - - const result = resolveManifestAssetUrls(manifest, urlBuilder); - - expect(result.application.logoUrl).toBeUndefined(); - }); - - it('should resolve relative screenshot paths', () => { - const manifest = buildMinimalManifest({ - screenshots: ['screen1.png', 'screen2.png'], - }); - - const result = resolveManifestAssetUrls(manifest, urlBuilder); - - expect(result.application.screenshots).toEqual([ - 'https://cdn.example.com/pkg/screen1.png', - 'https://cdn.example.com/pkg/screen2.png', - ]); - }); - - it('should not modify absolute screenshot URLs', () => { - const manifest = buildMinimalManifest({ - screenshots: [ - 'https://example.com/screen1.png', - 'https://example.com/screen2.png', - ], - }); - - const result = resolveManifestAssetUrls(manifest, urlBuilder); - - expect(result.application.screenshots).toEqual([ - 'https://example.com/screen1.png', - 'https://example.com/screen2.png', - ]); - }); - - it('should handle a mix of relative and absolute screenshot URLs', () => { - const manifest = buildMinimalManifest({ - screenshots: ['relative.png', 'https://example.com/absolute.png'], - }); - - const result = resolveManifestAssetUrls(manifest, urlBuilder); - - expect(result.application.screenshots).toEqual([ - 'https://cdn.example.com/pkg/relative.png', - 'https://example.com/absolute.png', - ]); - }); - - it('should handle empty screenshots array', () => { - const manifest = buildMinimalManifest({ screenshots: [] }); - - const result = resolveManifestAssetUrls(manifest, urlBuilder); - - expect(result.application.screenshots).toEqual([]); - }); - - it('should handle undefined screenshots', () => { - const manifest = buildMinimalManifest(); - - const result = resolveManifestAssetUrls(manifest, urlBuilder); - - expect(result.application.screenshots).toEqual([]); - }); - - it('should resolve a relative logo', () => { - const manifest = buildMinimalManifest({ logo: 'logo.png' }); - - const result = resolveManifestAssetUrls(manifest, urlBuilder); - - expect(result.application.logo).toBe( - 'https://cdn.example.com/pkg/logo.png', - ); - }); - - it('should not modify an absolute logo', () => { - const manifest = buildMinimalManifest({ - logo: 'https://example.com/logo.png', - }); - - const result = resolveManifestAssetUrls(manifest, urlBuilder); - - expect(result.application.logo).toBe('https://example.com/logo.png'); - }); - - it('should resolve galleryImages paths and leave absolute ones untouched', () => { - const manifest = buildMinimalManifest({ - galleryImages: ['a.png', 'https://example.com/b.png'], - }); - - const result = resolveManifestAssetUrls(manifest, urlBuilder); - - expect(result.application.galleryImages).toEqual([ - 'https://cdn.example.com/pkg/a.png', - 'https://example.com/b.png', - ]); - }); - - it('should handle undefined galleryImages', () => { - const manifest = buildMinimalManifest(); - - const result = resolveManifestAssetUrls(manifest, urlBuilder); - - expect(result.application.galleryImages).toEqual([]); - }); - - it('should preserve all other manifest properties', () => { - const manifest = buildMinimalManifest({ - logoUrl: 'logo.png', - author: 'Test Author', - websiteUrl: 'https://test.com', - }); - manifest.objects = [{ universalIdentifier: 'obj-1' } as never]; - - const result = resolveManifestAssetUrls(manifest, urlBuilder); - - expect(result.application.author).toBe('Test Author'); - expect(result.application.websiteUrl).toBe('https://test.com'); - expect(result.application.displayName).toBe('Test App'); - expect(result.objects).toEqual([{ universalIdentifier: 'obj-1' }]); - }); -}); diff --git a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/utils/resolve-manifest-asset-urls.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-marketplace/utils/resolve-manifest-asset-urls.util.ts deleted file mode 100644 index 7433841736..0000000000 --- a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/utils/resolve-manifest-asset-urls.util.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { type Manifest } from 'twenty-shared/application'; - -const isAbsoluteUrl = (url: string): boolean => - url.startsWith('http://') || url.startsWith('https://'); - -export const resolveManifestAssetUrls = ( - manifest: Manifest, - urlBuilder: (filePath: string) => string, -): Manifest => { - const resolveUrl = (url: string): string => - isAbsoluteUrl(url) ? url : urlBuilder(url); - - return { - ...manifest, - application: { - ...manifest.application, - logoUrl: manifest.application.logoUrl - ? resolveUrl(manifest.application.logoUrl) - : undefined, - logo: manifest.application.logo - ? resolveUrl(manifest.application.logo) - : undefined, - screenshots: (manifest.application.screenshots ?? []).map(resolveUrl), - galleryImages: (manifest.application.galleryImages ?? []).map(resolveUrl), - }, - }; -}; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/__tests__/application-registration-asset-url.service.spec.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/__tests__/application-registration-asset-url.service.spec.ts new file mode 100644 index 0000000000..93a3ce5e1e --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/__tests__/application-registration-asset-url.service.spec.ts @@ -0,0 +1,149 @@ +import { Test, type TestingModule } from '@nestjs/testing'; + +import { type Manifest } from 'twenty-shared/application'; + +import { ApplicationRegistrationAssetUrlService } from 'src/engine/core-modules/application/application-registration/application-registration-asset-url.service'; +import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum'; +import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; + +const CONFIG_VALUES: Record = { + SERVER_URL: 'https://api.twenty.com', + APP_REGISTRY_CDN_URL: 'https://cdn.registry.com', +}; + +const baseRegistration = { + sourceType: ApplicationRegistrationSourceType.TARBALL, + sourcePackage: null, + latestAvailableVersion: '1.0.0', + logo: null, + logoFileId: null, + galleryImages: null, + screenshots: [], + manifest: null, +}; + +describe('ApplicationRegistrationAssetUrlService', () => { + let service: ApplicationRegistrationAssetUrlService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + ApplicationRegistrationAssetUrlService, + { + provide: TwentyConfigService, + useValue: { + get: jest.fn((key: string) => CONFIG_VALUES[key]), + }, + }, + ], + }).compile(); + + service = module.get(ApplicationRegistrationAssetUrlService); + }); + + describe('buildLogoUrl', () => { + it('should build a server file url when a logoFileId is set', () => { + const logoUrl = service.buildLogoUrl({ + ...baseRegistration, + logo: 'public/logo.png', + logoFileId: 'file-id', + }); + + expect(logoUrl).toBe( + 'https://api.twenty.com/file/server/application-registration/file-id', + ); + }); + + it('should pass through absolute logo urls', () => { + const logoUrl = service.buildLogoUrl({ + ...baseRegistration, + logo: 'https://example.com/logo.png', + }); + + expect(logoUrl).toBe('https://example.com/logo.png'); + }); + + it('should build a registry cdn url for npm registrations', () => { + const logoUrl = service.buildLogoUrl({ + ...baseRegistration, + sourceType: ApplicationRegistrationSourceType.NPM, + sourcePackage: '@twenty/my-app', + latestAvailableVersion: '2.3.4', + logo: 'public/logo.png', + }); + + expect(logoUrl).toBe( + 'https://cdn.registry.com/@twenty/my-app@2.3.4/public/logo.png', + ); + }); + + it('should return null for a relative path without a stored file on non-npm registrations', () => { + const logoUrl = service.buildLogoUrl({ + ...baseRegistration, + logo: 'public/logo.png', + }); + + expect(logoUrl).toBeNull(); + }); + + it('should return null when there is no logo at all', () => { + const logoUrl = service.buildLogoUrl(baseRegistration); + + expect(logoUrl).toBeNull(); + }); + }); + + describe('buildGalleryImageUrls', () => { + it('should resolve stored gallery images by fileId and keep absolute urls', () => { + const urls = service.buildGalleryImageUrls({ + ...baseRegistration, + galleryImages: [ + { path: 'public/one.png', fileId: 'file-1' }, + { path: 'https://example.com/two.png', fileId: null }, + { path: 'public/missing.png', fileId: null }, + ], + }); + + expect(urls).toEqual([ + 'https://api.twenty.com/file/server/application-registration/file-1', + 'https://example.com/two.png', + ]); + }); + + it('should resolve npm gallery images from the registry cdn', () => { + const urls = service.buildGalleryImageUrls({ + ...baseRegistration, + sourceType: ApplicationRegistrationSourceType.NPM, + sourcePackage: '@twenty/my-app', + latestAvailableVersion: '2.3.4', + galleryImages: [{ path: 'public/one.png', fileId: null }], + }); + + expect(urls).toEqual([ + 'https://cdn.registry.com/@twenty/my-app@2.3.4/public/one.png', + ]); + }); + + it('should fall back to the deprecated screenshots column', () => { + const urls = service.buildGalleryImageUrls({ + ...baseRegistration, + screenshots: ['https://example.com/screenshot.png'], + }); + + expect(urls).toEqual(['https://example.com/screenshot.png']); + }); + + it('should fall back to manifest gallery images when nothing is backfilled', () => { + const urls = service.buildGalleryImageUrls({ + ...baseRegistration, + manifest: { + application: { + galleryImages: ['https://example.com/from-manifest.png'], + }, + } as unknown as Manifest, + }); + + expect(urls).toEqual(['https://example.com/from-manifest.png']); + }); + }); +}); diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration-asset-url.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration-asset-url.service.ts new file mode 100644 index 0000000000..a5b83c3f7e --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration-asset-url.service.ts @@ -0,0 +1,109 @@ +import { Injectable } from '@nestjs/common'; + +import { ServerFileFolder } from 'twenty-shared/types'; +import { isDefined, isNonEmptyArray } from 'twenty-shared/utils'; + +import { buildRegistryCdnUrl } from 'src/engine/core-modules/application/application-marketplace/utils/build-registry-cdn-url.util'; +import { SERVER_FILE_STORAGE_PREFIX } from 'src/engine/core-modules/file-storage/constants/server-file-storage-prefix.constant'; +import { type ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity'; +import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum'; +import { toGalleryImagePaths } from 'src/engine/core-modules/application/application-registration/utils/to-gallery-image-paths.util'; +import { isAbsoluteUrl } from 'src/engine/core-modules/application/application-registration/utils/is-absolute-url.util'; +import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; + +type AssetSourceFields = Pick< + ApplicationRegistrationEntity, + 'sourceType' | 'sourcePackage' | 'latestAvailableVersion' +>; + +// Builds display URLs for application registration assets at query time. +// Assets stored as instance-global server files (TARBALL, LOCAL) are served by +// fileId; NPM assets are served straight from the registry CDN; absolute URLs +// pass through untouched. +@Injectable() +export class ApplicationRegistrationAssetUrlService { + constructor(private readonly twentyConfigService: TwentyConfigService) {} + + buildLogoUrl( + registration: AssetSourceFields & + Pick, + ): string | null { + return this.resolveAssetUrl({ + fileId: registration.logoFileId, + path: registration.logo, + registration, + }); + } + + buildGalleryImageUrls( + registration: AssetSourceFields & + Pick< + ApplicationRegistrationEntity, + 'galleryImages' | 'screenshots' | 'manifest' + >, + ): string[] { + // TODO: read galleryImages only, once the deprecated screenshots column + // and manifest fallback are backfilled away. + const galleryImages = isNonEmptyArray(registration.galleryImages) + ? registration.galleryImages + : this.toGalleryImageFallbackEntries(registration); + + return galleryImages + .map(({ path, fileId }) => + this.resolveAssetUrl({ fileId, path, registration }), + ) + .filter(isDefined); + } + + private toGalleryImageFallbackEntries( + registration: Pick< + ApplicationRegistrationEntity, + 'screenshots' | 'manifest' + >, + ): { path: string; fileId: null }[] { + const paths = isNonEmptyArray(registration.screenshots) + ? registration.screenshots + : toGalleryImagePaths(registration.manifest?.application); + + return paths.map((path) => ({ path, fileId: null })); + } + + private resolveAssetUrl({ + fileId, + path, + registration, + }: { + fileId: string | null; + path: string | null; + registration: AssetSourceFields; + }): string | null { + if (isDefined(fileId)) { + const serverUrl = this.twentyConfigService.get('SERVER_URL'); + + return `${serverUrl}/file/${SERVER_FILE_STORAGE_PREFIX}/${ServerFileFolder.ApplicationRegistration}/${fileId}`; + } + + if (!isDefined(path) || path.length === 0) { + return null; + } + + if (isAbsoluteUrl(path)) { + return path; + } + + if ( + registration.sourceType === ApplicationRegistrationSourceType.NPM && + isDefined(registration.sourcePackage) && + isDefined(registration.latestAvailableVersion) + ) { + return buildRegistryCdnUrl({ + cdnBaseUrl: this.twentyConfigService.get('APP_REGISTRY_CDN_URL'), + packageName: registration.sourcePackage, + version: registration.latestAvailableVersion, + filePath: path, + }); + } + + return null; + } +} diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration-asset.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration-asset.service.ts new file mode 100644 index 0000000000..ec085b4faf --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration-asset.service.ts @@ -0,0 +1,182 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { ServerFileFolder } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; +import { type QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; + +import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity'; +import { type ApplicationRegistrationGalleryImage } from 'src/engine/core-modules/application/application-registration/types/application-registration-gallery-image.type'; +import { isStorableAssetPath } from 'src/engine/core-modules/application/application-registration/utils/is-storable-asset-path.util'; +import { toGalleryImagePaths } from 'src/engine/core-modules/application/application-registration/utils/to-gallery-image-paths.util'; +import { ServerFileStorageService } from 'src/engine/core-modules/file-storage/services/server-file-storage.service'; +import { prepareFileForStorageOrThrow } from 'src/engine/core-modules/file-storage/utils/prepare-file-for-storage-or-throw.util'; +import type { ApplicationManifest } from 'twenty-shared/application'; + +export type ReadRegistrationAsset = (path: string) => Promise; + +// Copies the manifest logo and gallery images into instance-global server file +// storage so their display URLs can be built at query time from fileIds, +// regardless of how the registration was created (LOCAL, TARBALL, NPM). +@Injectable() +export class ApplicationRegistrationAssetService { + private readonly logger = new Logger( + ApplicationRegistrationAssetService.name, + ); + + constructor( + @InjectRepository(ApplicationRegistrationEntity) + private readonly applicationRegistrationRepository: Repository, + private readonly serverFileStorageService: ServerFileStorageService, + ) {} + + async storeRegistrationAssets({ + applicationRegistrationId, + manifestApplication, + readAsset, + }: { + applicationRegistrationId: string; + manifestApplication: ApplicationManifest | undefined; + readAsset: ReadRegistrationAsset; + }): Promise { + const existing = await this.applicationRegistrationRepository.findOneOrFail( + { + select: ['id', 'logo', 'logoFileId', 'galleryImages'], + where: { id: applicationRegistrationId }, + }, + ); + + const logoFileId = await this.storeLogoFile({ + applicationRegistrationId, + manifestApplication, + readAsset, + }); + + const galleryImages = await this.storeGalleryImageFiles({ + applicationRegistrationId, + manifestApplication, + readAsset, + }); + + // A transient read/download failure must not clobber a working asset: + // keep the previously stored fileId when the path did not change. + const logoPath = manifestApplication?.logo ?? manifestApplication?.logoUrl; + const existingFileIdByPath = new Map( + (existing.galleryImages ?? []).map(({ path, fileId }) => [path, fileId]), + ); + + await this.applicationRegistrationRepository.update( + applicationRegistrationId, + { + logoFileId: + logoFileId ?? + (isDefined(logoPath) && + isStorableAssetPath(logoPath) && + existing.logo === logoPath + ? existing.logoFileId + : null), + galleryImages: galleryImages.map((galleryImage) => ({ + ...galleryImage, + fileId: + galleryImage.fileId ?? + existingFileIdByPath.get(galleryImage.path) ?? + null, + })), + } as QueryDeepPartialEntity, + ); + } + + private async storeLogoFile({ + applicationRegistrationId, + manifestApplication, + readAsset, + }: { + applicationRegistrationId: string; + manifestApplication: ApplicationManifest | undefined; + readAsset: ReadRegistrationAsset; + }): Promise { + const logoPath = manifestApplication?.logo ?? manifestApplication?.logoUrl; + + if (!isDefined(logoPath)) { + return null; + } + + return this.storeAssetFile({ + applicationRegistrationId, + path: logoPath, + readAsset, + }); + } + + private async storeGalleryImageFiles({ + applicationRegistrationId, + manifestApplication, + readAsset, + }: { + applicationRegistrationId: string; + manifestApplication: ApplicationManifest | undefined; + readAsset: ReadRegistrationAsset; + }): Promise { + const galleryImages: ApplicationRegistrationGalleryImage[] = []; + + for (const path of toGalleryImagePaths(manifestApplication)) { + const fileId = await this.storeAssetFile({ + applicationRegistrationId, + path, + readAsset, + }); + + // Entries without a fileId (absolute URLs, missing files) are kept so + // the query-time URL resolution can still fall back on the raw path. + galleryImages.push({ path, fileId }); + } + + return galleryImages; + } + + private async storeAssetFile({ + applicationRegistrationId, + path, + readAsset, + }: { + applicationRegistrationId: string; + path: string; + readAsset: ReadRegistrationAsset; + }): Promise { + if (!isStorableAssetPath(path)) { + return null; + } + + try { + const contents = await readAsset(path); + + if (!isDefined(contents)) { + return null; + } + + const { sourceFile, mimeType } = await prepareFileForStorageOrThrow({ + sourceFile: contents, + resourcePath: path, + }); + + const savedFile = await this.serverFileStorageService.writeServerFile({ + fileFolder: ServerFileFolder.ApplicationRegistration, + applicationRegistrationId, + resourcePath: path, + contents: Buffer.isBuffer(sourceFile) + ? sourceFile + : Buffer.from(sourceFile), + mimeType, + }); + + return savedFile.id; + } catch (error) { + this.logger.warn( + `Failed to store asset "${path}" for registration ${applicationRegistrationId}: ${error.message}`, + ); + + return null; + } + } +} diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration-summary.resolver.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration-summary.resolver.ts new file mode 100644 index 0000000000..a412733d5f --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration-summary.resolver.ts @@ -0,0 +1,24 @@ +import { Parent, ResolveField } from '@nestjs/graphql'; + +import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator'; +import { ApplicationRegistrationAssetUrlService } from 'src/engine/core-modules/application/application-registration/application-registration-asset-url.service'; +import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity'; +import { ApplicationRegistrationSummaryDTO } from 'src/engine/core-modules/application/application-registration/dtos/application-registration-summary.dto'; + +// The summary is resolved from ApplicationRegistrationEntity instances loaded +// through the Application.applicationRegistration relation. +@MetadataResolver(() => ApplicationRegistrationSummaryDTO) +export class ApplicationRegistrationSummaryResolver { + constructor( + private readonly applicationRegistrationAssetUrlService: ApplicationRegistrationAssetUrlService, + ) {} + + @ResolveField(() => String, { nullable: true }) + logoUrl( + @Parent() registration: ApplicationRegistrationEntity, + ): string | null { + return this.applicationRegistrationAssetUrlService.buildLogoUrl( + registration, + ); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.entity.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.entity.ts index 3ff754e696..05e9fc9f7d 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.entity.ts @@ -150,6 +150,17 @@ export class ApplicationRegistrationEntity { }) logo: string | null; + @Column({ nullable: true, type: 'uuid' }) + @WasIntroducedInUpgrade({ + upgradeCommandName: + '2.21.0_AddLogoFileIdToApplicationRegistrationFastInstanceCommand_1783945979243', + }) + logoFileId: string | null; + + @OneToOne(() => FileEntity, { onDelete: 'SET NULL', nullable: true }) + @JoinColumn({ name: 'logoFileId' }) + logoFile: Relation | null; + @Column({ nullable: true, type: 'text' }) @WasIntroducedInUpgrade({ upgradeCommandName: @@ -213,16 +224,6 @@ export class ApplicationRegistrationEntity { }) screenshots: string[]; - @Field(() => String, { nullable: true }) - get logoUrl(): string | null { - return ( - this.logo ?? - this.manifest?.application?.logo ?? - this.manifest?.application?.logoUrl ?? - null - ); - } - @OneToMany( () => ApplicationRegistrationVariableEntity, (variable) => variable.applicationRegistration, diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.module.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.module.ts index cf3c78dc1c..d128a40f9c 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.module.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.module.ts @@ -2,12 +2,14 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { CoreEntityCacheModule } from 'src/engine/core-entity-cache/core-entity-cache.module'; +import { ApplicationRegistrationAssetUrlService } from 'src/engine/core-modules/application/application-registration/application-registration-asset-url.service'; +import { ApplicationRegistrationAssetService } from 'src/engine/core-modules/application/application-registration/application-registration-asset.service'; import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity'; import { ApplicationRegistrationResolver } from 'src/engine/core-modules/application/application-registration/application-registration.resolver'; import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service'; +import { ApplicationRegistrationSummaryResolver } from 'src/engine/core-modules/application/application-registration/application-registration-summary.resolver'; import { ApplicationRegistrationVariableModule } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.module'; import { ApplicationTarballService } from 'src/engine/core-modules/application/application-registration/application-tarball.service'; -import { ManifestAssetUrlResolverService } from 'src/engine/core-modules/application/application-registration/manifest-asset-url-resolver.service'; import { ApplicationPackageModule } from 'src/engine/core-modules/application/application-package/application-package.module'; import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; @@ -42,13 +44,16 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/ providers: [ ApplicationRegistrationService, ApplicationRegistrationResolver, + ApplicationRegistrationSummaryResolver, ApplicationTarballService, - ManifestAssetUrlResolverService, + ApplicationRegistrationAssetService, + ApplicationRegistrationAssetUrlService, ], exports: [ ApplicationRegistrationService, ApplicationRegistrationVariableModule, - ManifestAssetUrlResolverService, + ApplicationRegistrationAssetService, + ApplicationRegistrationAssetUrlService, ], }) export class ApplicationRegistrationModule {} diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.resolver.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.resolver.ts index 65d76d23e6..dd1d631ff1 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.resolver.ts @@ -23,6 +23,7 @@ import { ApplicationRegistrationVariableService } from 'src/engine/core-modules/ import { CreateApplicationRegistrationVariableInput } from 'src/engine/core-modules/application/application-registration-variable/dtos/create-application-registration-variable.input'; import { UpdateApplicationRegistrationVariableInput } from 'src/engine/core-modules/application/application-registration-variable/dtos/update-application-registration-variable.input'; import { ApplicationRegistrationExceptionFilter } from 'src/engine/core-modules/application/application-registration/application-registration-exception-filter'; +import { ApplicationRegistrationAssetUrlService } from 'src/engine/core-modules/application/application-registration/application-registration-asset-url.service'; import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity'; import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service'; import { ApplicationTarballService } from 'src/engine/core-modules/application/application-registration/application-tarball.service'; @@ -67,6 +68,7 @@ export class ApplicationRegistrationResolver { private readonly applicationRegistrationService: ApplicationRegistrationService, private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService, private readonly applicationTarballService: ApplicationTarballService, + private readonly applicationRegistrationAssetUrlService: ApplicationRegistrationAssetUrlService, private readonly fileUrlService: FileUrlService, private readonly twentyConfigService: TwentyConfigService, ) {} @@ -356,4 +358,22 @@ export class ApplicationRegistrationResolver { applicationRegistrationId: registration.id, }); } + + @ResolveField(() => String, { nullable: true }) + logoUrl( + @Parent() registration: ApplicationRegistrationEntity, + ): string | null { + return this.applicationRegistrationAssetUrlService.buildLogoUrl( + registration, + ); + } + + @ResolveField(() => [String]) + galleryImagesUrls( + @Parent() registration: ApplicationRegistrationEntity, + ): string[] { + return this.applicationRegistrationAssetUrlService.buildGalleryImageUrls( + registration, + ); + } } diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.service.ts index f169a0229d..c30e66deff 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.service.ts @@ -11,7 +11,9 @@ import { v4 } from 'uuid'; import { ALL_OAUTH_SCOPES } from 'src/engine/core-modules/application/application-oauth/constants/oauth-scopes'; import { shouldRefreshApplicationRegistrationOnInstall } from 'src/engine/core-modules/application/application-install/utils/should-refresh-application-registration-on-install.util'; +import { ApplicationRegistrationAssetUrlService } from 'src/engine/core-modules/application/application-registration/application-registration-asset-url.service'; import { CacheLockService } from 'src/engine/core-modules/cache-lock/cache-lock.service'; +import { ServerFileStorageService } from 'src/engine/core-modules/file-storage/services/server-file-storage.service'; import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity'; import { TWENTY_CLI_APPLICATION_REGISTRATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-cli-application-registration.constant'; import { @@ -59,6 +61,7 @@ const APPLICATION_REGISTRATION_WITHOUT_MANIFEST_SELECT: (keyof ApplicationRegist 'isVetted', 'isPreInstalled', 'logo', + 'logoFileId', 'description', 'author', 'category', @@ -97,6 +100,8 @@ export class ApplicationRegistrationService { @InjectRepository(WorkspaceEntity) private readonly workspaceRepository: Repository, private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService, + private readonly applicationRegistrationAssetUrlService: ApplicationRegistrationAssetUrlService, + private readonly serverFileStorageService: ServerFileStorageService, private readonly cacheLockService: CacheLockService, private readonly coreEntityCacheService: CoreEntityCacheService, ) {} @@ -225,7 +230,17 @@ export class ApplicationRegistrationService { ): Promise { const registration = await this.applicationRegistrationRepository.findOne({ where: { oAuthClientId: clientId }, - select: ['id', 'name', 'logo', 'websiteUrl', 'oAuthScopes'], + select: [ + 'id', + 'name', + 'logo', + 'logoFileId', + 'sourceType', + 'sourcePackage', + 'latestAvailableVersion', + 'websiteUrl', + 'oAuthScopes', + ], }); if (!registration) { @@ -235,7 +250,8 @@ export class ApplicationRegistrationService { return { id: registration.id, name: registration.name, - logoUrl: registration.logo, + logoUrl: + this.applicationRegistrationAssetUrlService.buildLogoUrl(registration), websiteUrl: registration.websiteUrl, oAuthScopes: registration.oAuthScopes, }; @@ -389,11 +405,28 @@ export class ApplicationRegistrationService { return; } + const displayFields = fromManifestApplicationToDisplayFields( + manifest.application, + ); + + // Gallery image files are stored by the source-specific flows (tarball + // upload, dev sync); keep their fileIds for paths that did not change. + const existingFileIdByPath = new Map( + (existing.galleryImages ?? []).map(({ path, fileId }) => [ + path, + fileId, + ]), + ); + await this.applicationRegistrationRepository.save({ ...existing, name: manifest.application.displayName, manifest, - ...fromManifestApplicationToDisplayFields(manifest.application), + ...displayFields, + galleryImages: displayFields.galleryImages.map((galleryImage) => ({ + ...galleryImage, + fileId: existingFileIdByPath.get(galleryImage.path) ?? null, + })), ...(sourceType !== undefined && { sourceType }), ...(latestAvailableVersion !== undefined && { latestAvailableVersion, @@ -408,6 +441,17 @@ export class ApplicationRegistrationService { await this.findOneById(id, ownerWorkspaceId); await this.applicationRegistrationRepository.softDelete(id); + // Stored assets (logo, gallery images) are gone for good; deleting the + // file rows also nulls logoFileId through its ON DELETE SET NULL fk. + try { + await this.serverFileStorageService.deleteByApplicationRegistrationId(id); + } catch (error) { + this.logger.error( + `Failed to delete server files for registration ${id}`, + error, + ); + } + await this.invalidateMarketplaceAppsCache(); return true; @@ -464,6 +508,17 @@ export class ApplicationRegistrationService { const isVetted = vettedIdentifiers.has(params.universalIdentifier); if (isDefined(existing)) { + const displayFields = fromManifestApplicationToDisplayFields( + params.manifest?.application, + ); + + const existingFileIdByPath = new Map( + (existing.galleryImages ?? []).map(({ path, fileId }) => [ + path, + fileId, + ]), + ); + await this.applicationRegistrationRepository.save({ ...existing, name: params.name, @@ -472,7 +527,11 @@ export class ApplicationRegistrationService { latestAvailableVersion: params.latestAvailableVersion, isVetted, manifest: params.manifest, - ...fromManifestApplicationToDisplayFields(params.manifest?.application), + ...displayFields, + galleryImages: displayFields.galleryImages.map((galleryImage) => ({ + ...galleryImage, + fileId: existingFileIdByPath.get(galleryImage.path) ?? null, + })), }); } else { const registration = this.applicationRegistrationRepository.create({ @@ -552,9 +611,12 @@ export class ApplicationRegistrationService { 'id', 'universalIdentifier', 'name', + 'sourceType', 'sourcePackage', + 'latestAvailableVersion', 'isVetted', 'logo', + 'logoFileId', 'description', 'author', 'category', @@ -574,7 +636,8 @@ export class ApplicationRegistrationService { description: registration.description, author: registration.author, category: registration.category, - logoUrl: registration.logo, + logoUrl: + this.applicationRegistrationAssetUrlService.buildLogoUrl(registration), })); } diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-tarball.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-tarball.service.ts index fb2125558e..b77ef2bce9 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-tarball.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-tarball.service.ts @@ -6,7 +6,7 @@ import { tmpdir } from 'os'; import { isAbsolute, join, relative, resolve } from 'path'; import semver from 'semver'; -import { FileFolder, ServerFileFolder } from 'twenty-shared/types'; +import { FileFolder } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { Repository } from 'typeorm'; import { type QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; @@ -18,20 +18,16 @@ import { extractTarballSecurely } from 'src/engine/core-modules/application/appl import { readJsonFile } from 'src/engine/core-modules/application/application-package/utils/read-json-file.util'; import { resolvePackageContentDir } from 'src/engine/core-modules/application/application-package/utils/tarball-utils'; import { ApplicationRegistrationVariableService } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.service'; +import { ApplicationRegistrationAssetService } from 'src/engine/core-modules/application/application-registration/application-registration-asset.service'; import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity'; import { ApplicationRegistrationException, ApplicationRegistrationExceptionCode, } from 'src/engine/core-modules/application/application-registration/application-registration.exception'; import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum'; -import { type ApplicationRegistrationGalleryImage } from 'src/engine/core-modules/application/application-registration/types/application-registration-gallery-image.type'; import { fromManifestApplicationToDisplayFields } from 'src/engine/core-modules/application/application-registration/utils/from-manifest-application-to-display-fields.util'; -import { isImageFilePath } from 'src/engine/core-modules/application/application-registration/utils/is-image-file-path.util'; -import { toGalleryImagePaths } from 'src/engine/core-modules/application/application-registration/utils/to-gallery-image-paths.util'; import { ApplicationService } from 'src/engine/core-modules/application/application.service'; import { FileStorageService } from 'src/engine/core-modules/file-storage/services/file-storage.service'; -import { ServerFileStorageService } from 'src/engine/core-modules/file-storage/services/server-file-storage.service'; -import { prepareFileForStorageOrThrow } from 'src/engine/core-modules/file-storage/utils/prepare-file-for-storage-or-throw.util'; import type { ApplicationManifest } from 'twenty-shared/application'; @Injectable() @@ -42,7 +38,7 @@ export class ApplicationTarballService { @InjectRepository(ApplicationRegistrationEntity) private readonly appRegistrationRepository: Repository, private readonly fileStorageService: FileStorageService, - private readonly serverFileStorageService: ServerFileStorageService, + private readonly applicationRegistrationAssetService: ApplicationRegistrationAssetService, private readonly applicationService: ApplicationService, private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService, private readonly applicationVersionValidationService: ApplicationVersionValidationService, @@ -208,10 +204,10 @@ export class ApplicationTarballService { ownerWorkspaceId: params.ownerWorkspaceId, } as QueryDeepPartialEntity); - await this.storeGalleryImageFiles({ + await this.applicationRegistrationAssetService.storeRegistrationAssets({ applicationRegistrationId: appRegistration.id, manifestApplication: manifest.application, - contentDir, + readAsset: (path) => this.readAssetFromContentDir(contentDir, path), }); if (manifest.application?.serverVariables) { @@ -233,51 +229,10 @@ export class ApplicationTarballService { } } - private async storeGalleryImageFiles({ - applicationRegistrationId, - manifestApplication, - contentDir, - }: { - applicationRegistrationId: string; - manifestApplication: ApplicationManifest | undefined; - contentDir: string; - }): Promise { - const galleryImages: ApplicationRegistrationGalleryImage[] = []; - - for (const path of toGalleryImagePaths(manifestApplication)) { - const fileId = await this.storeGalleryImageFile({ - applicationRegistrationId, - contentDir, - path, - }); - - if (isDefined(fileId)) { - galleryImages.push({ path, fileId }); - } - } - - await this.appRegistrationRepository.update(applicationRegistrationId, { - galleryImages, - } as QueryDeepPartialEntity); - } - - private async storeGalleryImageFile({ - applicationRegistrationId, - contentDir, - path, - }: { - applicationRegistrationId: string; - contentDir: string; - path: string; - }): Promise { - if ( - path.startsWith('http://') || - path.startsWith('https://') || - !isImageFilePath(path) - ) { - return null; - } - + private async readAssetFromContentDir( + contentDir: string, + path: string, + ): Promise { const absolutePath = resolve(contentDir, path); const relativeToContentDir = relative(contentDir, absolutePath); @@ -287,37 +242,12 @@ export class ApplicationTarballService { isAbsolute(relativeToContentDir) ) { this.logger.warn( - `Gallery image "${path}" escapes the package directory; skipping`, + `Asset "${path}" escapes the package directory; skipping`, ); return null; } - try { - const contents = await fs.readFile(absolutePath); - - const { sourceFile, mimeType } = await prepareFileForStorageOrThrow({ - sourceFile: contents, - resourcePath: path, - }); - - const savedFile = await this.serverFileStorageService.writeServerFile({ - fileFolder: ServerFileFolder.ApplicationRegistration, - applicationRegistrationId, - resourcePath: path, - contents: Buffer.isBuffer(sourceFile) - ? sourceFile - : Buffer.from(sourceFile), - mimeType, - }); - - return savedFile.id; - } catch (error) { - this.logger.warn( - `Failed to store gallery image "${path}" for registration ${applicationRegistrationId}: ${error.message}`, - ); - - return null; - } + return fs.readFile(absolutePath); } } diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/manifest-asset-url-resolver.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/manifest-asset-url-resolver.service.ts deleted file mode 100644 index d4d2e77f21..0000000000 --- a/packages/twenty-server/src/engine/core-modules/application/application-registration/manifest-asset-url-resolver.service.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -import { type Manifest } from 'twenty-shared/application'; -import { isDefined } from 'twenty-shared/utils'; - -import { buildRegistryCdnUrl } from 'src/engine/core-modules/application/application-marketplace/utils/build-registry-cdn-url.util'; -import { resolveManifestAssetUrls } from 'src/engine/core-modules/application/application-marketplace/utils/resolve-manifest-asset-urls.util'; -import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum'; -import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; - -@Injectable() -export class ManifestAssetUrlResolverService { - constructor(private readonly twentyConfigService: TwentyConfigService) {} - - resolveFromRegistrySource({ - manifest, - packageName, - version, - }: { - manifest: Manifest; - packageName: string; - version: string; - }): Manifest { - const cdnBaseUrl = this.twentyConfigService.get('APP_REGISTRY_CDN_URL'); - - return resolveManifestAssetUrls(manifest, (filePath) => - buildRegistryCdnUrl({ cdnBaseUrl, packageName, version, filePath }), - ); - } - - resolveFromRegistration({ - sourceType, - sourcePackage, - manifest, - version, - }: { - sourceType: ApplicationRegistrationSourceType; - sourcePackage: string | null; - manifest: Manifest; - version: string; - }): Manifest { - if ( - sourceType !== ApplicationRegistrationSourceType.NPM || - !isDefined(sourcePackage) - ) { - return manifest; - } - - return this.resolveFromRegistrySource({ - manifest, - packageName: sourcePackage, - version, - }); - } -} diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/utils/__tests__/are-registration-assets-stored.util.spec.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/utils/__tests__/are-registration-assets-stored.util.spec.ts new file mode 100644 index 0000000000..89d47cf0c3 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/utils/__tests__/are-registration-assets-stored.util.spec.ts @@ -0,0 +1,82 @@ +import { areRegistrationAssetsStored } from 'src/engine/core-modules/application/application-registration/utils/are-registration-assets-stored.util'; +import type { ApplicationManifest } from 'twenty-shared/application'; + +const manifestApplication = ( + overrides: Partial, +): ApplicationManifest => + ({ + universalIdentifier: 'universal-identifier', + displayName: 'App', + ...overrides, + }) as ApplicationManifest; + +describe('areRegistrationAssetsStored', () => { + it('should be true when the manifest declares no storable assets', () => { + expect( + areRegistrationAssetsStored( + { logoFileId: null, galleryImages: null }, + manifestApplication({ + logo: 'https://example.com/logo.png', + galleryImages: ['https://example.com/shot.png'], + }), + ), + ).toBe(true); + }); + + it('should be false when a relative logo has no stored file', () => { + expect( + areRegistrationAssetsStored( + { logoFileId: null, galleryImages: [] }, + manifestApplication({ logo: 'public/logo.png' }), + ), + ).toBe(false); + }); + + it('should be false when a relative gallery image has no stored file', () => { + expect( + areRegistrationAssetsStored( + { + logoFileId: 'logo-file-id', + galleryImages: [{ path: 'public/shot.png', fileId: null }], + }, + manifestApplication({ + logo: 'public/logo.png', + galleryImages: ['public/shot.png'], + }), + ), + ).toBe(false); + }); + + it('should be true when every storable asset has a stored file', () => { + expect( + areRegistrationAssetsStored( + { + logoFileId: 'logo-file-id', + galleryImages: [ + { path: 'public/shot.png', fileId: 'shot-file-id' }, + { path: 'https://example.com/external.png', fileId: null }, + ], + }, + manifestApplication({ + logo: 'public/logo.png', + galleryImages: [ + 'public/shot.png', + 'https://example.com/external.png', + ], + }), + ), + ).toBe(true); + }); + + it('should be false when the manifest declares a gallery image the registration has never stored', () => { + expect( + areRegistrationAssetsStored( + { logoFileId: 'logo-file-id', galleryImages: [] }, + manifestApplication({ + logo: 'public/logo.png', + galleryImages: ['public/new-shot.png'], + }), + ), + ).toBe(false); + }); +}); diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/utils/are-registration-assets-stored.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/utils/are-registration-assets-stored.util.ts new file mode 100644 index 0000000000..df8c527d41 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/utils/are-registration-assets-stored.util.ts @@ -0,0 +1,37 @@ +import { isDefined } from 'twenty-shared/utils'; + +import { type ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity'; +import { isStorableAssetPath } from 'src/engine/core-modules/application/application-registration/utils/is-storable-asset-path.util'; +import { toGalleryImagePaths } from 'src/engine/core-modules/application/application-registration/utils/to-gallery-image-paths.util'; +import type { ApplicationManifest } from 'twenty-shared/application'; + +// True when every storable asset declared by the manifest already has a file +// stored for the registration, so a sync can skip re-downloading them. +export const areRegistrationAssetsStored = ( + registration: Pick< + ApplicationRegistrationEntity, + 'logoFileId' | 'galleryImages' + >, + manifestApplication: ApplicationManifest | undefined, +): boolean => { + const logoPath = manifestApplication?.logo ?? manifestApplication?.logoUrl; + + if ( + isDefined(logoPath) && + isStorableAssetPath(logoPath) && + !isDefined(registration.logoFileId) + ) { + return false; + } + + const storedFileIdByPath = new Map( + (registration.galleryImages ?? []).map(({ path, fileId }) => [ + path, + fileId, + ]), + ); + + return toGalleryImagePaths(manifestApplication) + .filter(isStorableAssetPath) + .every((path) => isDefined(storedFileIdByPath.get(path))); +}; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/utils/is-absolute-url.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/utils/is-absolute-url.util.ts new file mode 100644 index 0000000000..627511cfda --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/utils/is-absolute-url.util.ts @@ -0,0 +1,2 @@ +export const isAbsoluteUrl = (url: string): boolean => + /^https?:\/\//i.test(url); diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/utils/is-storable-asset-path.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/utils/is-storable-asset-path.util.ts new file mode 100644 index 0000000000..3d7aa40cb7 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/utils/is-storable-asset-path.util.ts @@ -0,0 +1,7 @@ +import { isAbsoluteUrl } from 'src/engine/core-modules/application/application-registration/utils/is-absolute-url.util'; +import { isImageFilePath } from 'src/engine/core-modules/application/application-registration/utils/is-image-file-path.util'; + +// Only relative image paths are copied into server file storage; absolute +// urls are served as-is at query time. +export const isStorableAssetPath = (path: string): boolean => + !isAbsoluteUrl(path) && isImageFilePath(path); diff --git a/packages/twenty-server/src/engine/core-modules/file-storage/services/__tests__/server-file-storage.service.spec.ts b/packages/twenty-server/src/engine/core-modules/file-storage/services/__tests__/server-file-storage.service.spec.ts index 961a577e36..0fc54e8911 100644 --- a/packages/twenty-server/src/engine/core-modules/file-storage/services/__tests__/server-file-storage.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/file-storage/services/__tests__/server-file-storage.service.spec.ts @@ -264,10 +264,14 @@ describe('ServerFileStorageService', () => { mockServerFileRepository.findOneBy.mockResolvedValue({ id: 'server-file-id', path: 'application-registration/registration-id/manifest.json', + mimeType: 'application/json', } as FileEntity); mockDriver.readFile.mockResolvedValue(stream); - const result = await service.readServerFileById('server-file-id'); + const result = await service.readServerFileById( + 'server-file-id', + ServerFileFolder.ApplicationRegistration, + ); expect(mockServerFileRepository.findOneBy).toHaveBeenCalledWith({ id: 'server-file-id', @@ -277,13 +281,18 @@ describe('ServerFileStorageService', () => { filePath: 'server/application-registration/registration-id/manifest.json', }); - expect(result).toBe(stream); + expect(result).toEqual({ stream, mimeType: 'application/json' }); }); it('should throw a missing-file exception when the row does not exist', async () => { mockServerFileRepository.findOneBy.mockResolvedValue(null); - await expect(service.readServerFileById('unknown-id')).rejects.toThrow( + await expect( + service.readServerFileById( + 'unknown-id', + ServerFileFolder.ApplicationRegistration, + ), + ).rejects.toThrow( expect.objectContaining({ code: FileStorageExceptionCode.FILE_NOT_FOUND, }), diff --git a/packages/twenty-server/src/engine/core-modules/file-storage/services/server-file-storage.service.ts b/packages/twenty-server/src/engine/core-modules/file-storage/services/server-file-storage.service.ts index 2fade618ac..7da4471114 100644 --- a/packages/twenty-server/src/engine/core-modules/file-storage/services/server-file-storage.service.ts +++ b/packages/twenty-server/src/engine/core-modules/file-storage/services/server-file-storage.service.ts @@ -148,14 +148,26 @@ export class ServerFileStorageService { return driver.readFile({ filePath: onStorageFilePath }); } - async readServerFileById(id: string): Promise { + async readServerFileById( + id: string, + fileFolder: ServerFileFolder, + ): Promise<{ stream: Readable; mimeType: string }> { const serverFile = await this.findServerFileByIdOrThrow(id); + if (!serverFile.path.startsWith(`${fileFolder}/`)) { + throw new FileStorageException( + `Server file ${id} not found`, + FileStorageExceptionCode.FILE_NOT_FOUND, + ); + } + const driver = this.fileStorageDriverFactory.getCurrentDriver(); - return driver.readFile({ + const stream = await driver.readFile({ filePath: this.buildServerOnStorageFilePath(serverFile), }); + + return { stream, mimeType: serverFile.mimeType }; } checkServerFileExists({ diff --git a/packages/twenty-server/src/engine/core-modules/file/controllers/file.controller.spec.ts b/packages/twenty-server/src/engine/core-modules/file/controllers/file.controller.spec.ts index afbb6a5f98..9711068c31 100644 --- a/packages/twenty-server/src/engine/core-modules/file/controllers/file.controller.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/file/controllers/file.controller.spec.ts @@ -4,12 +4,17 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { Readable } from 'stream'; import { pipeline } from 'node:stream/promises'; -import { FileFolder } from 'twenty-shared/types'; +import { FileFolder, ServerFileFolder } from 'twenty-shared/types'; jest.mock('node:stream/promises', () => ({ pipeline: jest.fn(), })); +import { + FileStorageException, + FileStorageExceptionCode, +} from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception'; +import { ServerFileStorageService } from 'src/engine/core-modules/file-storage/services/server-file-storage.service'; import { FileException, FileExceptionCode, @@ -45,6 +50,7 @@ const mockPipeline = jest.mocked(pipeline); describe('FileController', () => { let controller: FileController; let fileService: FileService; + let serverFileStorageService: ServerFileStorageService; const mock_FileByIdGuard: CanActivate = { canActivate: jest.fn(() => true) }; const mock_PublicEndpointGuard: CanActivate = { canActivate: jest.fn(() => true), @@ -65,6 +71,12 @@ describe('FileController', () => { getFilePresignedUrlOrStreamById: jest.fn(), }, }, + { + provide: ServerFileStorageService, + useValue: { + readServerFileById: jest.fn(), + }, + }, ], }) .overrideGuard(FileByIdGuard) @@ -79,6 +91,9 @@ describe('FileController', () => { controller = module.get(FileController); fileService = module.get(FileService); + serverFileStorageService = module.get( + ServerFileStorageService, + ); // Default to a resolved pipeline so happy-path tests don't have to wire it up. mockPipeline.mockResolvedValue(undefined); @@ -307,6 +322,108 @@ describe('FileController', () => { }); }); + describe('getApplicationRegistrationFileById', () => { + it('should stream the file with public cache headers', async () => { + const mockStream = createMockStream(); + + jest + .spyOn(serverFileStorageService, 'readServerFileById') + .mockResolvedValue({ + stream: mockStream, + mimeType: 'image/png', + }); + + const mockResponse = createMockResponse() as any; + + await controller.getApplicationRegistrationFileById( + mockResponse, + 'file-123', + ); + + expect(serverFileStorageService.readServerFileById).toHaveBeenCalledWith( + 'file-123', + ServerFileFolder.ApplicationRegistration, + ); + expect(mockResponse.setHeader).toHaveBeenCalledWith( + 'Content-Type', + 'image/png', + ); + expect(mockResponse.setHeader).toHaveBeenCalledWith( + 'Cache-Control', + 'public, max-age=3600', + ); + expect(mockPipeline).toHaveBeenCalledWith(mockStream, mockResponse); + }); + + it('should throw FILE_NOT_FOUND when the file does not exist', async () => { + jest + .spyOn(serverFileStorageService, 'readServerFileById') + .mockRejectedValue( + new FileStorageException( + 'Server file unknown-id not found', + FileStorageExceptionCode.FILE_NOT_FOUND, + ), + ); + + const mockResponse = createMockResponse() as any; + + await expect( + controller.getApplicationRegistrationFileById( + mockResponse, + 'unknown-id', + ), + ).rejects.toThrow( + new FileException('File not found', FileExceptionCode.FILE_NOT_FOUND), + ); + + expect(mockPipeline).not.toHaveBeenCalled(); + }); + + it('should throw INTERNAL_SERVER_ERROR when the stream errors before headers are sent', async () => { + jest + .spyOn(serverFileStorageService, 'readServerFileById') + .mockResolvedValue({ + stream: createMockStream(), + mimeType: 'image/png', + }); + + mockPipeline.mockRejectedValue(new Error('source backend exploded')); + + const mockResponse = createMockResponse({ headersSent: false }) as any; + + await expect( + controller.getApplicationRegistrationFileById(mockResponse, 'file-123'), + ).rejects.toThrow( + new FileException( + 'Error streaming file from storage', + FileExceptionCode.INTERNAL_SERVER_ERROR, + ), + ); + + expect(mockResponse.destroy).not.toHaveBeenCalled(); + }); + + it('should destroy the response without throwing when the stream errors after headers are sent', async () => { + jest + .spyOn(serverFileStorageService, 'readServerFileById') + .mockResolvedValue({ + stream: createMockStream(), + mimeType: 'image/png', + }); + + mockPipeline.mockRejectedValue(new Error('socket reset mid-flight')); + + const mockResponse = createMockResponse({ headersSent: true }) as any; + + await controller.getApplicationRegistrationFileById( + mockResponse, + 'file-123', + ); + + expect(mockResponse.destroy).toHaveBeenCalledTimes(1); + }); + }); + describe('getPublicAssets', () => { it('should 302 redirect when presigned URL is available', async () => { jest diff --git a/packages/twenty-server/src/engine/core-modules/file/controllers/file.controller.ts b/packages/twenty-server/src/engine/core-modules/file/controllers/file.controller.ts index 325bfb5c7d..3436ec193f 100644 --- a/packages/twenty-server/src/engine/core-modules/file/controllers/file.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/file/controllers/file.controller.ts @@ -11,15 +11,23 @@ import { import { pipeline } from 'node:stream/promises'; import { join } from 'path'; +import { type Readable } from 'stream'; import { Request, Response } from 'express'; -import { FileFolder } from 'twenty-shared/types'; +import { FileFolder, ServerFileFolder } from 'twenty-shared/types'; +import { SERVER_FILE_STORAGE_PREFIX } from 'src/engine/core-modules/file-storage/constants/server-file-storage-prefix.constant'; +import { + FileStorageException, + FileStorageExceptionCode, +} from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception'; +import { ServerFileStorageService } from 'src/engine/core-modules/file-storage/services/server-file-storage.service'; import { validateFilePath } from 'src/engine/core-modules/file-storage/utils/validate-file-path.util'; import { FileException, FileExceptionCode, } from 'src/engine/core-modules/file/file.exception'; +import { PUBLIC_ASSET_CACHE_CONTROL } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; import { FileApiExceptionFilter } from 'src/engine/core-modules/file/filters/file-api-exception.filter'; import { FileByIdGuard, @@ -35,7 +43,68 @@ import { PublicEndpointGuard } from 'src/engine/guards/public-endpoint.guard'; export class FileController { private readonly logger = new Logger(FileController.name); - constructor(private readonly fileService: FileService) {} + constructor( + private readonly fileService: FileService, + private readonly serverFileStorageService: ServerFileStorageService, + ) {} + + // Serves application registration assets (logo, gallery images). These are + // instance-global marketplace resources, also displayed on the public OAuth + // authorize page, hence no auth token. The /server/ segment separates + // instance-global server files from the workspace-scoped /file/:folder/:id. + @Get(`file/${SERVER_FILE_STORAGE_PREFIX}/application-registration/:id`) + @UseGuards(PublicEndpointGuard, NoPermissionGuard) + async getApplicationRegistrationFileById( + @Res() res: Response, + @Param('id') fileId: string, + ) { + let fileResponse: { stream: Readable; mimeType: string }; + + try { + fileResponse = await this.serverFileStorageService.readServerFileById( + fileId, + ServerFileFolder.ApplicationRegistration, + ); + } catch (error) { + if ( + error instanceof FileStorageException && + error.code === FileStorageExceptionCode.FILE_NOT_FOUND + ) { + throw new FileException( + 'File not found', + FileExceptionCode.FILE_NOT_FOUND, + ); + } + + this.logger.error('readServerFileById failed unexpectedly', { error }); + + throw new FileException( + 'Error retrieving file', + FileExceptionCode.INTERNAL_SERVER_ERROR, + ); + } + + setFileResponseHeaders(res, fileResponse.mimeType); + res.setHeader('Cache-Control', PUBLIC_ASSET_CACHE_CONTROL); + + try { + await pipeline(fileResponse.stream, res); + } catch (error) { + this.logger.error( + 'Application registration file stream failed mid-transfer', + { error }, + ); + + if (!res.headersSent) { + throw new FileException( + 'Error streaming file from storage', + FileExceptionCode.INTERNAL_SERVER_ERROR, + ); + } + + res.destroy(); + } + } @Get('public-assets/:workspaceId/:applicationId/*path') @UseGuards(PublicEndpointGuard, NoPermissionGuard)