Compare commits

...

26 Commits

Author SHA1 Message Date
Charles Bochet 0c3f3c4caa Regenerate SDK metadata client types after FileOutput addition 2026-05-23 15:39:43 +02:00
Charles Bochet a278cd6d85 Merge remote-tracking branch 'origin/main' into fix-app-logo 2026-05-23 15:39:13 +02:00
Charles Bochet 2d93bfa031 Extract .url from FileOutput in installedApplication logo consumers 2026-05-23 15:31:23 +02:00
Charles Bochet 9286e81275 Regenerate GraphQL types for FileOutput logo
Applied from CI's server-validation diff output. types.ts SDK numeric IDs
will follow in a separate commit after CI re-runs against the right base
(my local main is stale).
2026-05-23 15:25:18 +02:00
Charles Bochet d348a4c908 Fix bare logo selections in remaining GraphQL queries
graphql-codegen rejects scalar selections on object types: findManyApplications
and the AI tools-table query were still selecting logo without subfields.
2026-05-23 13:31:18 +02:00
Charles Bochet 24be69dbb0 Use explicit GraphQL type tokens on nullable FileOutputDTO fields
NestJS schema builder errors with UndefinedTypeError when @Field is used
without an explicit type token on nullable scalars (the union with null
hides the reflected metadata).
2026-05-23 13:06:09 +02:00
Charles Bochet f470b4d5a5 Expose ApplicationRegistration logo as FileOutput, drop redundant column
- New core FileOutputDTO ({ fileId, label, extension, url }) mirroring the
  workspace-entity FileOutput shape, so application/registration logos no
  longer leak as raw URL strings.
- Drop the redundant `logo` text column: manifest already carries
  application.logoUrl for non-tarball sources, and tarballs use logoFileId
  + FileEntity. Backfill command removed.
- Logo resolvers (Application, ApplicationRegistration regular + admin,
  nested ApplicationRegistrationSummary) all return FileOutput via the
  shared ApplicationRegistrationLogoService.
- Frontend fragments select `logo { fileId label extension url }`;
  consumers read `.logo?.url`.
