feat(server): refresh application registration on install (#22527)
Part of the app settings architecture cleanup (twentyhq/core-team-issues#2456) — unifies registration ingestion across sources. ## Problem The dev sync, catalog sync and tarball upload flows all refresh the `applicationRegistration` row (manifest + display columns) at ingestion time, but the install/upgrade flow never did. Installing or upgrading an app relied on catalog sync having run beforehand, so a registration could serve stale display data (name, logo, description, screenshots…) after an install that shipped a newer manifest. ## Changes - `doInstallApplication` now refreshes the global registration from the resolved manifest after all install steps succeed (post-install hook included, so a hook failure that triggers uninstall can't leave the registration refreshed for a failed install). - Downgrade guard: the refresh is skipped when the installed version is provably older than `latestAvailableVersion` (per-workspace installs of an older version never downgrade the global registration). Extracted as a pure util `shouldRefreshApplicationRegistrationOnInstall` with unit tests: - `latestAvailableVersion` null or invalid semver → refresh - installed ≥ latest → refresh, and `latestAvailableVersion` is bumped to the installed version - installed < latest, or installed not valid semver while latest is → skip - Asset URLs mirror the existing per-source ingestion behavior: NPM registrations get manifest `logoUrl`/`screenshots` resolved to registry CDN URLs (same as catalog sync); tarball and other sources persist the manifest as-is (same as tarball upload). - `updateFromManifest` gains an optional `latestAvailableVersion` param (same conditional-spread style as `sourceType`). - `ApplicationRegistrationModule` added to `ApplicationInstallModule` imports (no cycle: nothing in the registration module's import graph imports the install module). The dev sync flow (`syncRegistrationMetadata`) already goes through `updateFromManifest` and writes the display columns — verified, no change needed. ## Verification - New unit spec: 6 cases on the guard util - `npx jest "application-registration|application-install|marketplace"` → 3 suites, 21 tests passed - `npx nx typecheck twenty-server` → success - `npx nx lint:diff-with-main twenty-server` → clean <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22527?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@@ -3106,7 +3106,6 @@ type Query {
|
||||
getViewGroup(id: String!): ViewGroup
|
||||
findManyApplications: [Application!]!
|
||||
findOneApplication(id: UUID, universalIdentifier: UUID): Application!
|
||||
getRoles: [Role!]!
|
||||
findApplicationRegistrationByClientId(clientId: String!): PublicApplicationRegistration
|
||||
findApplicationRegistrationByUniversalIdentifier(universalIdentifier: String!): ApplicationRegistration
|
||||
findManyApplicationRegistrations: [ApplicationRegistration!]!
|
||||
@@ -3114,6 +3113,7 @@ type Query {
|
||||
findApplicationRegistrationStats(id: String!): ApplicationRegistrationStats!
|
||||
findApplicationRegistrationVariables(applicationRegistrationId: String!): [ApplicationRegistrationVariableDTO!]!
|
||||
applicationRegistrationTarballUrl(id: String!): String
|
||||
getRoles: [Role!]!
|
||||
previewMessageCampaignAudience(input: PreviewMessageCampaignAudienceInput!): CampaignAudiencePreviewDTO!
|
||||
unsubscribeTopics: [UnsubscribeTopic!]!
|
||||
unsubscribePagePreviewUrl: String!
|
||||
@@ -3363,6 +3363,15 @@ type Mutation {
|
||||
destroyViewGroup(input: DestroyViewGroupInput!): ViewGroup!
|
||||
runWorkspaceMigration(workspaceMigration: WorkspaceMigrationInput!): Boolean!
|
||||
uninstallApplication(universalIdentifier: String!): Boolean!
|
||||
createApplicationRegistration(input: CreateApplicationRegistrationInput!): CreateApplicationRegistration!
|
||||
updateApplicationRegistration(input: UpdateApplicationRegistrationInput!): ApplicationRegistration!
|
||||
deleteApplicationRegistration(id: String!): Boolean!
|
||||
rotateApplicationRegistrationClientSecret(id: String!): RotateClientSecret!
|
||||
createApplicationRegistrationVariable(input: CreateApplicationRegistrationVariableInput!): ApplicationRegistrationVariable!
|
||||
updateApplicationRegistrationVariable(input: UpdateApplicationRegistrationVariableInput!): ApplicationRegistrationVariable!
|
||||
deleteApplicationRegistrationVariable(id: String!): Boolean!
|
||||
uploadAppTarball(file: Upload!, universalIdentifier: String): ApplicationRegistration!
|
||||
transferApplicationRegistrationOwnership(applicationRegistrationId: String!, targetWorkspaceSubdomain: String!): ApplicationRegistration!
|
||||
updateWorkspaceMemberRole(workspaceMemberId: UUID!, roleId: UUID!): WorkspaceMember!
|
||||
createOneRole(createRoleInput: CreateRoleInput!): Role!
|
||||
updateOneRole(updateRoleInput: UpdateRoleInput!): Role!
|
||||
@@ -3373,15 +3382,6 @@ type Mutation {
|
||||
upsertRowLevelPermissionPredicates(input: UpsertRowLevelPermissionPredicatesInput!): UpsertRowLevelPermissionPredicatesResult!
|
||||
assignRoleToAgent(agentId: UUID!, roleId: UUID!): Boolean!
|
||||
removeRoleFromAgent(agentId: UUID!): Boolean!
|
||||
createApplicationRegistration(input: CreateApplicationRegistrationInput!): CreateApplicationRegistration!
|
||||
updateApplicationRegistration(input: UpdateApplicationRegistrationInput!): ApplicationRegistration!
|
||||
deleteApplicationRegistration(id: String!): Boolean!
|
||||
rotateApplicationRegistrationClientSecret(id: String!): RotateClientSecret!
|
||||
createApplicationRegistrationVariable(input: CreateApplicationRegistrationVariableInput!): ApplicationRegistrationVariable!
|
||||
updateApplicationRegistrationVariable(input: UpdateApplicationRegistrationVariableInput!): ApplicationRegistrationVariable!
|
||||
deleteApplicationRegistrationVariable(id: String!): Boolean!
|
||||
uploadAppTarball(file: Upload!, universalIdentifier: String): ApplicationRegistration!
|
||||
transferApplicationRegistrationOwnership(applicationRegistrationId: String!, targetWorkspaceSubdomain: String!): ApplicationRegistration!
|
||||
sendEmailViaEmailingDomain(input: SendEmailViaDomainInput!): SendEmailViaDomainOutput!
|
||||
sendMessageCampaign(input: SendMessageCampaignInput!): SendMessageCampaignOutputDTO!
|
||||
createUnsubscribeTopic(input: CreateUnsubscribeTopicInput!): UnsubscribeTopic!
|
||||
@@ -4279,6 +4279,45 @@ enum WorkspaceMigrationActionType {
|
||||
update
|
||||
}
|
||||
|
||||
input CreateApplicationRegistrationInput {
|
||||
name: String!
|
||||
universalIdentifier: String
|
||||
oAuthRedirectUris: [String!]
|
||||
oAuthScopes: [String!]
|
||||
}
|
||||
|
||||
input UpdateApplicationRegistrationInput {
|
||||
id: String!
|
||||
update: UpdateApplicationRegistrationPayload!
|
||||
}
|
||||
|
||||
input UpdateApplicationRegistrationPayload {
|
||||
name: String
|
||||
oAuthRedirectUris: [String!]
|
||||
oAuthScopes: [String!]
|
||||
isListed: Boolean
|
||||
isPreInstalled: Boolean
|
||||
}
|
||||
|
||||
input CreateApplicationRegistrationVariableInput {
|
||||
applicationRegistrationId: String!
|
||||
key: String!
|
||||
value: String!
|
||||
description: String
|
||||
isSecret: Boolean
|
||||
}
|
||||
|
||||
input UpdateApplicationRegistrationVariableInput {
|
||||
id: String!
|
||||
update: UpdateApplicationRegistrationVariablePayload!
|
||||
}
|
||||
|
||||
input UpdateApplicationRegistrationVariablePayload {
|
||||
value: String
|
||||
resetValue: Boolean
|
||||
description: String
|
||||
}
|
||||
|
||||
input CreateRoleInput {
|
||||
id: String
|
||||
label: String!
|
||||
@@ -4374,45 +4413,6 @@ input RowLevelPermissionPredicateGroupInput {
|
||||
positionInRowLevelPermissionPredicateGroup: Float
|
||||
}
|
||||
|
||||
input CreateApplicationRegistrationInput {
|
||||
name: String!
|
||||
universalIdentifier: String
|
||||
oAuthRedirectUris: [String!]
|
||||
oAuthScopes: [String!]
|
||||
}
|
||||
|
||||
input UpdateApplicationRegistrationInput {
|
||||
id: String!
|
||||
update: UpdateApplicationRegistrationPayload!
|
||||
}
|
||||
|
||||
input UpdateApplicationRegistrationPayload {
|
||||
name: String
|
||||
oAuthRedirectUris: [String!]
|
||||
oAuthScopes: [String!]
|
||||
isListed: Boolean
|
||||
isPreInstalled: Boolean
|
||||
}
|
||||
|
||||
input CreateApplicationRegistrationVariableInput {
|
||||
applicationRegistrationId: String!
|
||||
key: String!
|
||||
value: String!
|
||||
description: String
|
||||
isSecret: Boolean
|
||||
}
|
||||
|
||||
input UpdateApplicationRegistrationVariableInput {
|
||||
id: String!
|
||||
update: UpdateApplicationRegistrationVariablePayload!
|
||||
}
|
||||
|
||||
input UpdateApplicationRegistrationVariablePayload {
|
||||
value: String
|
||||
resetValue: Boolean
|
||||
description: String
|
||||
}
|
||||
|
||||
input SendEmailViaDomainInput {
|
||||
emailingDomainId: String!
|
||||
to: [String!]!
|
||||
|
||||
@@ -2714,7 +2714,6 @@ export interface Query {
|
||||
getViewGroup?: ViewGroup
|
||||
findManyApplications: Application[]
|
||||
findOneApplication: Application
|
||||
getRoles: Role[]
|
||||
findApplicationRegistrationByClientId?: PublicApplicationRegistration
|
||||
findApplicationRegistrationByUniversalIdentifier?: ApplicationRegistration
|
||||
findManyApplicationRegistrations: ApplicationRegistration[]
|
||||
@@ -2722,6 +2721,7 @@ export interface Query {
|
||||
findApplicationRegistrationStats: ApplicationRegistrationStats
|
||||
findApplicationRegistrationVariables: ApplicationRegistrationVariableDTO[]
|
||||
applicationRegistrationTarballUrl?: Scalars['String']
|
||||
getRoles: Role[]
|
||||
previewMessageCampaignAudience: CampaignAudiencePreviewDTO
|
||||
unsubscribeTopics: UnsubscribeTopic[]
|
||||
unsubscribePagePreviewUrl: Scalars['String']
|
||||
@@ -2891,6 +2891,15 @@ export interface Mutation {
|
||||
destroyViewGroup: ViewGroup
|
||||
runWorkspaceMigration: Scalars['Boolean']
|
||||
uninstallApplication: Scalars['Boolean']
|
||||
createApplicationRegistration: CreateApplicationRegistration
|
||||
updateApplicationRegistration: ApplicationRegistration
|
||||
deleteApplicationRegistration: Scalars['Boolean']
|
||||
rotateApplicationRegistrationClientSecret: RotateClientSecret
|
||||
createApplicationRegistrationVariable: ApplicationRegistrationVariable
|
||||
updateApplicationRegistrationVariable: ApplicationRegistrationVariable
|
||||
deleteApplicationRegistrationVariable: Scalars['Boolean']
|
||||
uploadAppTarball: ApplicationRegistration
|
||||
transferApplicationRegistrationOwnership: ApplicationRegistration
|
||||
updateWorkspaceMemberRole: WorkspaceMember
|
||||
createOneRole: Role
|
||||
updateOneRole: Role
|
||||
@@ -2901,15 +2910,6 @@ export interface Mutation {
|
||||
upsertRowLevelPermissionPredicates: UpsertRowLevelPermissionPredicatesResult
|
||||
assignRoleToAgent: Scalars['Boolean']
|
||||
removeRoleFromAgent: Scalars['Boolean']
|
||||
createApplicationRegistration: CreateApplicationRegistration
|
||||
updateApplicationRegistration: ApplicationRegistration
|
||||
deleteApplicationRegistration: Scalars['Boolean']
|
||||
rotateApplicationRegistrationClientSecret: RotateClientSecret
|
||||
createApplicationRegistrationVariable: ApplicationRegistrationVariable
|
||||
updateApplicationRegistrationVariable: ApplicationRegistrationVariable
|
||||
deleteApplicationRegistrationVariable: Scalars['Boolean']
|
||||
uploadAppTarball: ApplicationRegistration
|
||||
transferApplicationRegistrationOwnership: ApplicationRegistration
|
||||
sendEmailViaEmailingDomain: SendEmailViaDomainOutput
|
||||
sendMessageCampaign: SendMessageCampaignOutputDTO
|
||||
createUnsubscribeTopic: UnsubscribeTopic
|
||||
@@ -5885,7 +5885,6 @@ export interface QueryGenqlSelection{
|
||||
getViewGroup?: (ViewGroupGenqlSelection & { __args: {id: Scalars['String']} })
|
||||
findManyApplications?: ApplicationGenqlSelection
|
||||
findOneApplication?: (ApplicationGenqlSelection & { __args?: {id?: (Scalars['UUID'] | null), universalIdentifier?: (Scalars['UUID'] | null)} })
|
||||
getRoles?: RoleGenqlSelection
|
||||
findApplicationRegistrationByClientId?: (PublicApplicationRegistrationGenqlSelection & { __args: {clientId: Scalars['String']} })
|
||||
findApplicationRegistrationByUniversalIdentifier?: (ApplicationRegistrationGenqlSelection & { __args: {universalIdentifier: Scalars['String']} })
|
||||
findManyApplicationRegistrations?: ApplicationRegistrationGenqlSelection
|
||||
@@ -5893,6 +5892,7 @@ export interface QueryGenqlSelection{
|
||||
findApplicationRegistrationStats?: (ApplicationRegistrationStatsGenqlSelection & { __args: {id: Scalars['String']} })
|
||||
findApplicationRegistrationVariables?: (ApplicationRegistrationVariableDTOGenqlSelection & { __args: {applicationRegistrationId: Scalars['String']} })
|
||||
applicationRegistrationTarballUrl?: { __args: {id: Scalars['String']} }
|
||||
getRoles?: RoleGenqlSelection
|
||||
previewMessageCampaignAudience?: (CampaignAudiencePreviewDTOGenqlSelection & { __args: {input: PreviewMessageCampaignAudienceInput} })
|
||||
unsubscribeTopics?: UnsubscribeTopicGenqlSelection
|
||||
unsubscribePagePreviewUrl?: boolean | number
|
||||
@@ -6087,6 +6087,15 @@ export interface MutationGenqlSelection{
|
||||
destroyViewGroup?: (ViewGroupGenqlSelection & { __args: {input: DestroyViewGroupInput} })
|
||||
runWorkspaceMigration?: { __args: {workspaceMigration: WorkspaceMigrationInput} }
|
||||
uninstallApplication?: { __args: {universalIdentifier: Scalars['String']} }
|
||||
createApplicationRegistration?: (CreateApplicationRegistrationGenqlSelection & { __args: {input: CreateApplicationRegistrationInput} })
|
||||
updateApplicationRegistration?: (ApplicationRegistrationGenqlSelection & { __args: {input: UpdateApplicationRegistrationInput} })
|
||||
deleteApplicationRegistration?: { __args: {id: Scalars['String']} }
|
||||
rotateApplicationRegistrationClientSecret?: (RotateClientSecretGenqlSelection & { __args: {id: Scalars['String']} })
|
||||
createApplicationRegistrationVariable?: (ApplicationRegistrationVariableGenqlSelection & { __args: {input: CreateApplicationRegistrationVariableInput} })
|
||||
updateApplicationRegistrationVariable?: (ApplicationRegistrationVariableGenqlSelection & { __args: {input: UpdateApplicationRegistrationVariableInput} })
|
||||
deleteApplicationRegistrationVariable?: { __args: {id: Scalars['String']} }
|
||||
uploadAppTarball?: (ApplicationRegistrationGenqlSelection & { __args: {file: Scalars['Upload'], universalIdentifier?: (Scalars['String'] | null)} })
|
||||
transferApplicationRegistrationOwnership?: (ApplicationRegistrationGenqlSelection & { __args: {applicationRegistrationId: Scalars['String'], targetWorkspaceSubdomain: Scalars['String']} })
|
||||
updateWorkspaceMemberRole?: (WorkspaceMemberGenqlSelection & { __args: {workspaceMemberId: Scalars['UUID'], roleId: Scalars['UUID']} })
|
||||
createOneRole?: (RoleGenqlSelection & { __args: {createRoleInput: CreateRoleInput} })
|
||||
updateOneRole?: (RoleGenqlSelection & { __args: {updateRoleInput: UpdateRoleInput} })
|
||||
@@ -6097,15 +6106,6 @@ export interface MutationGenqlSelection{
|
||||
upsertRowLevelPermissionPredicates?: (UpsertRowLevelPermissionPredicatesResultGenqlSelection & { __args: {input: UpsertRowLevelPermissionPredicatesInput} })
|
||||
assignRoleToAgent?: { __args: {agentId: Scalars['UUID'], roleId: Scalars['UUID']} }
|
||||
removeRoleFromAgent?: { __args: {agentId: Scalars['UUID']} }
|
||||
createApplicationRegistration?: (CreateApplicationRegistrationGenqlSelection & { __args: {input: CreateApplicationRegistrationInput} })
|
||||
updateApplicationRegistration?: (ApplicationRegistrationGenqlSelection & { __args: {input: UpdateApplicationRegistrationInput} })
|
||||
deleteApplicationRegistration?: { __args: {id: Scalars['String']} }
|
||||
rotateApplicationRegistrationClientSecret?: (RotateClientSecretGenqlSelection & { __args: {id: Scalars['String']} })
|
||||
createApplicationRegistrationVariable?: (ApplicationRegistrationVariableGenqlSelection & { __args: {input: CreateApplicationRegistrationVariableInput} })
|
||||
updateApplicationRegistrationVariable?: (ApplicationRegistrationVariableGenqlSelection & { __args: {input: UpdateApplicationRegistrationVariableInput} })
|
||||
deleteApplicationRegistrationVariable?: { __args: {id: Scalars['String']} }
|
||||
uploadAppTarball?: (ApplicationRegistrationGenqlSelection & { __args: {file: Scalars['Upload'], universalIdentifier?: (Scalars['String'] | null)} })
|
||||
transferApplicationRegistrationOwnership?: (ApplicationRegistrationGenqlSelection & { __args: {applicationRegistrationId: Scalars['String'], targetWorkspaceSubdomain: Scalars['String']} })
|
||||
sendEmailViaEmailingDomain?: (SendEmailViaDomainOutputGenqlSelection & { __args: {input: SendEmailViaDomainInput} })
|
||||
sendMessageCampaign?: (SendMessageCampaignOutputDTOGenqlSelection & { __args: {input: SendMessageCampaignInput} })
|
||||
createUnsubscribeTopic?: (UnsubscribeTopicGenqlSelection & { __args: {input: CreateUnsubscribeTopicInput} })
|
||||
@@ -6474,6 +6474,18 @@ export interface WorkspaceMigrationInput {actions: WorkspaceMigrationDeleteActio
|
||||
|
||||
export interface WorkspaceMigrationDeleteActionInput {type: WorkspaceMigrationActionType,metadataName: AllMetadataName,universalIdentifier: Scalars['String']}
|
||||
|
||||
export interface CreateApplicationRegistrationInput {name: Scalars['String'],universalIdentifier?: (Scalars['String'] | null),oAuthRedirectUris?: (Scalars['String'][] | null),oAuthScopes?: (Scalars['String'][] | null)}
|
||||
|
||||
export interface UpdateApplicationRegistrationInput {id: Scalars['String'],update: UpdateApplicationRegistrationPayload}
|
||||
|
||||
export interface UpdateApplicationRegistrationPayload {name?: (Scalars['String'] | null),oAuthRedirectUris?: (Scalars['String'][] | null),oAuthScopes?: (Scalars['String'][] | null),isListed?: (Scalars['Boolean'] | null),isPreInstalled?: (Scalars['Boolean'] | null)}
|
||||
|
||||
export interface CreateApplicationRegistrationVariableInput {applicationRegistrationId: Scalars['String'],key: Scalars['String'],value: Scalars['String'],description?: (Scalars['String'] | null),isSecret?: (Scalars['Boolean'] | null)}
|
||||
|
||||
export interface UpdateApplicationRegistrationVariableInput {id: Scalars['String'],update: UpdateApplicationRegistrationVariablePayload}
|
||||
|
||||
export interface UpdateApplicationRegistrationVariablePayload {value?: (Scalars['String'] | null),resetValue?: (Scalars['Boolean'] | null),description?: (Scalars['String'] | null)}
|
||||
|
||||
export interface CreateRoleInput {id?: (Scalars['String'] | null),label: Scalars['String'],description?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),canUpdateAllSettings?: (Scalars['Boolean'] | null),canAccessAllTools?: (Scalars['Boolean'] | null),canReadAllObjectRecords?: (Scalars['Boolean'] | null),canUpdateAllObjectRecords?: (Scalars['Boolean'] | null),canSoftDeleteAllObjectRecords?: (Scalars['Boolean'] | null),canDestroyAllObjectRecords?: (Scalars['Boolean'] | null),canBeAssignedToUsers?: (Scalars['Boolean'] | null),canBeAssignedToAgents?: (Scalars['Boolean'] | null),canBeAssignedToApiKeys?: (Scalars['Boolean'] | null)}
|
||||
|
||||
export interface UpdateRoleInput {update: UpdateRolePayload,
|
||||
@@ -6498,18 +6510,6 @@ export interface RowLevelPermissionPredicateInput {id?: (Scalars['UUID'] | null)
|
||||
|
||||
export interface RowLevelPermissionPredicateGroupInput {id?: (Scalars['UUID'] | null),objectMetadataId: Scalars['UUID'],parentRowLevelPermissionPredicateGroupId?: (Scalars['UUID'] | null),logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator,positionInRowLevelPermissionPredicateGroup?: (Scalars['Float'] | null)}
|
||||
|
||||
export interface CreateApplicationRegistrationInput {name: Scalars['String'],universalIdentifier?: (Scalars['String'] | null),oAuthRedirectUris?: (Scalars['String'][] | null),oAuthScopes?: (Scalars['String'][] | null)}
|
||||
|
||||
export interface UpdateApplicationRegistrationInput {id: Scalars['String'],update: UpdateApplicationRegistrationPayload}
|
||||
|
||||
export interface UpdateApplicationRegistrationPayload {name?: (Scalars['String'] | null),oAuthRedirectUris?: (Scalars['String'][] | null),oAuthScopes?: (Scalars['String'][] | null),isListed?: (Scalars['Boolean'] | null),isPreInstalled?: (Scalars['Boolean'] | null)}
|
||||
|
||||
export interface CreateApplicationRegistrationVariableInput {applicationRegistrationId: Scalars['String'],key: Scalars['String'],value: Scalars['String'],description?: (Scalars['String'] | null),isSecret?: (Scalars['Boolean'] | null)}
|
||||
|
||||
export interface UpdateApplicationRegistrationVariableInput {id: Scalars['String'],update: UpdateApplicationRegistrationVariablePayload}
|
||||
|
||||
export interface UpdateApplicationRegistrationVariablePayload {value?: (Scalars['String'] | null),resetValue?: (Scalars['Boolean'] | null),description?: (Scalars['String'] | null)}
|
||||
|
||||
export interface SendEmailViaDomainInput {emailingDomainId: Scalars['String'],to: Scalars['String'][],cc?: (Scalars['String'][] | null),bcc?: (Scalars['String'][] | null),subject: Scalars['String'],text: Scalars['String'],html?: (Scalars['String'] | null),from: Scalars['String'],replyTo?: (Scalars['String'][] | null)}
|
||||
|
||||
export interface SendMessageCampaignInput {listId: Scalars['String'],unsubscribeTopicId?: (Scalars['String'] | null),subject: Scalars['String'],body: Scalars['String'],fromAddress: Scalars['String']}
|
||||
|
||||
@@ -6400,9 +6400,6 @@ export default {
|
||||
]
|
||||
}
|
||||
],
|
||||
"getRoles": [
|
||||
29
|
||||
],
|
||||
"findApplicationRegistrationByClientId": [
|
||||
199,
|
||||
{
|
||||
@@ -6460,6 +6457,9 @@ export default {
|
||||
]
|
||||
}
|
||||
],
|
||||
"getRoles": [
|
||||
29
|
||||
],
|
||||
"previewMessageCampaignAudience": [
|
||||
281,
|
||||
{
|
||||
@@ -7994,109 +7994,11 @@ export default {
|
||||
]
|
||||
}
|
||||
],
|
||||
"updateWorkspaceMemberRole": [
|
||||
20,
|
||||
{
|
||||
"workspaceMemberId": [
|
||||
3,
|
||||
"UUID!"
|
||||
],
|
||||
"roleId": [
|
||||
3,
|
||||
"UUID!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"createOneRole": [
|
||||
29,
|
||||
{
|
||||
"createRoleInput": [
|
||||
443,
|
||||
"CreateRoleInput!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"updateOneRole": [
|
||||
29,
|
||||
{
|
||||
"updateRoleInput": [
|
||||
444,
|
||||
"UpdateRoleInput!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"deleteOneRole": [
|
||||
1,
|
||||
{
|
||||
"roleId": [
|
||||
3,
|
||||
"UUID!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"upsertObjectPermissions": [
|
||||
16,
|
||||
{
|
||||
"upsertObjectPermissionsInput": [
|
||||
446,
|
||||
"UpsertObjectPermissionsInput!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"upsertPermissionFlags": [
|
||||
27,
|
||||
{
|
||||
"upsertPermissionFlagsInput": [
|
||||
448,
|
||||
"UpsertPermissionFlagsInput!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"upsertFieldPermissions": [
|
||||
26,
|
||||
{
|
||||
"upsertFieldPermissionsInput": [
|
||||
449,
|
||||
"UpsertFieldPermissionsInput!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"upsertRowLevelPermissionPredicates": [
|
||||
232,
|
||||
{
|
||||
"input": [
|
||||
451,
|
||||
"UpsertRowLevelPermissionPredicatesInput!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"assignRoleToAgent": [
|
||||
6,
|
||||
{
|
||||
"agentId": [
|
||||
3,
|
||||
"UUID!"
|
||||
],
|
||||
"roleId": [
|
||||
3,
|
||||
"UUID!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"removeRoleFromAgent": [
|
||||
6,
|
||||
{
|
||||
"agentId": [
|
||||
3,
|
||||
"UUID!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"createApplicationRegistration": [
|
||||
198,
|
||||
{
|
||||
"input": [
|
||||
454,
|
||||
443,
|
||||
"CreateApplicationRegistrationInput!"
|
||||
]
|
||||
}
|
||||
@@ -8105,7 +8007,7 @@ export default {
|
||||
8,
|
||||
{
|
||||
"input": [
|
||||
455,
|
||||
444,
|
||||
"UpdateApplicationRegistrationInput!"
|
||||
]
|
||||
}
|
||||
@@ -8132,7 +8034,7 @@ export default {
|
||||
5,
|
||||
{
|
||||
"input": [
|
||||
457,
|
||||
446,
|
||||
"CreateApplicationRegistrationVariableInput!"
|
||||
]
|
||||
}
|
||||
@@ -8141,7 +8043,7 @@ export default {
|
||||
5,
|
||||
{
|
||||
"input": [
|
||||
458,
|
||||
447,
|
||||
"UpdateApplicationRegistrationVariableInput!"
|
||||
]
|
||||
}
|
||||
@@ -8180,6 +8082,104 @@ export default {
|
||||
]
|
||||
}
|
||||
],
|
||||
"updateWorkspaceMemberRole": [
|
||||
20,
|
||||
{
|
||||
"workspaceMemberId": [
|
||||
3,
|
||||
"UUID!"
|
||||
],
|
||||
"roleId": [
|
||||
3,
|
||||
"UUID!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"createOneRole": [
|
||||
29,
|
||||
{
|
||||
"createRoleInput": [
|
||||
449,
|
||||
"CreateRoleInput!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"updateOneRole": [
|
||||
29,
|
||||
{
|
||||
"updateRoleInput": [
|
||||
450,
|
||||
"UpdateRoleInput!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"deleteOneRole": [
|
||||
1,
|
||||
{
|
||||
"roleId": [
|
||||
3,
|
||||
"UUID!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"upsertObjectPermissions": [
|
||||
16,
|
||||
{
|
||||
"upsertObjectPermissionsInput": [
|
||||
452,
|
||||
"UpsertObjectPermissionsInput!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"upsertPermissionFlags": [
|
||||
27,
|
||||
{
|
||||
"upsertPermissionFlagsInput": [
|
||||
454,
|
||||
"UpsertPermissionFlagsInput!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"upsertFieldPermissions": [
|
||||
26,
|
||||
{
|
||||
"upsertFieldPermissionsInput": [
|
||||
455,
|
||||
"UpsertFieldPermissionsInput!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"upsertRowLevelPermissionPredicates": [
|
||||
232,
|
||||
{
|
||||
"input": [
|
||||
457,
|
||||
"UpsertRowLevelPermissionPredicatesInput!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"assignRoleToAgent": [
|
||||
6,
|
||||
{
|
||||
"agentId": [
|
||||
3,
|
||||
"UUID!"
|
||||
],
|
||||
"roleId": [
|
||||
3,
|
||||
"UUID!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"removeRoleFromAgent": [
|
||||
6,
|
||||
{
|
||||
"agentId": [
|
||||
3,
|
||||
"UUID!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"sendEmailViaEmailingDomain": [
|
||||
282,
|
||||
{
|
||||
@@ -10950,6 +10950,99 @@ export default {
|
||||
]
|
||||
},
|
||||
"WorkspaceMigrationActionType": {},
|
||||
"CreateApplicationRegistrationInput": {
|
||||
"name": [
|
||||
1
|
||||
],
|
||||
"universalIdentifier": [
|
||||
1
|
||||
],
|
||||
"oAuthRedirectUris": [
|
||||
1
|
||||
],
|
||||
"oAuthScopes": [
|
||||
1
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"UpdateApplicationRegistrationInput": {
|
||||
"id": [
|
||||
1
|
||||
],
|
||||
"update": [
|
||||
445
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"UpdateApplicationRegistrationPayload": {
|
||||
"name": [
|
||||
1
|
||||
],
|
||||
"oAuthRedirectUris": [
|
||||
1
|
||||
],
|
||||
"oAuthScopes": [
|
||||
1
|
||||
],
|
||||
"isListed": [
|
||||
6
|
||||
],
|
||||
"isPreInstalled": [
|
||||
6
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"CreateApplicationRegistrationVariableInput": {
|
||||
"applicationRegistrationId": [
|
||||
1
|
||||
],
|
||||
"key": [
|
||||
1
|
||||
],
|
||||
"value": [
|
||||
1
|
||||
],
|
||||
"description": [
|
||||
1
|
||||
],
|
||||
"isSecret": [
|
||||
6
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"UpdateApplicationRegistrationVariableInput": {
|
||||
"id": [
|
||||
1
|
||||
],
|
||||
"update": [
|
||||
448
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"UpdateApplicationRegistrationVariablePayload": {
|
||||
"value": [
|
||||
1
|
||||
],
|
||||
"resetValue": [
|
||||
6
|
||||
],
|
||||
"description": [
|
||||
1
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"CreateRoleInput": {
|
||||
"id": [
|
||||
1
|
||||
@@ -10996,7 +11089,7 @@ export default {
|
||||
},
|
||||
"UpdateRoleInput": {
|
||||
"update": [
|
||||
445
|
||||
451
|
||||
],
|
||||
"id": [
|
||||
3
|
||||
@@ -11051,7 +11144,7 @@ export default {
|
||||
3
|
||||
],
|
||||
"objectPermissions": [
|
||||
447
|
||||
453
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -11093,7 +11186,7 @@ export default {
|
||||
3
|
||||
],
|
||||
"fieldPermissions": [
|
||||
450
|
||||
456
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -11124,10 +11217,10 @@ export default {
|
||||
3
|
||||
],
|
||||
"predicates": [
|
||||
452
|
||||
458
|
||||
],
|
||||
"predicateGroups": [
|
||||
453
|
||||
459
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -11185,99 +11278,6 @@ export default {
|
||||
1
|
||||
]
|
||||
},
|
||||
"CreateApplicationRegistrationInput": {
|
||||
"name": [
|
||||
1
|
||||
],
|
||||
"universalIdentifier": [
|
||||
1
|
||||
],
|
||||
"oAuthRedirectUris": [
|
||||
1
|
||||
],
|
||||
"oAuthScopes": [
|
||||
1
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"UpdateApplicationRegistrationInput": {
|
||||
"id": [
|
||||
1
|
||||
],
|
||||
"update": [
|
||||
456
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"UpdateApplicationRegistrationPayload": {
|
||||
"name": [
|
||||
1
|
||||
],
|
||||
"oAuthRedirectUris": [
|
||||
1
|
||||
],
|
||||
"oAuthScopes": [
|
||||
1
|
||||
],
|
||||
"isListed": [
|
||||
6
|
||||
],
|
||||
"isPreInstalled": [
|
||||
6
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"CreateApplicationRegistrationVariableInput": {
|
||||
"applicationRegistrationId": [
|
||||
1
|
||||
],
|
||||
"key": [
|
||||
1
|
||||
],
|
||||
"value": [
|
||||
1
|
||||
],
|
||||
"description": [
|
||||
1
|
||||
],
|
||||
"isSecret": [
|
||||
6
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"UpdateApplicationRegistrationVariableInput": {
|
||||
"id": [
|
||||
1
|
||||
],
|
||||
"update": [
|
||||
459
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"UpdateApplicationRegistrationVariablePayload": {
|
||||
"value": [
|
||||
1
|
||||
],
|
||||
"resetValue": [
|
||||
6
|
||||
],
|
||||
"description": [
|
||||
1
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"SendEmailViaDomainInput": {
|
||||
"emailingDomainId": [
|
||||
1
|
||||
|
||||
+2
@@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { CacheLockModule } from 'src/engine/core-modules/cache-lock/cache-lock.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
||||
import { ApplicationRegistrationModule } from 'src/engine/core-modules/application/application-registration/application-registration.module';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { ApplicationManifestModule } from 'src/engine/core-modules/application/application-manifest/application-manifest.module';
|
||||
import { ApplicationPackageModule } from 'src/engine/core-modules/application/application-package/application-package.module';
|
||||
@@ -19,6 +20,7 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([ApplicationRegistrationEntity]),
|
||||
ApplicationModule,
|
||||
ApplicationRegistrationModule,
|
||||
ApplicationManifestModule,
|
||||
ApplicationPackageModule,
|
||||
CacheLockModule,
|
||||
|
||||
+30
@@ -15,7 +15,9 @@ import {
|
||||
ApplicationExceptionCode,
|
||||
} from 'src/engine/core-modules/application/application.exception';
|
||||
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
||||
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
|
||||
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
|
||||
import { ManifestAssetUrlResolverService } from 'src/engine/core-modules/application/application-registration/manifest-asset-url-resolver.service';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { ApplicationPackageFetcherService } from 'src/engine/core-modules/application/application-package/application-package-fetcher.service';
|
||||
@@ -55,6 +57,7 @@ export class ApplicationInstallService {
|
||||
@InjectRepository(ApplicationRegistrationEntity)
|
||||
private readonly appRegistrationRepository: Repository<ApplicationRegistrationEntity>,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly applicationRegistrationService: ApplicationRegistrationService,
|
||||
private readonly applicationPackageFetcherService: ApplicationPackageFetcherService,
|
||||
private readonly applicationVersionValidationService: ApplicationVersionValidationService,
|
||||
private readonly applicationSyncService: ApplicationSyncService,
|
||||
@@ -65,6 +68,7 @@ export class ApplicationInstallService {
|
||||
@InjectMessageQueue(MessageQueue.logicFunctionQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly manifestAssetUrlResolverService: ManifestAssetUrlResolverService,
|
||||
) {}
|
||||
|
||||
async installApplication(params: {
|
||||
@@ -270,6 +274,12 @@ export class ApplicationInstallService {
|
||||
universalIdentifier,
|
||||
});
|
||||
|
||||
await this.refreshRegistrationFromInstall({
|
||||
appRegistration,
|
||||
manifest: resolvedPackage.manifest,
|
||||
installedVersion: newVersion,
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Successfully installed app ${universalIdentifier} v${resolvedPackage.packageJson.version ?? 'unknown'}`,
|
||||
);
|
||||
@@ -297,6 +307,26 @@ export class ApplicationInstallService {
|
||||
}
|
||||
}
|
||||
|
||||
private async refreshRegistrationFromInstall(params: {
|
||||
appRegistration: ApplicationRegistrationEntity;
|
||||
manifest: Manifest;
|
||||
installedVersion: string;
|
||||
}): Promise<void> {
|
||||
const { appRegistration, manifest, installedVersion } = params;
|
||||
|
||||
await this.applicationRegistrationService.updateFromManifest({
|
||||
applicationRegistrationId: appRegistration.id,
|
||||
manifest: this.manifestAssetUrlResolverService.resolveFromRegistration({
|
||||
sourceType: appRegistration.sourceType,
|
||||
sourcePackage: appRegistration.sourcePackage,
|
||||
manifest,
|
||||
version: installedVersion,
|
||||
}),
|
||||
latestAvailableVersion: installedVersion,
|
||||
preventVersionDowngrade: true,
|
||||
});
|
||||
}
|
||||
|
||||
private async runPreInstallHook(params: {
|
||||
manifest: Manifest;
|
||||
workspaceId: string;
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import { shouldRefreshApplicationRegistrationOnInstall } from 'src/engine/core-modules/application/application-install/utils/should-refresh-application-registration-on-install.util';
|
||||
|
||||
describe('shouldRefreshApplicationRegistrationOnInstall', () => {
|
||||
it('should refresh when latestAvailableVersion is null', () => {
|
||||
expect(
|
||||
shouldRefreshApplicationRegistrationOnInstall({
|
||||
installedVersion: '1.0.0',
|
||||
latestAvailableVersion: null,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should refresh when latestAvailableVersion is not a valid semver', () => {
|
||||
expect(
|
||||
shouldRefreshApplicationRegistrationOnInstall({
|
||||
installedVersion: '1.0.0',
|
||||
latestAvailableVersion: 'not-a-version',
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should refresh when installed version is newer than latestAvailableVersion', () => {
|
||||
expect(
|
||||
shouldRefreshApplicationRegistrationOnInstall({
|
||||
installedVersion: '2.0.0',
|
||||
latestAvailableVersion: '1.0.0',
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should refresh when installed version equals latestAvailableVersion', () => {
|
||||
expect(
|
||||
shouldRefreshApplicationRegistrationOnInstall({
|
||||
installedVersion: '1.2.3',
|
||||
latestAvailableVersion: '1.2.3',
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should not refresh when installed version is older than latestAvailableVersion', () => {
|
||||
expect(
|
||||
shouldRefreshApplicationRegistrationOnInstall({
|
||||
installedVersion: '1.0.0',
|
||||
latestAvailableVersion: '2.0.0',
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should not refresh when installed version is not a valid semver and latestAvailableVersion is valid', () => {
|
||||
expect(
|
||||
shouldRefreshApplicationRegistrationOnInstall({
|
||||
installedVersion: 'not-a-version',
|
||||
latestAvailableVersion: '1.0.0',
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import semver from 'semver';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const shouldRefreshApplicationRegistrationOnInstall = ({
|
||||
installedVersion,
|
||||
latestAvailableVersion,
|
||||
}: {
|
||||
installedVersion: string;
|
||||
latestAvailableVersion: string | null;
|
||||
}): boolean => {
|
||||
if (
|
||||
!isDefined(latestAvailableVersion) ||
|
||||
!isDefined(semver.valid(latestAvailableVersion))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isDefined(semver.valid(installedVersion))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return semver.gte(installedVersion, latestAvailableVersion);
|
||||
};
|
||||
+8
-16
@@ -3,9 +3,7 @@ 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';
|
||||
import { ManifestAssetUrlResolverService } from 'src/engine/core-modules/application/application-registration/manifest-asset-url-resolver.service';
|
||||
|
||||
@Injectable()
|
||||
export class MarketplaceCatalogSyncService {
|
||||
@@ -14,7 +12,7 @@ export class MarketplaceCatalogSyncService {
|
||||
constructor(
|
||||
private readonly applicationRegistrationService: ApplicationRegistrationService,
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly manifestAssetUrlResolverService: ManifestAssetUrlResolverService,
|
||||
) {}
|
||||
|
||||
async syncCatalog(): Promise<void> {
|
||||
@@ -45,18 +43,12 @@ export class MarketplaceCatalogSyncService {
|
||||
const universalIdentifier =
|
||||
fetchedManifest.application.universalIdentifier;
|
||||
|
||||
const cdnBaseUrl = this.twentyConfigService.get('APP_REGISTRY_CDN_URL');
|
||||
|
||||
const manifestWithResolvedUrls = resolveManifestAssetUrls(
|
||||
fetchedManifest,
|
||||
(filePath) =>
|
||||
buildRegistryCdnUrl({
|
||||
cdnBaseUrl,
|
||||
packageName: pkg.name,
|
||||
version: pkg.version,
|
||||
filePath,
|
||||
}),
|
||||
);
|
||||
const manifestWithResolvedUrls =
|
||||
this.manifestAssetUrlResolverService.resolveFromRegistrySource({
|
||||
manifest: fetchedManifest,
|
||||
packageName: pkg.name,
|
||||
version: pkg.version,
|
||||
});
|
||||
|
||||
await this.applicationRegistrationService.upsertFromCatalog({
|
||||
universalIdentifier,
|
||||
|
||||
+5
@@ -6,9 +6,11 @@ import { ApplicationRegistrationResolver } from 'src/engine/core-modules/applica
|
||||
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
|
||||
import { ApplicationRegistrationVariableModule } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.module';
|
||||
import { ApplicationTarballService } from 'src/engine/core-modules/application/application-registration/application-tarball.service';
|
||||
import { ManifestAssetUrlResolverService } from 'src/engine/core-modules/application/application-registration/manifest-asset-url-resolver.service';
|
||||
import { ApplicationPackageModule } from 'src/engine/core-modules/application/application-package/application-package.module';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { CacheLockModule } from 'src/engine/core-modules/cache-lock/cache-lock.module';
|
||||
import { DomainServerConfigModule } from 'src/engine/core-modules/domain/domain-server-config/domain-server-config.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
|
||||
@@ -27,6 +29,7 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
||||
ApplicationRegistrationVariableModule,
|
||||
ApplicationModule,
|
||||
ApplicationPackageModule,
|
||||
CacheLockModule,
|
||||
DomainServerConfigModule,
|
||||
FeatureFlagModule,
|
||||
PermissionsModule,
|
||||
@@ -38,10 +41,12 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
||||
ApplicationRegistrationService,
|
||||
ApplicationRegistrationResolver,
|
||||
ApplicationTarballService,
|
||||
ManifestAssetUrlResolverService,
|
||||
],
|
||||
exports: [
|
||||
ApplicationRegistrationService,
|
||||
ApplicationRegistrationVariableModule,
|
||||
ManifestAssetUrlResolverService,
|
||||
],
|
||||
})
|
||||
export class ApplicationRegistrationModule {}
|
||||
|
||||
+41
-11
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import crypto from 'crypto';
|
||||
@@ -10,6 +10,8 @@ import { ILike, type FindOptionsWhere, type Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ALL_OAUTH_SCOPES } from 'src/engine/core-modules/application/application-oauth/constants/oauth-scopes';
|
||||
import { shouldRefreshApplicationRegistrationOnInstall } from 'src/engine/core-modules/application/application-install/utils/should-refresh-application-registration-on-install.util';
|
||||
import { CacheLockService } from 'src/engine/core-modules/cache-lock/cache-lock.service';
|
||||
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
||||
import { TWENTY_CLI_APPLICATION_REGISTRATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-cli-application-registration.constant';
|
||||
import {
|
||||
@@ -79,6 +81,8 @@ export type ApplicationRegistrationCatalogCard = {
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationRegistrationService {
|
||||
private readonly logger = new Logger(ApplicationRegistrationService.name);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(ApplicationRegistrationEntity)
|
||||
private readonly applicationRegistrationRepository: Repository<ApplicationRegistrationEntity>,
|
||||
@@ -87,6 +91,7 @@ export class ApplicationRegistrationService {
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
private readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService,
|
||||
private readonly cacheLockService: CacheLockService,
|
||||
) {}
|
||||
|
||||
async findMany(
|
||||
@@ -286,22 +291,47 @@ export class ApplicationRegistrationService {
|
||||
applicationRegistrationId,
|
||||
manifest,
|
||||
sourceType,
|
||||
latestAvailableVersion,
|
||||
preventVersionDowngrade = false,
|
||||
}: {
|
||||
applicationRegistrationId: string;
|
||||
manifest: Manifest;
|
||||
sourceType?: ApplicationRegistrationSourceType;
|
||||
latestAvailableVersion?: string;
|
||||
preventVersionDowngrade?: boolean;
|
||||
}): Promise<void> {
|
||||
const existing = await this.applicationRegistrationRepository.findOneOrFail(
|
||||
{ where: { id: applicationRegistrationId } },
|
||||
);
|
||||
await this.cacheLockService.withLock(async () => {
|
||||
const existing =
|
||||
await this.applicationRegistrationRepository.findOneOrFail({
|
||||
where: { id: applicationRegistrationId },
|
||||
});
|
||||
|
||||
await this.applicationRegistrationRepository.save({
|
||||
...existing,
|
||||
name: manifest.application.displayName,
|
||||
manifest,
|
||||
...fromManifestApplicationToDisplayFields(manifest.application),
|
||||
...(sourceType !== undefined && { sourceType }),
|
||||
});
|
||||
if (
|
||||
preventVersionDowngrade &&
|
||||
isDefined(latestAvailableVersion) &&
|
||||
!shouldRefreshApplicationRegistrationOnInstall({
|
||||
installedVersion: latestAvailableVersion,
|
||||
latestAvailableVersion: existing.latestAvailableVersion,
|
||||
})
|
||||
) {
|
||||
this.logger.log(
|
||||
`Skipping registration update for ${existing.universalIdentifier}: version ${latestAvailableVersion} is older than latest available version ${existing.latestAvailableVersion}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.applicationRegistrationRepository.save({
|
||||
...existing,
|
||||
name: manifest.application.displayName,
|
||||
manifest,
|
||||
...fromManifestApplicationToDisplayFields(manifest.application),
|
||||
...(sourceType !== undefined && { sourceType }),
|
||||
...(latestAvailableVersion !== undefined && {
|
||||
latestAvailableVersion,
|
||||
}),
|
||||
});
|
||||
}, `application-registration-update:${applicationRegistrationId}`);
|
||||
}
|
||||
|
||||
async delete(id: string, ownerWorkspaceId: string): Promise<boolean> {
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { type Manifest } from 'twenty-shared/application';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { buildRegistryCdnUrl } from 'src/engine/core-modules/application/application-marketplace/utils/build-registry-cdn-url.util';
|
||||
import { resolveManifestAssetUrls } from 'src/engine/core-modules/application/application-marketplace/utils/resolve-manifest-asset-urls.util';
|
||||
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
|
||||
@Injectable()
|
||||
export class ManifestAssetUrlResolverService {
|
||||
constructor(private readonly twentyConfigService: TwentyConfigService) {}
|
||||
|
||||
resolveFromRegistrySource({
|
||||
manifest,
|
||||
packageName,
|
||||
version,
|
||||
}: {
|
||||
manifest: Manifest;
|
||||
packageName: string;
|
||||
version: string;
|
||||
}): Manifest {
|
||||
const cdnBaseUrl = this.twentyConfigService.get('APP_REGISTRY_CDN_URL');
|
||||
|
||||
return resolveManifestAssetUrls(manifest, (filePath) =>
|
||||
buildRegistryCdnUrl({ cdnBaseUrl, packageName, version, filePath }),
|
||||
);
|
||||
}
|
||||
|
||||
resolveFromRegistration({
|
||||
sourceType,
|
||||
sourcePackage,
|
||||
manifest,
|
||||
version,
|
||||
}: {
|
||||
sourceType: ApplicationRegistrationSourceType;
|
||||
sourcePackage: string | null;
|
||||
manifest: Manifest;
|
||||
version: string;
|
||||
}): Manifest {
|
||||
if (
|
||||
sourceType !== ApplicationRegistrationSourceType.NPM ||
|
||||
!isDefined(sourcePackage)
|
||||
) {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
return this.resolveFromRegistrySource({
|
||||
manifest,
|
||||
packageName: sourcePackage,
|
||||
version,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user