feat(server): promote registration display fields to first-class columns (#22513)
Part of the application settings architecture work: https://github.com/twentyhq/core-team-issues/issues/2456 — follow-up to #22453, delivering the promised removal of the temporary manifest load. Display data (description, author, category, websiteUrl, aboutDescription, termsUrl, emailSupport, issueReportUrl, screenshots) only existed inside the `manifest` jsonb, forcing hot paths to load it. This PR: - Promotes those 9 fields to first-class columns on `applicationRegistration`, populated at every ingestion point (`updateFromManifest`, both `upsertFromCatalog` branches) — fast command creates the columns at deploy, slow command backfills them from the manifest. - `findManyListedCatalogCards()` (marketplace list) now selects only scalar columns — the manifest jsonb is no longer loaded there. - `findPublicByClientId()` (OAuth consent page) now selects `id, name, logo, websiteUrl, oAuthScopes` — no manifest. - The narrow select used by `findMany`/`findAll`/`findOneById`/`findOneByIdGlobal` includes the new columns. - GraphQL surface unchanged (no new fields); the marketplace detail endpoint still reads the manifest and is slimmed in the next PR. Verified: migration applied via the real runner (both commands recorded completed), backfill SQL exercised against live rows (full + minimal manifests), migration generator reports no pending schema changes, typecheck, lint, unit suites (application-registration + marketplace 15/15). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_011sST4rPLU1Koi2oVGi84ei --- _Generated by [Claude Code](https://claude.ai/code/session_011sST4rPLU1Koi2oVGi84ei)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22513?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. -->
This commit is contained in:
+1
-1
@@ -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 ?? '',
|
||||
|
||||
+63
@@ -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;
|
||||
|
||||
+31
-15
@@ -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<PublicApplicationRegistrationDTO | null> {
|
||||
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,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -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,
|
||||
|
||||
+16
@@ -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 ?? [],
|
||||
});
|
||||
Reference in New Issue
Block a user