2026-05-23 13:00:25 +02:00
martmull cffd782de7 Fix wrong change 2026-05-22 12:14:40 +02:00
martmull 6482824a09 Code review returns 2026-05-22 12:01:03 +02:00
martmull 47386fd578 Code review returns 2026-05-22 12:00:24 +02:00
martmull 6095f872f5 Merge branch 'main' into fix-app-logo 2026-05-22 11:48:28 +02:00
martmull 1b1860b038 Fix missing db update 2026-05-22 09:46:34 +02:00
martmull 393f2f11f7 Restore deleted files 2026-05-22 09:41:04 +02:00
martmull 42875de279 Fix commit with conflicts 2026-05-22 09:34:19 +02:00
martmull db476b2f5c Merge branch 'main' into fix-app-logo 2026-05-22 09:09:04 +02:00
martmull c03d159e06 Merge branch 'main' into fix-app-logo 2026-05-21 20:24:40 +02:00
martmull 91f7fc66db Move to 2.8 2026-05-21 20:22:55 +02:00
martmull 3d096e9825 Remove mjs files 2026-05-21 20:10:46 +02:00
martmull 26d04719d7 Merge branch 'main' into fix-app-logo 2026-05-21 17:48:14 +02:00
martmull 5d0aa872ee Generate 2026-05-21 17:45:29 +02:00
martmull 8c2fa796e4 Remove wrong change 2026-05-21 17:44:40 +02:00
martmull 12685622c9 Fix lint 2026-05-21 17:40:32 +02:00
martmull 1c9b79013c Wrong sourceType display 2026-05-21 17:38:13 +02:00
martmull 116c17ee11 Fix source type when changing source type 2026-05-21 17:34:42 +02:00
martmull 64cfe8b07f Fix logo when tarball publish 2026-05-21 17:29:50 +02:00
martmull 6ed955e89f Init 2026-05-21 16:16:13 +02:00
56 changed files with 1669 additions and 1079 deletions
@@ -48,9 +48,9 @@ type ApplicationRegistration {
isListed: Boolean!
isFeatured: Boolean!
isPreInstalled: Boolean!
logoUrl: String
createdAt: DateTime!
updatedAt: DateTime!
logo: FileOutput
isConfigured: Boolean!
}
@@ -283,11 +283,18 @@ type Role {
rowLevelPermissionPredicateGroups: [RowLevelPermissionPredicateGroup!]
}
type FileOutput {
fileId: UUID
label: String
extension: String
url: String!
}
type ApplicationRegistrationSummary {
id: UUID!
latestAvailableVersion: String
sourceType: ApplicationRegistrationSourceType!
logoUrl: String
logo: FileOutput
}
type ApplicationVariable {
@@ -691,7 +698,6 @@ type Application {
id: UUID!
name: String!
description: String
logo: String
version: String
universalIdentifier: String!
packageJsonChecksum: String
@@ -711,6 +717,7 @@ type Application {
objects: [Object!]!
applicationVariables: [ApplicationVariable!]!
applicationRegistration: ApplicationRegistrationSummary
logo: FileOutput
}
type ViewField {
@@ -1971,7 +1978,7 @@ type CreateApplicationRegistration {
type PublicApplicationRegistration {
id: UUID!
name: String!
logoUrl: String
logo: FileOutput
websiteUrl: String
oAuthScopes: [String!]!
}
@@ -2357,6 +2364,7 @@ type MarketplaceAppDetail {
sourceType: ApplicationRegistrationSourceType!
sourcePackage: String
latestAvailableVersion: String
logo: FileOutput
isListed: Boolean!
isFeatured: Boolean!
manifest: JSON
@@ -52,9 +52,9 @@ export interface ApplicationRegistration {
isListed: Scalars['Boolean']
isFeatured: Scalars['Boolean']
isPreInstalled: Scalars['Boolean']
logoUrl?: Scalars['String']
createdAt: Scalars['DateTime']
updatedAt: Scalars['DateTime']
logo?: FileOutput
isConfigured: Scalars['Boolean']
__typename: 'ApplicationRegistration'
}
@@ -232,11 +232,19 @@ export interface Role {
__typename: 'Role'
}
export interface FileOutput {
fileId?: Scalars['UUID']
label?: Scalars['String']
extension?: Scalars['String']
url: Scalars['String']
__typename: 'FileOutput'
}
export interface ApplicationRegistrationSummary {
id: Scalars['UUID']
latestAvailableVersion?: Scalars['String']
sourceType: ApplicationRegistrationSourceType
logoUrl?: Scalars['String']
logo?: FileOutput
__typename: 'ApplicationRegistrationSummary'
}
@@ -456,7 +464,6 @@ export interface Application {
id: Scalars['UUID']
name: Scalars['String']
description?: Scalars['String']
logo?: Scalars['String']
version?: Scalars['String']
universalIdentifier: Scalars['String']
packageJsonChecksum?: Scalars['String']
@@ -476,6 +483,7 @@ export interface Application {
objects: Object[]
applicationVariables: ApplicationVariable[]
applicationRegistration?: ApplicationRegistrationSummary
logo?: FileOutput
__typename: 'Application'
}
@@ -1611,7 +1619,7 @@ export interface CreateApplicationRegistration {
export interface PublicApplicationRegistration {
id: Scalars['UUID']
name: Scalars['String']
logoUrl?: Scalars['String']
logo?: FileOutput
websiteUrl?: Scalars['String']
oAuthScopes: Scalars['String'][]
__typename: 'PublicApplicationRegistration'
@@ -2034,6 +2042,7 @@ export interface MarketplaceAppDetail {
sourceType: ApplicationRegistrationSourceType
sourcePackage?: Scalars['String']
latestAvailableVersion?: Scalars['String']
logo?: FileOutput
isListed: Scalars['Boolean']
isFeatured: Scalars['Boolean']
manifest?: Scalars['JSON']
@@ -2923,9 +2932,9 @@ export interface ApplicationRegistrationGenqlSelection{
isListed?: boolean | number
isFeatured?: boolean | number
isPreInstalled?: boolean | number
logoUrl?: boolean | number
createdAt?: boolean | number
updatedAt?: boolean | number
logo?: FileOutputGenqlSelection
isConfigured?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
@@ -3096,11 +3105,20 @@ export interface RoleGenqlSelection{
__scalar?: boolean | number
}
export interface FileOutputGenqlSelection{
fileId?: boolean | number
label?: boolean | number
extension?: boolean | number
url?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface ApplicationRegistrationSummaryGenqlSelection{
id?: boolean | number
latestAvailableVersion?: boolean | number
sourceType?: boolean | number
logoUrl?: boolean | number
logo?: FileOutputGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
@@ -3365,7 +3383,6 @@ export interface ApplicationGenqlSelection{
id?: boolean | number
name?: boolean | number
description?: boolean | number
logo?: boolean | number
version?: boolean | number
universalIdentifier?: boolean | number
packageJsonChecksum?: boolean | number
@@ -3385,6 +3402,7 @@ export interface ApplicationGenqlSelection{
objects?: ObjectGenqlSelection
applicationVariables?: ApplicationVariableGenqlSelection
applicationRegistration?: ApplicationRegistrationSummaryGenqlSelection
logo?: FileOutputGenqlSelection
__typename?: boolean | number
__scalar?: boolean | number
}
@@ -4555,7 +4573,7 @@ export interface CreateApplicationRegistrationGenqlSelection{
export interface PublicApplicationRegistrationGenqlSelection{
id?: boolean | number
name?: boolean | number
logoUrl?: boolean | number
logo?: FileOutputGenqlSelection
websiteUrl?: boolean | number
oAuthScopes?: boolean | number
__typename?: boolean | number
@@ -5030,6 +5048,7 @@ export interface MarketplaceAppDetailGenqlSelection{
sourceType?: boolean | number
sourcePackage?: boolean | number
latestAvailableVersion?: boolean | number
logo?: FileOutputGenqlSelection
isListed?: boolean | number
isFeatured?: boolean | number
manifest?: boolean | number
@@ -6409,6 +6428,14 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const FileOutput_possibleTypes: string[] = ['FileOutput']
export const isFileOutput = (obj?: { __typename?: any } | null): obj is FileOutput => {
if (!obj?.__typename) throw new Error('__typename is missing in "isFileOutput"')
return FileOutput_possibleTypes.includes(obj.__typename)
}
const ApplicationRegistrationSummary_possibleTypes: string[] = ['ApplicationRegistrationSummary']
export const isApplicationRegistrationSummary = (obj?: { __typename?: any } | null): obj is ApplicationRegistrationSummary => {
if (!obj?.__typename) throw new Error('__typename is missing in "isApplicationRegistrationSummary"')
File diff suppressed because it is too large Load Diff
@@ -176,7 +176,7 @@ export type ApplicationRegistration = {
isListed: Scalars['Boolean'];
isPreInstalled: Scalars['Boolean'];
latestAvailableVersion?: Maybe<Scalars['String']>;
logoUrl?: Maybe<Scalars['String']>;
logo?: Maybe<FileOutput>;
name: Scalars['String'];
oAuthClientId: Scalars['String'];
oAuthRedirectUris: Array<Scalars['String']>;
@@ -293,6 +293,14 @@ export enum FeatureFlagKey {
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED'
}
export type FileOutput = {
__typename?: 'FileOutput';
extension?: Maybe<Scalars['String']>;
fileId?: Maybe<Scalars['UUID']>;
label?: Maybe<Scalars['String']>;
url: Scalars['String'];
};
export enum HealthIndicatorId {
app = 'app',
connectedAccount = 'connectedAccount',
@@ -933,7 +941,7 @@ export type FindAdminApplicationRegistrationVariablesQuery = { __typename?: 'Que
export type FindAllApplicationRegistrationsQueryVariables = Exact<{ [key: string]: never; }>;
export type FindAllApplicationRegistrationsQuery = { __typename?: 'Query', findAllApplicationRegistrations: 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, isFeatured: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string }> };
export type FindAllApplicationRegistrationsQuery = { __typename?: 'Query', findAllApplicationRegistrations: Array<{ __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string, logo?: { __typename?: 'FileOutput', fileId?: string | null, label?: string | null, extension?: string | null, url: string } | null }> };
export type CreateDatabaseConfigVariableMutationVariables = Exact<{
key: Scalars['String'];
@@ -1000,7 +1008,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, isFeatured: 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, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string, logo?: { __typename?: 'FileOutput', fileId?: string | null, label?: string | null, extension?: string | null, url: string } | null } };
export type GetAdminChatThreadMessagesQueryVariables = Exact<{
threadId: Scalars['UUID'];
@@ -1136,10 +1144,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, isFeatured: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string };
export type ApplicationRegistrationFragmentFragment = { __typename?: 'ApplicationRegistration', id: string, universalIdentifier: string, name: string, oAuthClientId: string, oAuthRedirectUris: Array<string>, oAuthScopes: Array<string>, sourceType: ApplicationRegistrationSourceType, sourcePackage?: string | null, latestAvailableVersion?: string | null, isListed: boolean, isFeatured: boolean, isPreInstalled: boolean, isConfigured: boolean, ownerWorkspaceId?: string | null, createdAt: string, updatedAt: string, logo?: { __typename?: 'FileOutput', fileId?: string | null, label?: string | null, extension?: string | null, url: string } | null };
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":"isFeatured"}},{"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":"logo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fileId"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"extension"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}},{"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":"isFeatured"}},{"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>;
@@ -1155,7 +1163,7 @@ export const GetAiProvidersDocument = {"kind":"Document","definitions":[{"kind":
export const GetModelsDevProvidersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetModelsDevProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getModelsDevProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"modelCount"}},{"kind":"Field","name":{"kind":"Name","value":"npm"}}]}}]}}]} as unknown as DocumentNode<GetModelsDevProvidersQuery, GetModelsDevProvidersQueryVariables>;
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 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":"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"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAllApplicationRegistrations"},"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":"isFeatured"}},{"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"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAllApplicationRegistrations"},"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":"logo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fileId"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"extension"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}},{"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":"isFeatured"}},{"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>;
@@ -1164,7 +1172,7 @@ export const GetDatabaseConfigVariableDocument = {"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":"isFeatured"}},{"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":"logo"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"fileId"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"extension"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}},{"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":"isFeatured"}},{"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 GetUpgradeStatusDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetUpgradeStatus"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceIds"}},"type":{"kind":"NonNullType","type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getUpgradeStatus"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceIds"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceIds"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"inferredVersion"}},{"kind":"Field","name":{"kind":"Name","value":"health"}},{"kind":"Field","name":{"kind":"Name","value":"latestCommand"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"executedByVersion"}},{"kind":"Field","name":{"kind":"Name","value":"errorMessage"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]}}]} as unknown as DocumentNode<GetUpgradeStatusQuery, GetUpgradeStatusQueryVariables>;
File diff suppressed because one or more lines are too long
@@ -11,7 +11,12 @@ export const APPLICATION_FRAGMENT = gql`
id
name
description
logo
logo {
fileId
label
extension
url
}
version
universalIdentifier
applicationRegistrationId
@@ -19,7 +24,12 @@ export const APPLICATION_FRAGMENT = gql`
id
latestAvailableVersion
sourceType
logoUrl
logo {
fileId
label
extension
url
}
}
canBeUninstalled
defaultRoleId
@@ -6,7 +6,12 @@ export const FIND_MANY_APPLICATIONS = gql`
id
name
description
logo
logo {
fileId
label
extension
url
}
version
universalIdentifier
applicationRegistrationId
@@ -8,7 +8,6 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { buildApplicationLogoUrl } from '@/applications/utils/buildApplicationLogoUrl';
import CustomLogo from '~/pages/settings/applications/assets/custom-illustrations/custom-logo.webp';
import StandardLogo from '~/pages/settings/applications/assets/standard-illustrations/standard-logo.webp';
@@ -67,11 +66,7 @@ export const useApplicationChipData = ({
? new URL(StandardLogo, window.location.href).toString()
: isCustom
? new URL(CustomLogo, window.location.href).toString()
: buildApplicationLogoUrl({
applicationId: application.id,
logo: application.logo,
workspaceId: currentWorkspace?.id,
});
: (application.logo?.url ?? undefined);
return {
applicationChipData: {
@@ -1,26 +0,0 @@
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { isDefined } from 'twenty-shared/utils';
export const buildApplicationLogoUrl = ({
workspaceId,
applicationId,
logo,
}: {
workspaceId?: string | null;
applicationId?: string | null;
logo?: string | null;
}): string | undefined => {
if (
!isDefined(logo) ||
logo.startsWith('http://') ||
logo.startsWith('https://')
) {
return logo ?? undefined;
}
if (!isDefined(workspaceId) || !isDefined(applicationId)) {
return undefined;
}
return `${REACT_APP_SERVER_BASE_URL}/public-assets/${workspaceId}/${applicationId}/${logo}`;
};
@@ -4,7 +4,12 @@ export const FIND_APPLICATION_REGISTRATION_BY_CLIENT_ID = gql`
query FindApplicationRegistrationByClientId($clientId: String!) {
findApplicationRegistrationByClientId(clientId: $clientId) {
id
logoUrl
logo {
fileId
label
extension
url
}
name
oAuthScopes
websiteUrl
@@ -8,6 +8,12 @@ export const MARKETPLACE_APP_DETAIL_FRAGMENT = gql`
sourceType
sourcePackage
latestAvailableVersion
logo {
fileId
label
extension
url
}
isListed
isFeatured
manifest
@@ -179,7 +179,7 @@ const SettingsAdminAppsTableRow = ({
<AppChip
size="md"
fallbackApplicationData={{
logo: registration.logoUrl,
logo: registration.logo?.url ?? null,
name: registration.name,
}}
/>
@@ -0,0 +1,21 @@
import { gql } from '@apollo/client';
export const FIND_ADMIN_APPLICATION_REGISTRATION_VARIABLES = gql`
query FindAdminApplicationRegistrationVariables(
$applicationRegistrationId: String!
) {
findAdminApplicationRegistrationVariables(
applicationRegistrationId: $applicationRegistrationId
) {
id
key
value
description
isSecret
isRequired
isFilled
createdAt
updatedAt
}
}
`;
@@ -5,7 +5,12 @@ export const APPLICATION_REGISTRATION_FRAGMENT = gql`
id
universalIdentifier
name
logoUrl
logo {
fileId
label
extension
url
}
oAuthClientId
oAuthRedirectUris
oAuthScopes
@@ -28,7 +28,14 @@ export const useObjectAndFieldRows = ({
}) => {
const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const installedApplications = currentWorkspace?.installedApplications;
const installedApplications = currentWorkspace?.installedApplications?.map(
(app) => ({
id: app.id,
name: app.name,
universalIdentifier: app.universalIdentifier,
logo: app.logo?.url ?? null,
}),
);
return useMemo(() => {
if (isDefined(installedApplication)) {
@@ -44,7 +44,7 @@ export const getInstalledApplicationObjectAndFieldRows = ({
}),
application: {
id: installedApplication.id,
logo: installedApplication.logo,
logo: installedApplication.logo?.url ?? null,
name: installedApplication.name,
universalIdentifier: installedApplication.universalIdentifier,
},
@@ -70,7 +70,12 @@ export const USER_QUERY_FRAGMENT = gql`
id
name
universalIdentifier
logo
logo {
fileId
label
extension
url
}
}
isCustomDomainEnabled
workspaceUrls {
@@ -285,7 +285,7 @@ export const Authorize = () => {
}
const appName = applicationRegistration.name;
const appLogoUrl = applicationRegistration.logoUrl;
const appLogoUrl = applicationRegistration.logo?.url ?? null;
const requestedScopes: string[] = applicationRegistration.oAuthScopes ?? [];
const showLogoImage = isNonEmptyString(appLogoUrl) && !hasLogoError;
@@ -20,7 +20,12 @@ const buildHandlers = (
application: {
id: string;
name: string;
logoUrl: string | null;
logo: {
fileId: string | null;
label: string | null;
extension: string | null;
url: string;
} | null;
oAuthScopes: string[];
websiteUrl: string | null;
} | null,
@@ -90,7 +95,7 @@ export const Default: Story = {
handlers: buildHandlers({
id: 'application-id-default',
name: 'ChatGPT',
logoUrl: null,
logo: null,
oAuthScopes: ['api', 'profile'],
websiteUrl: null,
}),
@@ -116,7 +121,7 @@ export const WithApiScopeOnly: Story = {
handlers: buildHandlers({
id: 'application-id-api-only',
name: 'Internal Tool',
logoUrl: null,
logo: null,
oAuthScopes: ['api'],
websiteUrl: null,
}),
@@ -144,7 +149,7 @@ export const WithLongAppName: Story = {
handlers: buildHandlers({
id: 'application-id-long-name',
name: 'Custom Workspace Automation Suite',
logoUrl: null,
logo: null,
oAuthScopes: ['api', 'profile'],
websiteUrl: null,
}),
@@ -8,12 +8,15 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBa
import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient';
import { APPLICATION_REGISTRATION_ADMIN_PATH } from '@/settings/admin-panel/apps/constants/ApplicationRegistrationAdminPath';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { styled } from '@linaria/react';
import {
Avatar,
IconInfoCircle,
IconKey,
IconSettings,
IconWorld,
} from 'twenty-ui/display';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { SettingsApplicationRegistrationConfigTab } from '~/pages/settings/applications/tabs/SettingsApplicationRegistrationConfigTab';
import { SettingsApplicationRegistrationOAuthTab } from '~/pages/settings/applications/tabs/SettingsApplicationRegistrationOAuthTab';
import { SettingsApplicationRegistrationDistributionTab } from '~/pages/settings/applications/tabs/SettingsApplicationRegistrationDistributionTab';
@@ -22,6 +25,12 @@ import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
const StyledTitleContainer = styled.div`
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[2]};
`;
const REGISTRATION_DETAIL_TAB_LIST_ID =
'admin-application-registration-detail-tab-list';
@@ -94,7 +103,18 @@ export const SettingsAdminApplicationRegistrationDetail = () => {
return (
<SubMenuTopBarContainer
title={registration.name}
title={
<StyledTitleContainer>
<Avatar
type="app"
size="md"
avatarUrl={registration.logo?.url ?? undefined}
placeholder={registration.name}
placeholderColorSeed={registration.name}
/>
{registration.name}
</StyledTitleContainer>
}
links={[
{
children: t`Other`,
@@ -54,7 +54,12 @@ const FIND_MANY_APPLICATIONS_FOR_TOOL_TABLE = gql`
id
name
universalIdentifier
logo
logo {
fileId
label
extension
url
}
}
}
`;
@@ -92,7 +97,12 @@ export const SettingsToolsTable = () => {
id: string;
name: string;
universalIdentifier: string;
logo?: string | null;
logo?: {
fileId: string | null;
label: string | null;
extension: string | null;
url: string;
} | null;
}>;
}>(FIND_MANY_APPLICATIONS_FOR_TOOL_TABLE);
const { data: marketplaceAppsData } = useQuery<{
@@ -293,7 +293,7 @@ export const SettingsApplicationDetails = () => {
applicationInfo={{
id: application.id,
name: displayName,
logo: application.logo,
logo: application.logo?.url ?? null,
universalIdentifier: application.universalIdentifier,
}}
/>
@@ -8,12 +8,15 @@ import { useLingui } from '@lingui/react/macro';
import { Tag } from 'twenty-ui/components';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { styled } from '@linaria/react';
import {
Avatar,
IconInfoCircle,
IconKey,
IconSettings,
IconWorld,
} from 'twenty-ui/display';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { SettingsApplicationRegistrationConfigTab } from '~/pages/settings/applications/tabs/SettingsApplicationRegistrationConfigTab';
import { SettingsApplicationRegistrationOAuthTab } from '~/pages/settings/applications/tabs/SettingsApplicationRegistrationOAuthTab';
import { SettingsApplicationRegistrationDistributionTab } from '~/pages/settings/applications/tabs/SettingsApplicationRegistrationDistributionTab';
@@ -21,6 +24,12 @@ import { SettingsApplicationRegistrationGeneralTab } from '~/pages/settings/appl
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
const StyledTitleContainer = styled.div`
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[2]};
`;
const REGISTRATION_DETAIL_TAB_LIST_ID =
'application-registration-detail-tab-list';
@@ -86,7 +95,18 @@ export const SettingsApplicationRegistrationDetails = () => {
return (
<SubMenuTopBarContainer
title={registration.name}
title={
<StyledTitleContainer>
<Avatar
type="app"
size="md"
avatarUrl={registration.logo?.url ?? undefined}
placeholder={registration.name}
placeholderColorSeed={registration.name}
/>
{registration.name}
</StyledTitleContainer>
}
tag={<Tag text={t`Owner`} color={'gray'} />}
links={[
{
@@ -245,7 +245,7 @@ export const SettingsAvailableApplicationDetails = () => {
manifestContent={manifest}
applicationInfo={{
name: displayName,
logo: app?.logoUrl,
logo: detail?.logo?.url,
universalIdentifier: detail.universalIdentifier,
}}
/>
@@ -286,6 +286,10 @@ export const SettingsAvailableApplicationDetails = () => {
displayName={displayName}
description={description}
applicationId={application?.id}
fallbackApplicationData={{
logo: detail?.logo?.url,
name: displayName,
}}
isUnlisted={isUnlisted}
/>
}
@@ -9,10 +9,11 @@ import { useApplicationChipData } from '@/applications/hooks/useApplicationChipD
type SettingsApplicationDetailTitleProps = {
displayName: string;
description?: string;
logoUrl?: string;
applicationId?: string;
applicationName?: string;
universalIdentifier?: string;
fallbackApplicationData?: {
logo?: string | null;
name?: string | null;
};
isUnlisted?: boolean;
};
@@ -73,12 +74,14 @@ export const SettingsApplicationDetailTitle = ({
displayName,
description,
applicationId,
fallbackApplicationData,
isUnlisted = false,
}: SettingsApplicationDetailTitleProps) => {
const descriptionSummary = getApplicationDescriptionSummary(description);
const { applicationChipData } = useApplicationChipData({
applicationId,
fallbackApplicationData,
});
return (
@@ -177,8 +177,8 @@ export const SettingsApplicationsDeveloperTab = () => {
<TableRow
gridTemplateColumns={APPLICATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
>
<TableHeader> {t`Name`}</TableHeader>
<TableHeader>{''}</TableHeader>
<TableHeader>{t`Name`}</TableHeader>
<TableHeader>{t`Type`}</TableHeader>
<TableHeader>{''}</TableHeader>
<TableHeader />
</TableRow>
@@ -187,7 +187,11 @@ export const SettingsApplicationsDeveloperTab = () => {
return (
<SettingsApplicationTableRow
key={registration.id}
application={registration}
application={{
...registration,
logo: registration.logo?.url ?? null,
}}
sourceType={registration.sourceType}
action={
<IconChevronRight
size={theme.icon.size.md}
@@ -2,6 +2,7 @@ import {
DEFAULT_API_URL_NAME,
DEFAULT_APP_ACCESS_TOKEN_NAME,
} from 'twenty-shared/application';
import { buildPublicAssetUrl } from 'twenty-shared/utils';
const decodeTokenPayload = (
token: string,
@@ -38,5 +39,16 @@ export const getPublicAssetUrl = (path: string): string => {
.map(encodeURIComponent)
.join('/');
return `${apiUrl}/public-assets/${workspaceId}/${applicationId}/${encodedPath}`;
const url = buildPublicAssetUrl({
path: encodedPath,
serverUrl: apiUrl,
workspaceId,
applicationId,
});
if (!url) {
throw new Error('Failed to build public asset URL');
}
return url;
};
@@ -29,9 +29,7 @@ export class AddWorkspaceIdToTotoFastInstanceCommand
implements FastInstanceCommand
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."toto" ADD "workspaceId" uuid`,
);
await queryRunner.query(`ALTER TABLE "core"."toto" ADD "workspaceId" uuid`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
@@ -81,12 +79,9 @@ Workspace commands run per-workspace logic across all active or suspended worksp
@RegisteredWorkspaceCommand('1.22.0', 1780000002000)
@Command({
name: 'upgrade:1-22:backfill-standard-skills',
description:
'Backfill standard skills for existing workspaces',
description: 'Backfill standard skills for existing workspaces',
})
export class BackfillStandardSkillsCommand
extends ActiveOrSuspendedWorkspaceCommandRunner
{
export class BackfillStandardSkillsCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
constructor(
protected readonly workspaceIteratorService: WorkspaceIteratorService,
// inject any services you need
@@ -3,12 +3,14 @@
This Bash script helps generate self-signed SSL certificates for local development. It uses OpenSSL to create a root certificate authority, a domain certificate, and configures them for local usage.
## Features
- Generates a private key and root certificate.
- Creates a signed certificate for a specified domain.
- Adds the root certificate to the macOS keychain for trusted usage (macOS only).
- Customizable with default values for easier use.
## Requirements
- OpenSSL
## Usage
@@ -30,24 +32,27 @@ To generate certificates using the default values:
#### Examples:
1. **Using Default Values**:
```sh
./script.sh
```
```sh
./script.sh
```
2. **Custom Domain Name**:
```sh
./script.sh example.com
```
```sh
./script.sh example.com
```
3. **Custom Domain Name and Root Certificate Name**:
```sh
./script.sh example.com customRootCertificate
```
```sh
./script.sh example.com customRootCertificate
```
4. **Custom Domain Name, Root Certificate Name, and Validity Days**:
```sh
./script.sh example.com customRootCertificate 398
```
```sh
./script.sh example.com customRootCertificate 398
```
## Script Details
@@ -71,4 +76,4 @@ The generated files are stored in `~/certs/{domain}`:
## Notes
- If running on non-macOS systems, you'll need to manually add the root certificate to your trusted certificate store.
- Ensure that OpenSSL is installed and available in your PATH.
- Ensure that OpenSSL is installed and available in your PATH.
@@ -0,0 +1,33 @@
import { QueryRunner } from 'typeorm';
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface';
@RegisteredInstanceCommand('2.8.0', 1779387505162)
export class AddLogoToApplicationRegistrationFastInstanceCommand
implements FastInstanceCommand
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
'ALTER TABLE "core"."applicationRegistration" ADD "logoFileId" uuid',
);
await queryRunner.query(
'ALTER TABLE "core"."applicationRegistration" ADD CONSTRAINT "UQ_796819fb23559c233e6ebd49f34" UNIQUE ("logoFileId")',
);
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 "FK_796819fb23559c233e6ebd49f34"',
);
await queryRunner.query(
'ALTER TABLE "core"."applicationRegistration" DROP CONSTRAINT "UQ_796819fb23559c233e6ebd49f34"',
);
await queryRunner.query(
'ALTER TABLE "core"."applicationRegistration" DROP COLUMN "logoFileId"',
);
}
}
@@ -53,6 +53,7 @@ import { DropPostgresCredentialsTableFastInstanceCommand } from 'src/database/co
import { AddRelationTargetFieldMetadataIdToViewFilterFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1798000005000-add-relation-target-field-metadata-id-to-view-filter';
import { AddChannelSyncStageIndexesFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-6/2-6-instance-command-fast-1798000010000-add-channel-sync-stage-indexes';
import { FinalizeRolePermissionFlagCutoverFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-7/2-7-instance-command-fast-1779600000000-finalize-role-permission-flag-cutover';
import { AddLogoToApplicationRegistrationFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-8/2-8-instance-command-fast-1779387505162-add-logo-to-application-registration';
export const INSTANCE_COMMANDS = [
AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand,
@@ -108,4 +109,5 @@ export const INSTANCE_COMMANDS = [
AddRelationTargetFieldMetadataIdToViewFilterFastInstanceCommand,
AddChannelSyncStageIndexesFastInstanceCommand,
FinalizeRolePermissionFlagCutoverFastInstanceCommand,
AddLogoToApplicationRegistrationFastInstanceCommand,
];
@@ -2,10 +2,25 @@ import { Context, Parent, ResolveField } from '@nestjs/graphql';
import { AdminResolver } from 'src/engine/api/graphql/graphql-config/decorators/admin-resolver.decorator';
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
import { ApplicationRegistrationLogoService } from 'src/engine/core-modules/application/application-registration/application-registration-logo.service';
import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
import { FileOutputDTO } from 'src/engine/core-modules/file/dtos/file-output.dto';
@AdminResolver(() => ApplicationRegistrationEntity)
export class AdminPanelApplicationRegistrationResolver {
constructor(
private readonly applicationRegistrationLogoService: ApplicationRegistrationLogoService,
) {}
@ResolveField(() => FileOutputDTO, { nullable: true })
async logo(
@Parent() registration: ApplicationRegistrationEntity,
): Promise<FileOutputDTO | null> {
return this.applicationRegistrationLogoService.resolveLogoFile(
registration,
);
}
@ResolveField(() => Boolean)
async isConfigured(
@Parent() registration: ApplicationRegistrationEntity,
@@ -22,7 +22,6 @@ import { UploadApplicationFileInput } from 'src/engine/core-modules/application/
import { WorkspaceMigrationDTO } from 'src/engine/core-modules/application/application-development/dtos/workspace-migration.dto';
import { ApplicationExceptionFilter } from 'src/engine/core-modules/application/application-exception-filter';
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 { ApplicationTokenPairDTO } from 'src/engine/core-modules/application/application-oauth/dtos/application-token-pair.dto';
import { ApplicationRegistrationVariableService } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.service';
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
@@ -38,7 +37,6 @@ import { FileDTO } from 'src/engine/core-modules/file/dtos/file.dto';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
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 { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
@@ -68,7 +66,6 @@ export class ApplicationDevelopmentResolver {
private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService,
private readonly fileStorageService: FileStorageService,
private readonly sdkClientGenerationService: SdkClientGenerationService,
private readonly twentyConfigService: TwentyConfigService,
private readonly throttlerService: ThrottlerService,
) {}
@@ -172,12 +169,7 @@ export class ApplicationDevelopmentResolver {
});
}
await this.syncRegistrationMetadata(
applicationRegistrationId,
manifest,
workspaceId,
application.id,
);
await this.syncRegistrationMetadata(applicationRegistrationId, manifest);
return {
applicationUniversalIdentifier:
@@ -279,20 +271,11 @@ export class ApplicationDevelopmentResolver {
private async syncRegistrationMetadata(
applicationRegistrationId: string,
manifest: ApplicationInput['manifest'],
workspaceId: string,
applicationId: string,
): Promise<void> {
const serverUrl = this.twentyConfigService.get('SERVER_URL');
const manifestWithResolvedUrls = resolveManifestAssetUrls(
manifest,
(filePath) =>
`${serverUrl}/public-assets/${workspaceId}/${applicationId}/${filePath}`,
);
await this.applicationRegistrationService.updateFromManifest(
applicationRegistrationId,
manifestWithResolvedUrls,
manifest,
ApplicationRegistrationSourceType.LOCAL,
);
if (manifest.application.serverVariables) {
@@ -9,6 +9,7 @@ import { ApplicationManifestModule } from 'src/engine/core-modules/application/a
import { ApplicationPackageModule } from 'src/engine/core-modules/application/application-package/application-package.module';
import { ApplicationInstallResolver } from 'src/engine/core-modules/application/application-install/application-install.resolver';
import { ApplicationInstallService } from 'src/engine/core-modules/application/application-install/application-install.service';
import { ApplicationLogoResolver } from 'src/engine/core-modules/application/application-install/application-logo.resolver';
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
import { LogicFunctionModule } from 'src/engine/core-modules/logic-function/logic-function.module';
import { SdkClientModule } from 'src/engine/core-modules/sdk-client/sdk-client.module';
@@ -29,7 +30,11 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
FileStorageModule,
WorkspaceCacheModule,
],
providers: [ApplicationInstallResolver, ApplicationInstallService],
providers: [
ApplicationInstallResolver,
ApplicationInstallService,
ApplicationLogoResolver,
],
exports: [ApplicationInstallService],
})
export class ApplicationInstallModule {}
@@ -0,0 +1,33 @@
import { Parent, ResolveField } from '@nestjs/graphql';
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
import { ApplicationDTO } from 'src/engine/core-modules/application/dtos/application.dto';
import { resolveApplicationLogoUrl } from 'src/engine/core-modules/application/utils/resolve-application-logo-url.util';
import { FileOutputDTO } from 'src/engine/core-modules/file/dtos/file-output.dto';
import { buildFileOutputFromUrl } from 'src/engine/core-modules/file/utils/build-file-output-from-url.util';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@MetadataResolver(() => ApplicationDTO)
export class ApplicationLogoResolver {
constructor(private readonly twentyConfigService: TwentyConfigService) {}
@ResolveField(() => FileOutputDTO, { nullable: true })
logo(
@Parent() application: ApplicationDTO & { workspaceId?: string },
): FileOutputDTO | null {
if (!application.workspaceId) {
return buildFileOutputFromUrl(application.logo ?? null);
}
const serverUrl = this.twentyConfigService.get('SERVER_URL');
return buildFileOutputFromUrl(
resolveApplicationLogoUrl({
logo: application.logo,
serverUrl,
workspaceId: application.workspaceId,
applicationId: application.id,
}),
);
}
}
@@ -5,6 +5,7 @@ import { GraphQLJSON } from 'graphql-type-json';
import { type Manifest } from 'twenty-shared/application';
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
import { FileOutputDTO } from 'src/engine/core-modules/file/dtos/file-output.dto';
@ObjectType('MarketplaceAppDetail')
export class MarketplaceAppDetailDTO {
@@ -36,6 +37,10 @@ export class MarketplaceAppDetailDTO {
@Field({ nullable: true })
latestAvailableVersion?: string;
@IsOptional()
@Field(() => FileOutputDTO, { nullable: true })
logo?: FileOutputDTO | null;
@IsBoolean()
@Field(() => Boolean)
isListed: boolean;
@@ -3,9 +3,6 @@ import { Injectable, Logger } from '@nestjs/common';
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 { 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 { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@Injectable()
export class MarketplaceCatalogSyncService {
@@ -14,7 +11,6 @@ export class MarketplaceCatalogSyncService {
constructor(
private readonly applicationRegistrationService: ApplicationRegistrationService,
private readonly marketplaceService: MarketplaceService,
private readonly twentyConfigService: TwentyConfigService,
) {}
async syncCatalog(): Promise<void> {
@@ -59,26 +55,13 @@ export class MarketplaceCatalogSyncService {
}
: fetchedManifest;
const cdnBaseUrl = this.twentyConfigService.get('APP_REGISTRY_CDN_URL');
const manifestWithResolvedUrls = resolveManifestAssetUrls(
manifest,
(filePath) =>
buildRegistryCdnUrl({
cdnBaseUrl,
packageName: pkg.name,
version: pkg.version,
filePath,
}),
);
await this.applicationRegistrationService.upsertFromCatalog({
universalIdentifier,
name: manifest.application.displayName ?? pkg.name,
sourceType: ApplicationRegistrationSourceType.NPM,
sourcePackage: pkg.name,
latestAvailableVersion: pkg.version ?? null,
manifest: manifestWithResolvedUrls,
manifest,
});
} catch (error) {
this.logger.error(
@@ -12,9 +12,12 @@ import { ApplicationRegistrationService } from 'src/engine/core-modules/applicat
import { MarketplaceCatalogSyncCronJob } from 'src/engine/core-modules/application/application-marketplace/crons/marketplace-catalog-sync.cron.job';
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 { resolveApplicationRegistrationLogoUrl } from 'src/engine/core-modules/application/utils/resolve-application-registration-logo-url.util';
import { buildFileOutputFromUrl } from 'src/engine/core-modules/file/utils/build-file-output-from-url.util';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@Injectable()
export class MarketplaceQueryService {
@@ -24,6 +27,7 @@ export class MarketplaceQueryService {
constructor(
private readonly applicationRegistrationService: ApplicationRegistrationService,
private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService,
private readonly twentyConfigService: TwentyConfigService,
@InjectMessageQueue(MessageQueue.cronQueue)
private readonly messageQueueService: MessageQueueService,
) {}
@@ -96,7 +100,14 @@ export class MarketplaceQueryService {
description: app?.description ?? '',
author: `${app?.author ?? 'Unknown'}`,
category: app?.category ?? '',
logo: app?.logoUrl ?? undefined,
logo:
resolveApplicationRegistrationLogoUrl({
logo: app?.logoUrl ?? null,
sourceType: registration.sourceType,
sourcePackage: registration.sourcePackage,
latestAvailableVersion: registration.latestAvailableVersion,
cdnBaseUrl: this.twentyConfigService.get('APP_REGISTRY_CDN_URL'),
}) ?? undefined,
sourcePackage: registration.sourcePackage ?? undefined,
isFeatured: registration.isFeatured,
};
@@ -112,6 +123,15 @@ export class MarketplaceQueryService {
sourceType: registration.sourceType,
sourcePackage: registration.sourcePackage ?? undefined,
latestAvailableVersion: registration.latestAvailableVersion ?? undefined,
logo: buildFileOutputFromUrl(
resolveApplicationRegistrationLogoUrl({
logo: registration.manifest?.application?.logoUrl ?? null,
sourceType: registration.sourceType,
sourcePackage: registration.sourcePackage,
latestAvailableVersion: registration.latestAvailableVersion,
cdnBaseUrl: this.twentyConfigService.get('APP_REGISTRY_CDN_URL'),
}),
),
isListed: registration.isListed,
isFeatured: registration.isFeatured,
manifest: registration.manifest ?? undefined,
@@ -0,0 +1,74 @@
import { Injectable } from '@nestjs/common';
import { FileFolder } from 'twenty-shared/types';
import { 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 { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { resolveApplicationLogoUrl } from 'src/engine/core-modules/application/utils/resolve-application-logo-url.util';
import { resolveApplicationRegistrationLogoUrl } from 'src/engine/core-modules/application/utils/resolve-application-registration-logo-url.util';
import { type FileOutputDTO } from 'src/engine/core-modules/file/dtos/file-output.dto';
import { FileUrlService } from 'src/engine/core-modules/file/file-url/file-url.service';
import { buildFileOutputFromUrl } from 'src/engine/core-modules/file/utils/build-file-output-from-url.util';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@Injectable()
export class ApplicationRegistrationLogoService {
constructor(
private readonly twentyConfigService: TwentyConfigService,
private readonly applicationService: ApplicationService,
private readonly fileUrlService: FileUrlService,
) {}
async resolveLogoFile(
registration: ApplicationRegistrationEntity,
): Promise<FileOutputDTO | null> {
if (
registration.sourceType === ApplicationRegistrationSourceType.TARBALL &&
registration.logoFileId
) {
const url = await this.fileUrlService.signFileByIdUrl({
fileId: registration.logoFileId,
workspaceId: registration.ownerWorkspaceId ?? '',
fileFolder: FileFolder.AppTarball,
});
return buildFileOutputFromUrl(url, registration.logoFileId);
}
const manifestLogo = registration.manifest?.application?.logoUrl ?? null;
if (
registration.sourceType === ApplicationRegistrationSourceType.LOCAL &&
manifestLogo &&
registration.ownerWorkspaceId
) {
const application =
await this.applicationService.findByUniversalIdentifier({
universalIdentifier: registration.universalIdentifier,
workspaceId: registration.ownerWorkspaceId,
});
if (application) {
return buildFileOutputFromUrl(
resolveApplicationLogoUrl({
logo: manifestLogo,
serverUrl: this.twentyConfigService.get('SERVER_URL'),
workspaceId: registration.ownerWorkspaceId,
applicationId: application.id,
}),
);
}
}
return buildFileOutputFromUrl(
resolveApplicationRegistrationLogoUrl({
logo: manifestLogo,
sourceType: registration.sourceType,
sourcePackage: registration.sourcePackage,
latestAvailableVersion: registration.latestAvailableVersion,
cdnBaseUrl: this.twentyConfigService.get('APP_REGISTRY_CDN_URL'),
}),
);
}
}
@@ -0,0 +1,23 @@
import { Parent, ResolveField } from '@nestjs/graphql';
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
import { ApplicationRegistrationLogoService } from 'src/engine/core-modules/application/application-registration/application-registration-logo.service';
import { ApplicationRegistrationSummaryDTO } from 'src/engine/core-modules/application/application-registration/dtos/application-registration-summary.dto';
import { FileOutputDTO } from 'src/engine/core-modules/file/dtos/file-output.dto';
@MetadataResolver(() => ApplicationRegistrationSummaryDTO)
export class ApplicationRegistrationSummaryResolver {
constructor(
private readonly applicationRegistrationLogoService: ApplicationRegistrationLogoService,
) {}
@ResolveField(() => FileOutputDTO, { nullable: true })
async logo(
@Parent() registration: ApplicationRegistrationEntity,
): Promise<FileOutputDTO | null> {
return this.applicationRegistrationLogoService.resolveLogoFile(
registration,
);
}
}
@@ -21,6 +21,7 @@ import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/
import { type Manifest } from 'twenty-shared/application';
import { ApplicationRegistrationVariableEntity } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.entity';
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator';
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
import { UserEntity } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
@@ -133,10 +134,16 @@ export class ApplicationRegistrationEntity {
@Column({ type: 'jsonb', nullable: true })
manifest: Manifest | null;
@Field(() => String, { nullable: true })
get logoUrl(): string | null {
return this.manifest?.application?.logoUrl ?? null;
}
@Column({ nullable: true, type: 'uuid' })
@WasIntroducedInUpgrade({
upgradeCommandName:
'2.8.0_AddLogoToApplicationRegistrationFastInstanceCommand_1779387505162',
})
logoFileId: string | null;
@OneToOne(() => FileEntity, { onDelete: 'SET NULL', nullable: true })
@JoinColumn({ name: 'logoFileId' })
logoFile: Relation<FileEntity> | null;
@OneToMany(
() => ApplicationRegistrationVariableEntity,
@@ -2,8 +2,10 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
import { ApplicationRegistrationLogoService } from 'src/engine/core-modules/application/application-registration/application-registration-logo.service';
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 { ApplicationPackageModule } from 'src/engine/core-modules/application/application-package/application-package.module';
@@ -36,11 +38,14 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
],
providers: [
ApplicationRegistrationService,
ApplicationRegistrationLogoService,
ApplicationRegistrationResolver,
ApplicationRegistrationSummaryResolver,
ApplicationTarballService,
],
exports: [
ApplicationRegistrationService,
ApplicationRegistrationLogoService,
ApplicationRegistrationVariableModule,
],
})
@@ -24,6 +24,7 @@ import { CreateApplicationRegistrationVariableInput } from 'src/engine/core-modu
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 { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
import { ApplicationRegistrationLogoService } from 'src/engine/core-modules/application/application-registration/application-registration-logo.service';
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';
import { ApplicationRegistrationStatsDTO } from 'src/engine/core-modules/application/application-registration/dtos/application-registration-stats.dto';
@@ -35,6 +36,7 @@ import { TransferApplicationRegistrationOwnershipInput } from 'src/engine/core-m
import { UpdateApplicationRegistrationInput } from 'src/engine/core-modules/application/application-registration/dtos/update-application-registration.input';
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
import { FileOutputDTO } from 'src/engine/core-modules/file/dtos/file-output.dto';
import { FileUrlService } from 'src/engine/core-modules/file/file-url/file-url.service';
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
@@ -64,6 +66,7 @@ import {
export class ApplicationRegistrationResolver {
constructor(
private readonly applicationRegistrationService: ApplicationRegistrationService,
private readonly applicationRegistrationLogoService: ApplicationRegistrationLogoService,
private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService,
private readonly applicationTarballService: ApplicationTarballService,
private readonly fileUrlService: FileUrlService,
@@ -330,6 +333,15 @@ export class ApplicationRegistrationResolver {
});
}
@ResolveField(() => FileOutputDTO, { nullable: true })
async logo(
@Parent() registration: ApplicationRegistrationEntity,
): Promise<FileOutputDTO | null> {
return this.applicationRegistrationLogoService.resolveLogoFile(
registration,
);
}
@ResolveField(() => Boolean)
async isConfigured(
@Parent() registration: ApplicationRegistrationEntity,
@@ -16,6 +16,9 @@ import {
ApplicationRegistrationException,
ApplicationRegistrationExceptionCode,
} from 'src/engine/core-modules/application/application-registration/application-registration.exception';
import { resolveApplicationRegistrationLogoUrl } from 'src/engine/core-modules/application/utils/resolve-application-registration-logo-url.util';
import { buildFileOutputFromUrl } from 'src/engine/core-modules/file/utils/build-file-output-from-url.util';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { type ApplicationRegistrationStatsDTO } from 'src/engine/core-modules/application/application-registration/dtos/application-registration-stats.dto';
import { type CreateApplicationRegistrationInput } from 'src/engine/core-modules/application/application-registration/dtos/create-application-registration.input';
import { type PublicApplicationRegistrationDTO } from 'src/engine/core-modules/application/application-registration/dtos/public-application-registration.dto';
@@ -39,6 +42,7 @@ export class ApplicationRegistrationService {
@InjectRepository(WorkspaceEntity)
private readonly workspaceRepository: Repository<WorkspaceEntity>,
private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService,
private readonly twentyConfigService: TwentyConfigService,
) {}
async findMany(
@@ -104,7 +108,6 @@ export class ApplicationRegistrationService {
): Promise<PublicApplicationRegistrationDTO | null> {
const registration = await this.applicationRegistrationRepository.findOne({
where: { oAuthClientId: clientId },
select: ['id', 'name', 'manifest', 'oAuthScopes'],
});
if (!registration) {
@@ -114,7 +117,15 @@ export class ApplicationRegistrationService {
return {
id: registration.id,
name: registration.name,
logoUrl: registration.manifest?.application?.logoUrl ?? null,
logo: buildFileOutputFromUrl(
resolveApplicationRegistrationLogoUrl({
logo: registration.manifest?.application?.logoUrl ?? null,
sourceType: registration.sourceType,
sourcePackage: registration.sourcePackage,
latestAvailableVersion: registration.latestAvailableVersion,
cdnBaseUrl: this.twentyConfigService.get('APP_REGISTRY_CDN_URL'),
}),
),
websiteUrl: registration.manifest?.application?.websiteUrl ?? null,
oAuthScopes: registration.oAuthScopes,
};
@@ -214,6 +225,7 @@ export class ApplicationRegistrationService {
async updateFromManifest(
applicationRegistrationId: string,
manifest: Manifest,
sourceType?: ApplicationRegistrationSourceType,
): Promise<void> {
const existing = await this.applicationRegistrationRepository.findOneOrFail(
{ where: { id: applicationRegistrationId } },
@@ -223,6 +235,7 @@ export class ApplicationRegistrationService {
...existing,
name: manifest.application.displayName,
manifest,
...(sourceType !== undefined && { sourceType }),
});
}
@@ -3,7 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { promises as fs } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { join, resolve, sep } from 'path';
import semver from 'semver';
import { FileFolder } from 'twenty-shared/types';
@@ -28,6 +28,8 @@ import { ApplicationService } from 'src/engine/core-modules/application/applicat
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import type { ApplicationManifest } from 'twenty-shared/application';
import { ApplicationRegistrationVariableService } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.service';
import { extractFileInfo } from 'src/engine/core-modules/file/utils/extract-file-info.utils';
import { sanitizeFile } from 'src/engine/core-modules/file/utils/sanitize-file.utils';
@Injectable()
export class ApplicationTarballService {
@@ -202,10 +204,21 @@ export class ApplicationTarballService {
},
});
const logoFileId = await this.uploadLogoFromTarball({
contentDir,
logoPath: manifest.application?.logoUrl ?? null,
registrationId: appRegistration.id,
existingLogoFileId: appRegistration.logoFileId ?? undefined,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
workspaceId: params.ownerWorkspaceId,
});
await this.appRegistrationRepository.update(appRegistration.id, {
sourceType: ApplicationRegistrationSourceType.TARBALL,
tarballFileId: savedFile.id,
name: manifest.application?.displayName ?? 'Unknown App',
logoFileId,
manifest,
latestAvailableVersion: packageJson?.version ?? null,
isListed: false,
@@ -231,4 +244,59 @@ export class ApplicationTarballService {
await fs.rm(tempDir, { recursive: true, force: true });
}
}
private async uploadLogoFromTarball(params: {
contentDir: string;
logoPath: string | null;
registrationId: string;
existingLogoFileId?: string;
applicationUniversalIdentifier: string;
workspaceId: string;
}): Promise<string | null> {
if (
!params.logoPath ||
params.logoPath.startsWith('http://') ||
params.logoPath.startsWith('https://')
) {
return null;
}
const absolutePath = resolve(params.contentDir, params.logoPath);
const safePrefx = params.contentDir.endsWith(sep)
? params.contentDir
: params.contentDir + sep;
if (!absolutePath.startsWith(safePrefx)) {
return null;
}
let content: Buffer;
try {
content = await fs.readFile(absolutePath);
} catch {
this.logger.warn(`Logo file not found in tarball: ${params.logoPath}`);
return null;
}
const { mimeType, ext } = await extractFileInfo({
file: content,
filename: params.logoPath,
});
const sanitizedContent = sanitizeFile({ file: content, ext, mimeType });
const savedLogoFile = await this.fileStorageService.writeFile({
sourceFile: sanitizedContent,
mimeType,
fileFolder: FileFolder.AppTarball,
applicationUniversalIdentifier: params.applicationUniversalIdentifier,
workspaceId: params.workspaceId,
resourcePath: `${params.registrationId}/logo${ext}`,
fileId: params.existingLogoFileId ?? v4(),
settings: { isTemporaryFile: false, toDelete: false },
});
return savedLogoFile.id;
}
}
@@ -4,6 +4,7 @@ import { IsOptional, IsString } from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
import { FileOutputDTO } from 'src/engine/core-modules/file/dtos/file-output.dto';
@ObjectType('ApplicationRegistrationSummary')
export class ApplicationRegistrationSummaryDTO {
@@ -18,8 +19,6 @@ export class ApplicationRegistrationSummaryDTO {
@Field(() => ApplicationRegistrationSourceType)
sourceType: ApplicationRegistrationSourceType;
@IsOptional()
@IsString()
@Field(() => String, { nullable: true })
logoUrl?: string | null;
@Field(() => FileOutputDTO, { nullable: true })
logo?: FileOutputDTO | null;
}
@@ -1,6 +1,7 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { FileOutputDTO } from 'src/engine/core-modules/file/dtos/file-output.dto';
@ObjectType('PublicApplicationRegistration')
export class PublicApplicationRegistrationDTO {
@@ -10,8 +11,8 @@ export class PublicApplicationRegistrationDTO {
@Field()
name: string;
@Field(() => String, { nullable: true })
logoUrl: string | null;
@Field(() => FileOutputDTO, { nullable: true })
logo: FileOutputDTO | null;
@Field(() => String, { nullable: true })
websiteUrl: string | null;
@@ -35,9 +35,8 @@ export class ApplicationDTO {
@Field({ nullable: true })
description?: string;
@IsOptional()
@IsString()
@Field({ nullable: true })
// Internal raw value (path or absolute URL); GraphQL exposure as FileOutput
// is handled by ApplicationLogoResolver.
logo?: string;
@IsOptional()
@@ -14,8 +14,9 @@ export const fromFlatApplicationToApplicationDto = ({
availablePackages,
universalIdentifier,
version,
workspaceId,
settingsCustomTabFrontComponentId,
}: FlatApplication): ApplicationDTO => {
}: FlatApplication): ApplicationDTO & { workspaceId: string } => {
return {
canBeUninstalled,
description: description ?? undefined,
@@ -30,6 +31,7 @@ export const fromFlatApplicationToApplicationDto = ({
availablePackages: availablePackages ?? {},
universalIdentifier,
version: version ?? undefined,
workspaceId,
settingsCustomTabFrontComponentId:
settingsCustomTabFrontComponentId ?? undefined,
};
@@ -0,0 +1,20 @@
import { buildPublicAssetUrl } from 'twenty-shared/utils';
export const resolveApplicationLogoUrl = ({
logo,
serverUrl,
workspaceId,
applicationId,
}: {
logo: string | null | undefined;
serverUrl: string;
workspaceId: string;
applicationId: string;
}): string | null => {
return buildPublicAssetUrl({
path: logo,
serverUrl,
workspaceId,
applicationId,
});
};
@@ -0,0 +1,42 @@
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
import { buildRegistryCdnUrl } from 'src/engine/core-modules/application/application-marketplace/utils/build-registry-cdn-url.util';
const isAbsoluteUrl = (url: string): boolean =>
url.startsWith('http://') || url.startsWith('https://');
export const resolveApplicationRegistrationLogoUrl = ({
logo,
sourceType,
sourcePackage,
latestAvailableVersion,
cdnBaseUrl,
}: {
logo: string | null | undefined;
sourceType: ApplicationRegistrationSourceType;
sourcePackage: string | null;
latestAvailableVersion: string | null;
cdnBaseUrl: string;
}): string | null => {
if (!logo) {
return null;
}
if (isAbsoluteUrl(logo)) {
return logo;
}
if (
sourceType === ApplicationRegistrationSourceType.NPM &&
sourcePackage &&
latestAvailableVersion
) {
return buildRegistryCdnUrl({
cdnBaseUrl,
packageName: sourcePackage,
version: latestAvailableVersion,
filePath: logo,
});
}
return null;
};
@@ -0,0 +1,18 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@ObjectType('FileOutput')
export class FileOutputDTO {
@Field(() => UUIDScalarType, { nullable: true })
fileId: string | null;
@Field(() => String, { nullable: true })
label: string | null;
@Field(() => String, { nullable: true })
extension: string | null;
@Field(() => String)
url: string;
}
@@ -0,0 +1,17 @@
import { type FileOutputDTO } from 'src/engine/core-modules/file/dtos/file-output.dto';
export const buildFileOutputFromUrl = (
url: string | null,
fileId: string | null = null,
): FileOutputDTO | null => {
if (url === null) {
return null;
}
return {
fileId,
label: null,
extension: null,
url,
};
};
@@ -199,6 +199,7 @@ export { isRecordGqlOperationSignature } from './typeguard/isRecordGqlOperationS
export { throwIfNotDefined } from './typeguard/throwIfNotDefined';
export { formatUpgradeCommandName } from './upgrade/formatUpgradeCommandName';
export { absoluteUrlSchema } from './url/absoluteUrlSchema';
export { buildPublicAssetUrl } from './url/buildPublicAssetUrl';
export { buildSignedPath } from './url/buildSignedPath';
export { ensureAbsoluteUrl } from './url/ensureAbsoluteUrl';
export { getAbsoluteUrlOrThrow } from './url/getAbsoluteUrlOrThrow';
@@ -0,0 +1,26 @@
const isAbsoluteUrl = (url: string): boolean =>
url.startsWith('http://') || url.startsWith('https://');
export const buildPublicAssetUrl = ({
path,
serverUrl,
workspaceId,
applicationId,
}: {
path: string | null | undefined;
serverUrl: string;
workspaceId: string;
applicationId: string;
}): string | null => {
if (!path) {
return null;
}
if (isAbsoluteUrl(path)) {
return path;
}
const baseUrl = serverUrl.endsWith('/') ? serverUrl.slice(0, -1) : serverUrl;
return `${baseUrl}/public-assets/${workspaceId}/${applicationId}/${path}`;
};
@@ -1,4 +1,5 @@
export * from './absoluteUrlSchema';
export * from './buildPublicAssetUrl';
export * from './getSafeUrl';
export * from './isSafeUrl';
export * from './ensureAbsoluteUrl';