diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-19/2-19-instance-command-fast-1783073776590-add-display-fields-to-application-registration.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-19/2-19-instance-command-fast-1783073776590-add-display-fields-to-application-registration.ts new file mode 100644 index 0000000000..f12de56cdf --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-19/2-19-instance-command-fast-1783073776590-add-display-fields-to-application-registration.ts @@ -0,0 +1,69 @@ +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.19.0', 1783073776590) +export class AddDisplayFieldsToApplicationRegistrationFastInstanceCommand + implements FastInstanceCommand +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" ADD COLUMN IF NOT EXISTS "description" text', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" ADD COLUMN IF NOT EXISTS "author" text', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" ADD COLUMN IF NOT EXISTS "category" text', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" ADD COLUMN IF NOT EXISTS "websiteUrl" text', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" ADD COLUMN IF NOT EXISTS "aboutDescription" text', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" ADD COLUMN IF NOT EXISTS "termsUrl" text', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" ADD COLUMN IF NOT EXISTS "emailSupport" text', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" ADD COLUMN IF NOT EXISTS "issueReportUrl" text', + ); + await queryRunner.query( + `ALTER TABLE "core"."applicationRegistration" ADD COLUMN IF NOT EXISTS "screenshots" text array NOT NULL DEFAULT '{}'`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" DROP COLUMN "description"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" DROP COLUMN "author"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" DROP COLUMN "category"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" DROP COLUMN "websiteUrl"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" DROP COLUMN "aboutDescription"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" DROP COLUMN "termsUrl"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" DROP COLUMN "emailSupport"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" DROP COLUMN "issueReportUrl"', + ); + await queryRunner.query( + 'ALTER TABLE "core"."applicationRegistration" DROP COLUMN "screenshots"', + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-19/2-19-instance-command-slow-1783073776591-backfill-display-fields-on-application-registration.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-19/2-19-instance-command-slow-1783073776591-backfill-display-fields-on-application-registration.ts new file mode 100644 index 0000000000..ee5a87a7a1 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-19/2-19-instance-command-slow-1783073776591-backfill-display-fields-on-application-registration.ts @@ -0,0 +1,44 @@ +import { DataSource, QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface'; + +@RegisteredInstanceCommand('2.19.0', 1783073776591, { type: 'slow' }) +export class BackfillDisplayFieldsOnApplicationRegistrationSlowInstanceCommand + implements SlowInstanceCommand +{ + async runDataMigration(dataSource: DataSource): Promise { + await dataSource.query( + `UPDATE "core"."applicationRegistration" + SET + "description" = "manifest"->'application'->>'description', + "author" = "manifest"->'application'->>'author', + "category" = "manifest"->'application'->>'category', + "websiteUrl" = "manifest"->'application'->>'websiteUrl', + "aboutDescription" = "manifest"->'application'->>'aboutDescription', + "termsUrl" = "manifest"->'application'->>'termsUrl', + "emailSupport" = "manifest"->'application'->>'emailSupport', + "issueReportUrl" = "manifest"->'application'->>'issueReportUrl', + "screenshots" = COALESCE(ARRAY(SELECT jsonb_array_elements_text("manifest"->'application'->'screenshots')), '{}') + WHERE "manifest" IS NOT NULL`, + ); + } + + public async up(_queryRunner: QueryRunner): Promise {} + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `UPDATE "core"."applicationRegistration" + SET + "description" = NULL, + "author" = NULL, + "category" = NULL, + "websiteUrl" = NULL, + "aboutDescription" = NULL, + "termsUrl" = NULL, + "emailSupport" = NULL, + "issueReportUrl" = NULL, + "screenshots" = '{}'`, + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts index 35be9d5411..4e5c100f26 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts @@ -94,6 +94,8 @@ import { AddWorkspaceDiscoverabilityToWorkspaceFastInstanceCommand } from './2-1 import { DropMetadataStandardOverridesColumnFastInstanceCommand } from './2-20/2-20-instance-command-fast-1825000000000-drop-metadata-standard-overrides-column'; import { AddLogoToApplicationRegistrationFastInstanceCommand } from './2-19/2-19-instance-command-fast-1783069672191-add-logo-to-application-registration'; import { BackfillLogoOnApplicationRegistrationSlowInstanceCommand } from './2-19/2-19-instance-command-slow-1783069673191-backfill-logo-on-application-registration'; +import { AddDisplayFieldsToApplicationRegistrationFastInstanceCommand } from './2-19/2-19-instance-command-fast-1783073776590-add-display-fields-to-application-registration'; +import { BackfillDisplayFieldsOnApplicationRegistrationSlowInstanceCommand } from './2-19/2-19-instance-command-slow-1783073776591-backfill-display-fields-on-application-registration'; export const INSTANCE_COMMANDS = [ AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand, @@ -190,4 +192,6 @@ export const INSTANCE_COMMANDS = [ AddTypeAndOptionsToApplicationVariablesFastInstanceCommand, AddLogoToApplicationRegistrationFastInstanceCommand, BackfillLogoOnApplicationRegistrationSlowInstanceCommand, + AddDisplayFieldsToApplicationRegistrationFastInstanceCommand, + BackfillDisplayFieldsOnApplicationRegistrationSlowInstanceCommand, ]; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace-query.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace-query.service.ts index 71711c4dd4..94440992b3 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace-query.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-marketplace/marketplace-query.service.ts @@ -93,7 +93,7 @@ export class MarketplaceQueryService { ): MarketplaceAppDTO { return { id: catalogCard.universalIdentifier, - name: catalogCard.displayName ?? catalogCard.name, + name: catalogCard.name, description: catalogCard.description ?? '', author: catalogCard.author ?? 'Unknown', category: catalogCard.category ?? '', diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.entity.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.entity.ts index 2a26f31c7b..0a56dbebe9 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.entity.ts @@ -141,6 +141,69 @@ export class ApplicationRegistrationEntity { }) logo: string | null; + @Column({ nullable: true, type: 'text' }) + @WasIntroducedInUpgrade({ + upgradeCommandName: + '2.19.0_AddDisplayFieldsToApplicationRegistrationFastInstanceCommand_1783073776590', + }) + description: string | null; + + @Column({ nullable: true, type: 'text' }) + @WasIntroducedInUpgrade({ + upgradeCommandName: + '2.19.0_AddDisplayFieldsToApplicationRegistrationFastInstanceCommand_1783073776590', + }) + author: string | null; + + @Column({ nullable: true, type: 'text' }) + @WasIntroducedInUpgrade({ + upgradeCommandName: + '2.19.0_AddDisplayFieldsToApplicationRegistrationFastInstanceCommand_1783073776590', + }) + category: string | null; + + @Column({ nullable: true, type: 'text' }) + @WasIntroducedInUpgrade({ + upgradeCommandName: + '2.19.0_AddDisplayFieldsToApplicationRegistrationFastInstanceCommand_1783073776590', + }) + websiteUrl: string | null; + + @Column({ nullable: true, type: 'text' }) + @WasIntroducedInUpgrade({ + upgradeCommandName: + '2.19.0_AddDisplayFieldsToApplicationRegistrationFastInstanceCommand_1783073776590', + }) + aboutDescription: string | null; + + @Column({ nullable: true, type: 'text' }) + @WasIntroducedInUpgrade({ + upgradeCommandName: + '2.19.0_AddDisplayFieldsToApplicationRegistrationFastInstanceCommand_1783073776590', + }) + termsUrl: string | null; + + @Column({ nullable: true, type: 'text' }) + @WasIntroducedInUpgrade({ + upgradeCommandName: + '2.19.0_AddDisplayFieldsToApplicationRegistrationFastInstanceCommand_1783073776590', + }) + emailSupport: string | null; + + @Column({ nullable: true, type: 'text' }) + @WasIntroducedInUpgrade({ + upgradeCommandName: + '2.19.0_AddDisplayFieldsToApplicationRegistrationFastInstanceCommand_1783073776590', + }) + issueReportUrl: string | null; + + @Column({ type: 'text', array: true, default: '{}' }) + @WasIntroducedInUpgrade({ + upgradeCommandName: + '2.19.0_AddDisplayFieldsToApplicationRegistrationFastInstanceCommand_1783073776590', + }) + screenshots: string[]; + @Field(() => String, { nullable: true }) get logoUrl(): string | null { return this.logo ?? this.manifest?.application?.logoUrl ?? null; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.service.ts index 5fc8905004..b172acc23f 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.service.ts @@ -25,6 +25,7 @@ import { type UpdateApplicationRegistrationPayload, } 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 { fromManifestApplicationToDisplayFields } from 'src/engine/core-modules/application/application-registration/utils/from-manifest-application-to-display-fields.util'; import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { validateRedirectUri } from 'src/engine/core-modules/auth/utils/validate-redirect-uri.util'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; @@ -51,6 +52,15 @@ const APPLICATION_REGISTRATION_WITHOUT_MANIFEST_SELECT: (keyof ApplicationRegist 'isFeatured', 'isPreInstalled', 'logo', + 'description', + 'author', + 'category', + 'websiteUrl', + 'aboutDescription', + 'termsUrl', + 'emailSupport', + 'issueReportUrl', + 'screenshots', 'createdAt', 'updatedAt', ]; @@ -61,7 +71,6 @@ export type ApplicationRegistrationCatalogCard = { name: string; sourcePackage: string | null; isFeatured: boolean; - displayName: string | null; description: string | null; author: string | null; category: string | null; @@ -147,7 +156,7 @@ export class ApplicationRegistrationService { ): Promise { const registration = await this.applicationRegistrationRepository.findOne({ where: { oAuthClientId: clientId }, - select: ['id', 'name', 'manifest', 'oAuthScopes'], + select: ['id', 'name', 'logo', 'websiteUrl', 'oAuthScopes'], }); if (!registration) { @@ -157,8 +166,8 @@ export class ApplicationRegistrationService { return { id: registration.id, name: registration.name, - logoUrl: registration.manifest?.application?.logoUrl ?? null, - websiteUrl: registration.manifest?.application?.websiteUrl ?? null, + logoUrl: registration.logo, + websiteUrl: registration.websiteUrl, oAuthScopes: registration.oAuthScopes, }; } @@ -290,7 +299,7 @@ export class ApplicationRegistrationService { ...existing, name: manifest.application.displayName, manifest, - logo: manifest.application.logoUrl ?? null, + ...fromManifestApplicationToDisplayFields(manifest.application), ...(sourceType !== undefined && { sourceType }), }); } @@ -360,7 +369,7 @@ export class ApplicationRegistrationService { sourcePackage: params.sourcePackage, latestAvailableVersion: params.latestAvailableVersion, manifest: params.manifest, - logo: params.manifest?.application?.logoUrl ?? null, + ...fromManifestApplicationToDisplayFields(params.manifest?.application), isFeatured, }); } else { @@ -373,7 +382,7 @@ export class ApplicationRegistrationService { isListed: true, isFeatured, manifest: params.manifest, - logo: params.manifest?.application?.logoUrl ?? null, + ...fromManifestApplicationToDisplayFields(params.manifest?.application), oAuthClientId: v4(), oAuthRedirectUris: [], oAuthScopes: [], @@ -430,6 +439,17 @@ export class ApplicationRegistrationService { ApplicationRegistrationCatalogCard[] > { const registrations = await this.applicationRegistrationRepository.find({ + select: [ + 'id', + 'universalIdentifier', + 'name', + 'sourcePackage', + 'isFeatured', + 'logo', + 'description', + 'author', + 'category', + ], where: { isListed: true, sourceType: ApplicationRegistrationSourceType.NPM, @@ -442,14 +462,10 @@ export class ApplicationRegistrationService { name: registration.name, sourcePackage: registration.sourcePackage, isFeatured: registration.isFeatured, - displayName: registration.manifest?.application?.displayName ?? null, - description: registration.manifest?.application?.description ?? null, - author: registration.manifest?.application?.author ?? null, - category: registration.manifest?.application?.category ?? null, - logoUrl: - registration.logo ?? - registration.manifest?.application?.logoUrl ?? - null, + description: registration.description, + author: registration.author, + category: registration.category, + logoUrl: registration.logo, })); } diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-tarball.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-tarball.service.ts index be0b9bc126..b12fc717a6 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-tarball.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-tarball.service.ts @@ -18,6 +18,7 @@ import { ApplicationRegistrationExceptionCode, } from 'src/engine/core-modules/application/application-registration/application-registration.exception'; import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum'; +import { fromManifestApplicationToDisplayFields } from 'src/engine/core-modules/application/application-registration/utils/from-manifest-application-to-display-fields.util'; import { extractTarballSecurely } from 'src/engine/core-modules/application/application-package/utils/extract-tarball-securely.util'; import { readJsonFile } from 'src/engine/core-modules/application/application-package/utils/read-json-file.util'; import { resolvePackageContentDir } from 'src/engine/core-modules/application/application-package/utils/tarball-utils'; @@ -170,6 +171,7 @@ export class ApplicationTarballService { name: manifest.application?.displayName ?? 'Unknown App', sourceType: ApplicationRegistrationSourceType.TARBALL, manifest, + ...fromManifestApplicationToDisplayFields(manifest.application), latestAvailableVersion: packageJson?.version ?? null, isListed: false, isFeatured: false, @@ -207,6 +209,7 @@ export class ApplicationTarballService { tarballFileId: savedFile.id, name: manifest.application?.displayName ?? 'Unknown App', manifest, + ...fromManifestApplicationToDisplayFields(manifest.application), latestAvailableVersion: packageJson?.version ?? null, isListed: false, isFeatured: false, diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/utils/from-manifest-application-to-display-fields.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/utils/from-manifest-application-to-display-fields.util.ts new file mode 100644 index 0000000000..31ff997b3d --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/utils/from-manifest-application-to-display-fields.util.ts @@ -0,0 +1,16 @@ +import { type ApplicationManifest } from 'twenty-shared/application'; + +export const fromManifestApplicationToDisplayFields = ( + application: ApplicationManifest | undefined, +) => ({ + logo: application?.logoUrl ?? null, + description: application?.description ?? null, + author: application?.author ?? null, + category: application?.category ?? null, + websiteUrl: application?.websiteUrl ?? null, + aboutDescription: application?.aboutDescription ?? null, + termsUrl: application?.termsUrl ?? null, + emailSupport: application?.emailSupport ?? null, + issueReportUrl: application?.issueReportUrl ?? null, + screenshots: application?.screenshots ?? [], +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/application/marketplace-catalog-sync.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/application/marketplace-catalog-sync.integration-spec.ts index 6a2532fbb3..d4401b6f0c 100644 --- a/packages/twenty-server/test/integration/metadata/suites/application/marketplace-catalog-sync.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/application/marketplace-catalog-sync.integration-spec.ts @@ -68,6 +68,7 @@ describe('Marketplace Catalog Sync (integration)', () => { sourcePackage: string; latestAvailableVersion?: string; manifest?: Record; + category?: string; }): Promise => { const id = crypto.randomUUID(); const oAuthClientId = crypto.randomUUID(); @@ -77,8 +78,8 @@ describe('Marketplace Catalog Sync (integration)', () => { (id, "universalIdentifier", name, "oAuthClientId", "oAuthRedirectUris", "oAuthScopes", "workspaceId", "sourceType", "sourcePackage", "latestAvailableVersion", - "manifest", "isListed") - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, + "manifest", "isListed", "category") + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`, [ id, params.universalIdentifier, @@ -92,6 +93,7 @@ describe('Marketplace Catalog Sync (integration)', () => { params.latestAvailableVersion ?? '1.0.0', params.manifest ? JSON.stringify(params.manifest) : null, true, + params.category ?? null, ], ); @@ -146,6 +148,7 @@ describe('Marketplace Catalog Sync (integration)', () => { category: 'Data', }, }, + category: 'Data', }); });