Resolve application registration logo and gallery image urls at query time (#22827)
## Context
Application registration logo and gallery image urls were baked into the
stored manifest and display columns at write time, with each source flow
doing it differently: npm catalog sync baked CDN urls, local dev sync
baked `public-assets` urls, and tarball uploads left raw manifest paths
that never displayed in the UI. The entity also carried a `logoUrl`
getter computed field.
This moves url generation to query time, the same way the workspace logo
works.
## What changed
**Read side**
- `ApplicationRegistrationAssetUrlService` builds display urls when
queried: stored files are served by fileId, absolute urls pass through
untouched, and not-yet-rehosted npm assets fall back to the registry CDN
from `sourcePackage@latestAvailableVersion`.
- The `logoUrl` getter on `ApplicationRegistrationEntity` is replaced by
`logoUrl` and `galleryImages` `@ResolveField`s on the metadata resolver,
the admin panel resolver, and a new resolver for
`ApplicationRegistrationSummary` (used by
`Application.applicationRegistration`).
- The marketplace detail/card DTOs and the public OAuth authorize DTO
(`findApplicationRegistrationByClientId`) go through the same url
builder.
- New public route `GET /file/application-registration/:id` streams
registration server files (these are instance-global marketplace assets,
also shown on the public OAuth authorize page).
`ServerFileStorageService.readServerFileById` now returns the mime type
alongside the stream.
**Write side**
- New `logoFileId` column on `applicationRegistration` (2.21 fast
instance command, constraint names match TypeORM naming), complementing
the fileIds already stored in the `galleryImages` jsonb.
- `ApplicationRegistrationAssetService` copies the manifest logo and
gallery images into instance-global server file storage, so all three
sources behave the same:
- **TARBALL**: from the uploaded package (previously only gallery images
were stored, never the logo).
- **LOCAL**: dev sync reads the already-uploaded public assets from
workspace storage (the CLI uploads files before syncing).
- **NPM**: catalog sync downloads the assets from the registry CDN.
Downloads are skipped when the package version is unchanged and the
files are already stored; failed or pending downloads fall back to CDN
urls at query time.
- Write-time url rewriting is removed
(`ManifestAssetUrlResolverService`, `resolveManifestAssetUrls`);
manifests now keep raw asset paths. Existing rows with baked absolute
urls keep working through the absolute-url passthrough, so no backfill
is needed.
- `updateFromManifest` and `upsertFromCatalog` preserve stored gallery
fileIds for unchanged paths, so installs and the hourly catalog sync no
longer clobber them.
## How it was verified
Against a local Postgres/Redis with the server running:
- Fresh database init runs the new instance command; column and FK/UQ
constraint names match TypeORM's generated names, and the CI
pending-migration check produces no diff.
- `findManyApplicationRegistrations { logoUrl galleryImages }` returns
fileId-served urls for a TARBALL registration (absolute urls passed
through), and null/[] for a LOCAL registration without assets.
- Ran `marketplace:catalog-sync` against the real npm registry: 14
packages synced, logos and gallery images rehosted from unpkg with
fileIds set; a second run re-downloaded nothing (version-unchanged
skip); `findMarketplaceAppDetail` for `twenty-linear` returns
fileId-served urls for the logo and all four gallery images.
- `GET /file/application-registration/:id` serves stored files with the
right content type (png and svg verified), 404s on unknown ids, and the
token-guarded generic `/file/:folder/:id` route still returns 403
without a token.
- Unit tests for the url builder and the assets-stored check; server
unit test suites for the application module pass; typecheck and lint
clean.
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
]
|
||||
|
||||
@@ -184,6 +184,7 @@ export enum AiModelRole {
|
||||
export type ApplicationRegistration = {
|
||||
__typename?: 'ApplicationRegistration';
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
galleryImagesUrls: Array<Scalars['String']['output']>;
|
||||
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<string>, oAuthScopes: Array<string>, 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<string>, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, 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<string>, oAuthScopes: Array<string>, 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<string>, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, 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<string>, oAuthScopes: Array<string>, 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<string>, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, 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<string>, oAuthScopes: Array<string>, 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<string>, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, 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<UserInfoFragmentFragment, unknown>;
|
||||
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<ApplicationRegistrationFragmentFragment, unknown>;
|
||||
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<ApplicationRegistrationFragmentFragment, unknown>;
|
||||
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<AddAiProviderMutation, AddAiProviderMutationVariables>;
|
||||
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<AddModelToProviderMutation, AddModelToProviderMutationVariables>;
|
||||
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<RemoveAiProviderMutation, RemoveAiProviderMutationVariables>;
|
||||
@@ -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<GetModelsDevSuggestionsQuery, GetModelsDevSuggestionsQueryVariables>;
|
||||
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<BackfillApplicationInstallationMutation, BackfillApplicationInstallationMutationVariables>;
|
||||
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<SyncMarketplaceCatalogMutation, SyncMarketplaceCatalogMutationVariables>;
|
||||
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<UpdateAdminApplicationRegistrationMutation, UpdateAdminApplicationRegistrationMutationVariables>;
|
||||
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<UpdateAdminApplicationRegistrationMutation, UpdateAdminApplicationRegistrationMutationVariables>;
|
||||
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<UpdateAdminApplicationRegistrationVariableMutation, UpdateAdminApplicationRegistrationVariableMutationVariables>;
|
||||
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<FindAdminApplicationRegistrationInstalledWorkspacesQuery, FindAdminApplicationRegistrationInstalledWorkspacesQueryVariables>;
|
||||
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<FindAdminApplicationRegistrationStatsQuery, FindAdminApplicationRegistrationStatsQueryVariables>;
|
||||
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<FindAdminApplicationRegistrationVariablesQuery, FindAdminApplicationRegistrationVariablesQueryVariables>;
|
||||
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<FindAllApplicationRegistrationsQuery, FindAllApplicationRegistrationsQueryVariables>;
|
||||
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<FindAllApplicationRegistrationsQuery, FindAllApplicationRegistrationsQueryVariables>;
|
||||
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<CreateDatabaseConfigVariableMutation, CreateDatabaseConfigVariableMutationVariables>;
|
||||
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<DeleteDatabaseConfigVariableMutation, DeleteDatabaseConfigVariableMutationVariables>;
|
||||
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<UpdateDatabaseConfigVariableMutation, UpdateDatabaseConfigVariableMutationVariables>;
|
||||
@@ -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<UpdateWorkspaceFeatureFlagMutation, UpdateWorkspaceFeatureFlagMutationVariables>;
|
||||
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<AdminPanelRecentUsersQuery, AdminPanelRecentUsersQueryVariables>;
|
||||
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<AdminPanelTopWorkspacesQuery, AdminPanelTopWorkspacesQueryVariables>;
|
||||
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<FindOneAdminApplicationRegistrationQuery, FindOneAdminApplicationRegistrationQueryVariables>;
|
||||
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<FindOneAdminApplicationRegistrationQuery, FindOneAdminApplicationRegistrationQueryVariables>;
|
||||
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<GetAdminChatThreadMessagesQuery, GetAdminChatThreadMessagesQueryVariables>;
|
||||
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<GetAdminWorkspaceChatThreadsQuery, GetAdminWorkspaceChatThreadsQueryVariables>;
|
||||
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<GetServerAdminsQuery, GetServerAdminsQueryVariables>;
|
||||
|
||||
@@ -327,6 +327,7 @@ export type ApplicationConnectionProviderOAuthConfig = {
|
||||
export type ApplicationRegistration = {
|
||||
__typename?: 'ApplicationRegistration';
|
||||
createdAt: Scalars['DateTime']['output'];
|
||||
galleryImagesUrls: Array<Scalars['String']['output']>;
|
||||
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<string>, oAuthScopes: Array<string>, 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<string>, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, 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<string>, oAuthScopes: Array<string>, 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<string>, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, 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<string>, oAuthScopes: Array<string>, 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<string>, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, 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<MarketplaceAppFieldsFragment, unknown>;
|
||||
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<NavigationMenuItemFieldsFragment, unknown>;
|
||||
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<NavigationMenuItemQueryFieldsFragment, unknown>;
|
||||
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<ApplicationRegistrationFragmentFragment, unknown>;
|
||||
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<ApplicationRegistrationListItemFragment, unknown>;
|
||||
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<ApplicationRegistrationFragmentFragment, unknown>;
|
||||
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<ApplicationRegistrationListItemFragment, unknown>;
|
||||
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<BillingPriceLicensedFragmentFragment, unknown>;
|
||||
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<BillingPriceMeteredFragmentFragment, unknown>;
|
||||
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<ApiKeyFragmentFragment, unknown>;
|
||||
@@ -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<DeleteApplicationRegistrationMutation, DeleteApplicationRegistrationMutationVariables>;
|
||||
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<RotateApplicationRegistrationClientSecretMutation, RotateApplicationRegistrationClientSecretMutationVariables>;
|
||||
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<TransferApplicationRegistrationOwnershipMutation, TransferApplicationRegistrationOwnershipMutationVariables>;
|
||||
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<UpdateApplicationRegistrationMutation, UpdateApplicationRegistrationMutationVariables>;
|
||||
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<UpdateApplicationRegistrationMutation, UpdateApplicationRegistrationMutationVariables>;
|
||||
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<UpdateApplicationRegistrationVariableMutation, UpdateApplicationRegistrationVariableMutationVariables>;
|
||||
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<ApplicationRegistrationTarballUrlQuery, ApplicationRegistrationTarballUrlQueryVariables>;
|
||||
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<FindApplicationRegistrationStatsQuery, FindApplicationRegistrationStatsQueryVariables>;
|
||||
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<FindApplicationRegistrationVariablesQuery, FindApplicationRegistrationVariablesQueryVariables>;
|
||||
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<FindManyApplicationRegistrationsQuery, FindManyApplicationRegistrationsQueryVariables>;
|
||||
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<FindOneApplicationRegistrationQuery, FindOneApplicationRegistrationQueryVariables>;
|
||||
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<FindManyApplicationRegistrationsQuery, FindManyApplicationRegistrationsQueryVariables>;
|
||||
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<FindOneApplicationRegistrationQuery, FindOneApplicationRegistrationQueryVariables>;
|
||||
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<UninstallApplicationMutation, UninstallApplicationMutationVariables>;
|
||||
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<UpdateOneApplicationVariableMutation, UpdateOneApplicationVariableMutationVariables>;
|
||||
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<ApplicationConnectionProvidersQuery, ApplicationConnectionProvidersQueryVariables>;
|
||||
|
||||
@@ -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 = ({
|
||||
<Avatar
|
||||
type="app"
|
||||
size={size}
|
||||
avatarUrl={getAbsoluteImageUrl(applicationChipData.logo)}
|
||||
avatarUrl={getAbsoluteImageUrl(logoUrl ?? applicationChipData.logo)}
|
||||
placeholder={applicationChipData.name}
|
||||
placeholderColorSeed={applicationChipData.seed}
|
||||
color={applicationChipData.colors?.color}
|
||||
|
||||
@@ -20,6 +20,7 @@ export const ApplicationDisplay = ({
|
||||
<StyledAppChip
|
||||
size="md"
|
||||
applicationId={application?.id}
|
||||
logoUrl={application?.logoUrl}
|
||||
fallbackApplicationData={{
|
||||
logo: application?.logo,
|
||||
name: application?.name,
|
||||
|
||||
@@ -3,4 +3,7 @@ export type ApplicationDisplayData = {
|
||||
name?: string | null;
|
||||
universalIdentifier?: string | null;
|
||||
logo?: string | null;
|
||||
// Resolved display url (the registration's logoUrl resolve field); takes
|
||||
// precedence over the logo computed from the installed application.
|
||||
logoUrl?: string | null;
|
||||
};
|
||||
|
||||
+1
@@ -6,6 +6,7 @@ export const APPLICATION_REGISTRATION_FRAGMENT = gql`
|
||||
universalIdentifier
|
||||
name
|
||||
logoUrl
|
||||
galleryImagesUrls
|
||||
oAuthClientId
|
||||
oAuthRedirectUris
|
||||
oAuthScopes
|
||||
|
||||
+1
@@ -6,6 +6,7 @@ export const APPLICATION_REGISTRATION_LIST_ITEM_FRAGMENT = gql`
|
||||
universalIdentifier
|
||||
name
|
||||
sourceType
|
||||
logoUrl
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
+1
-1
@@ -294,8 +294,8 @@ export const SettingsAvailableApplicationDetails = () => {
|
||||
icon={
|
||||
<AppChip
|
||||
applicationId={application?.id}
|
||||
logoUrl={detail?.logo}
|
||||
fallbackApplicationData={{
|
||||
logo: detail?.logo,
|
||||
name: displayName,
|
||||
}}
|
||||
size="md"
|
||||
|
||||
+4
-1
@@ -101,7 +101,10 @@ export const SettingsApplicationsTable = ({
|
||||
return (
|
||||
<SettingsApplicationTableRow
|
||||
key={application.id}
|
||||
application={application}
|
||||
application={{
|
||||
...application,
|
||||
logoUrl: application.applicationRegistration?.logoUrl,
|
||||
}}
|
||||
hasUpdate={hasUpdate}
|
||||
sourceType={application.applicationRegistration?.sourceType}
|
||||
action={
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
import { type FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface';
|
||||
|
||||
// Idempotent: this command was re-slotted from timestamp 1783698499446 to keep
|
||||
// the 2.21 sequence append-only, so it may re-run on instances that already
|
||||
// applied it under the previous name.
|
||||
@RegisteredInstanceCommand('2.21.0', 1783945979243)
|
||||
export class AddLogoFileIdToApplicationRegistrationFastInstanceCommand
|
||||
implements FastInstanceCommand
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
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<void> {
|
||||
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"',
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
@@ -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,
|
||||
];
|
||||
|
||||
+23
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+42
-14
@@ -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<void> {
|
||||
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<Buffer | null> {
|
||||
try {
|
||||
const stream = await this.fileStorageService.readFile({
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder: FileFolder.PublicAsset,
|
||||
resourcePath: path,
|
||||
});
|
||||
|
||||
return await streamToBuffer(stream);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-8
@@ -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,
|
||||
});
|
||||
|
||||
+46
-10
@@ -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<void> {
|
||||
@@ -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)}`,
|
||||
|
||||
+12
-11
@@ -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) =>
|
||||
|
||||
+33
@@ -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<Buffer | null> {
|
||||
const cdnBaseUrl = this.twentyConfigService.get('APP_REGISTRY_CDN_URL');
|
||||
const url = buildRegistryCdnUrl({
|
||||
cdnBaseUrl,
|
||||
packageName,
|
||||
version,
|
||||
filePath,
|
||||
});
|
||||
|
||||
try {
|
||||
const { data } = await axios.get<ArrayBuffer>(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<RegistryPackageInfo[]> {
|
||||
const registryUrl = this.twentyConfigService.get('APP_REGISTRY_URL');
|
||||
|
||||
|
||||
-190
@@ -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']> = {},
|
||||
): 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' }]);
|
||||
});
|
||||
});
|
||||
-27
@@ -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),
|
||||
},
|
||||
};
|
||||
};
|
||||
+149
@@ -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<string, string> = {
|
||||
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']);
|
||||
});
|
||||
});
|
||||
});
|
||||
+109
@@ -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<ApplicationRegistrationEntity, 'logo' | 'logoFileId'>,
|
||||
): 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;
|
||||
}
|
||||
}
|
||||
+182
@@ -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<Buffer | null>;
|
||||
|
||||
// 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<ApplicationRegistrationEntity>,
|
||||
private readonly serverFileStorageService: ServerFileStorageService,
|
||||
) {}
|
||||
|
||||
async storeRegistrationAssets({
|
||||
applicationRegistrationId,
|
||||
manifestApplication,
|
||||
readAsset,
|
||||
}: {
|
||||
applicationRegistrationId: string;
|
||||
manifestApplication: ApplicationManifest | undefined;
|
||||
readAsset: ReadRegistrationAsset;
|
||||
}): Promise<void> {
|
||||
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<ApplicationRegistrationEntity>,
|
||||
);
|
||||
}
|
||||
|
||||
private async storeLogoFile({
|
||||
applicationRegistrationId,
|
||||
manifestApplication,
|
||||
readAsset,
|
||||
}: {
|
||||
applicationRegistrationId: string;
|
||||
manifestApplication: ApplicationManifest | undefined;
|
||||
readAsset: ReadRegistrationAsset;
|
||||
}): Promise<string | null> {
|
||||
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<ApplicationRegistrationGalleryImage[]> {
|
||||
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<string | null> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
+11
-10
@@ -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<FileEntity> | 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,
|
||||
|
||||
+8
-3
@@ -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 {}
|
||||
|
||||
+20
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+68
-5
@@ -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<WorkspaceEntity>,
|
||||
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<PublicApplicationRegistrationDTO | null> {
|
||||
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),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
+11
-81
@@ -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<ApplicationRegistrationEntity>,
|
||||
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<ApplicationRegistrationEntity>);
|
||||
|
||||
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<void> {
|
||||
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<ApplicationRegistrationEntity>);
|
||||
}
|
||||
|
||||
private async storeGalleryImageFile({
|
||||
applicationRegistrationId,
|
||||
contentDir,
|
||||
path,
|
||||
}: {
|
||||
applicationRegistrationId: string;
|
||||
contentDir: string;
|
||||
path: string;
|
||||
}): Promise<string | null> {
|
||||
if (
|
||||
path.startsWith('http://') ||
|
||||
path.startsWith('https://') ||
|
||||
!isImageFilePath(path)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private async readAssetFromContentDir(
|
||||
contentDir: string,
|
||||
path: string,
|
||||
): Promise<Buffer | null> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
-55
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
+82
@@ -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>,
|
||||
): 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);
|
||||
});
|
||||
});
|
||||
+37
@@ -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)));
|
||||
};
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const isAbsoluteUrl = (url: string): boolean =>
|
||||
/^https?:\/\//i.test(url);
|
||||
+7
@@ -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);
|
||||
+12
-3
@@ -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,
|
||||
}),
|
||||
|
||||
+14
-2
@@ -148,14 +148,26 @@ export class ServerFileStorageService {
|
||||
return driver.readFile({ filePath: onStorageFilePath });
|
||||
}
|
||||
|
||||
async readServerFileById(id: string): Promise<Readable> {
|
||||
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({
|
||||
|
||||
+118
-1
@@ -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>(FileController);
|
||||
fileService = module.get<FileService>(FileService);
|
||||
serverFileStorageService = module.get<ServerFileStorageService>(
|
||||
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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user