Resolve application registration logo and gallery image urls at query time (#22827)
## Context
Application registration logo and gallery image urls were baked into the
stored manifest and display columns at write time, with each source flow
doing it differently: npm catalog sync baked CDN urls, local dev sync
baked `public-assets` urls, and tarball uploads left raw manifest paths
that never displayed in the UI. The entity also carried a `logoUrl`
getter computed field.
This moves url generation to query time, the same way the workspace logo
works.
## What changed
**Read side**
- `ApplicationRegistrationAssetUrlService` builds display urls when
queried: stored files are served by fileId, absolute urls pass through
untouched, and not-yet-rehosted npm assets fall back to the registry CDN
from `sourcePackage@latestAvailableVersion`.
- The `logoUrl` getter on `ApplicationRegistrationEntity` is replaced by
`logoUrl` and `galleryImages` `@ResolveField`s on the metadata resolver,
the admin panel resolver, and a new resolver for
`ApplicationRegistrationSummary` (used by
`Application.applicationRegistration`).
- The marketplace detail/card DTOs and the public OAuth authorize DTO
(`findApplicationRegistrationByClientId`) go through the same url
builder.
- New public route `GET /file/application-registration/:id` streams
registration server files (these are instance-global marketplace assets,
also shown on the public OAuth authorize page).
`ServerFileStorageService.readServerFileById` now returns the mime type
alongside the stream.
**Write side**
- New `logoFileId` column on `applicationRegistration` (2.21 fast
instance command, constraint names match TypeORM naming), complementing
the fileIds already stored in the `galleryImages` jsonb.
- `ApplicationRegistrationAssetService` copies the manifest logo and
gallery images into instance-global server file storage, so all three
sources behave the same:
- **TARBALL**: from the uploaded package (previously only gallery images
were stored, never the logo).
- **LOCAL**: dev sync reads the already-uploaded public assets from
workspace storage (the CLI uploads files before syncing).
- **NPM**: catalog sync downloads the assets from the registry CDN.
Downloads are skipped when the package version is unchanged and the
files are already stored; failed or pending downloads fall back to CDN
urls at query time.
- Write-time url rewriting is removed
(`ManifestAssetUrlResolverService`, `resolveManifestAssetUrls`);
manifests now keep raw asset paths. Existing rows with baked absolute
urls keep working through the absolute-url passthrough, so no backfill
is needed.
- `updateFromManifest` and `upsertFromCatalog` preserve stored gallery
fileIds for unchanged paths, so installs and the hourly catalog sync no
longer clobber them.
## How it was verified
Against a local Postgres/Redis with the server running:
- Fresh database init runs the new instance command; column and FK/UQ
constraint names match TypeORM's generated names, and the CI
pending-migration check produces no diff.
- `findManyApplicationRegistrations { logoUrl galleryImages }` returns
fileId-served urls for a TARBALL registration (absolute urls passed
through), and null/[] for a LOCAL registration without assets.
- Ran `marketplace:catalog-sync` against the real npm registry: 14
packages synced, logos and gallery images rehosted from unpkg with
fileIds set; a second run re-downloaded nothing (version-unchanged
skip); `findMarketplaceAppDetail` for `twenty-linear` returns
fileId-served urls for the logo and all four gallery images.
- `GET /file/application-registration/:id` serves stored files with the
right content type (png and svg verified), 404s on unknown ids, and the
token-guarded generic `/file/:folder/:id` route still returns 403
without a token.
- Unit tests for the url builder and the assets-stored check; server
unit test suites for the application module pass; typecheck and lint
clean.
This commit is contained in:
+23
@@ -1,11 +1,16 @@
|
||||
import { Context, Parent, ResolveField } from '@nestjs/graphql';
|
||||
|
||||
import { AdminResolver } from 'src/engine/api/graphql/graphql-config/decorators/admin-resolver.decorator';
|
||||
import { ApplicationRegistrationAssetUrlService } from 'src/engine/core-modules/application/application-registration/application-registration-asset-url.service';
|
||||
import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity';
|
||||
import { type IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
|
||||
|
||||
@AdminResolver(() => ApplicationRegistrationEntity)
|
||||
export class AdminPanelApplicationRegistrationResolver {
|
||||
constructor(
|
||||
private readonly applicationRegistrationAssetUrlService: ApplicationRegistrationAssetUrlService,
|
||||
) {}
|
||||
|
||||
@ResolveField(() => Boolean)
|
||||
async isConfigured(
|
||||
@Parent() registration: ApplicationRegistrationEntity,
|
||||
@@ -15,4 +20,22 @@ export class AdminPanelApplicationRegistrationResolver {
|
||||
applicationRegistrationId: registration.id,
|
||||
});
|
||||
}
|
||||
|
||||
@ResolveField(() => String, { nullable: true })
|
||||
logoUrl(
|
||||
@Parent() registration: ApplicationRegistrationEntity,
|
||||
): string | null {
|
||||
return this.applicationRegistrationAssetUrlService.buildLogoUrl(
|
||||
registration,
|
||||
);
|
||||
}
|
||||
|
||||
@ResolveField(() => [String])
|
||||
galleryImagesUrls(
|
||||
@Parent() registration: ApplicationRegistrationEntity,
|
||||
): string[] {
|
||||
return this.applicationRegistrationAssetUrlService.buildGalleryImageUrls(
|
||||
registration,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user