feat(server): add public marketplace resolver for vetted app catalog (#22647)
## What Adds a public GraphQL resolver so unauthenticated clients (the public website) can read the listed/vetted marketplace catalog without a workspace token. - `MarketplacePublicResolver` (metadata schema) exposes two public queries guarded by `PublicEndpointGuard` + `NoPermissionGuard`: - `publicMarketplaceApps` - `publicMarketplaceAppDetail(universalIdentifier)` Both delegate to the existing `MarketplaceQueryService` (no new logic, no new REST routing). The existing workspace-guarded `findManyMarketplaceApps` / `findMarketplaceAppDetail` queries are untouched. - Adds a shared `ApplicationCategory` type in `twenty-shared` (known values plus `string` for backward compatibility) used to type `ApplicationManifest.category`. A warning is logged server-side when an app declares a category outside the known set. ## Why This is the backend half of the public apps marketplace on the website. Splitting it out so the server-side catalog exposure can be reviewed independently from the website UI. ## Follow-up The website PR (the `/apps` marketplace UI) consumes `publicMarketplaceApps` and should merge after this one. --- _Generated by [Claude Code](https://claude.ai/code/session_01GBfegArtJcoiTLSsnWPH8R)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22647?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: martmull <martin@twenty.com>
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
export const APPLICATION_CATEGORIES = [
|
||||
'Communication',
|
||||
'Productivity',
|
||||
'Product management',
|
||||
'Sales',
|
||||
'Marketing',
|
||||
'Enrichment',
|
||||
'Data',
|
||||
'Search',
|
||||
'Other',
|
||||
] as const;
|
||||
|
||||
export type KnownApplicationCategory = (typeof APPLICATION_CATEGORIES)[number];
|
||||
|
||||
export type ApplicationCategory = KnownApplicationCategory | (string & {});
|
||||
|
||||
export const isKnownApplicationCategory = (
|
||||
category: string,
|
||||
): category is KnownApplicationCategory =>
|
||||
(APPLICATION_CATEGORIES as readonly string[]).includes(category);
|
||||
@@ -1,3 +1,4 @@
|
||||
import { type ApplicationCategory } from './applicationCategoryType';
|
||||
import { type ApplicationVariables } from './applicationVariablesType';
|
||||
import { type ServerVariables } from './server-variables.type';
|
||||
import { type SyncableEntityOptions } from './syncableEntityOptionsType';
|
||||
@@ -11,7 +12,7 @@ export type ApplicationManifest = SyncableEntityOptions & {
|
||||
applicationVariables?: ApplicationVariables;
|
||||
serverVariables?: ServerVariables;
|
||||
author?: string;
|
||||
category?: string;
|
||||
category?: ApplicationCategory;
|
||||
logoUrl?: string;
|
||||
screenshots?: string[];
|
||||
aboutDescription?: string;
|
||||
|
||||
@@ -9,6 +9,14 @@
|
||||
|
||||
export type { AgentManifest } from './agentManifestType';
|
||||
export type { AppConnection } from './appConnectionType';
|
||||
export type {
|
||||
KnownApplicationCategory,
|
||||
ApplicationCategory,
|
||||
} from './applicationCategoryType';
|
||||
export {
|
||||
APPLICATION_CATEGORIES,
|
||||
isKnownApplicationCategory,
|
||||
} from './applicationCategoryType';
|
||||
export type { ApplicationManifest } from './applicationType';
|
||||
export type {
|
||||
ApplicationVariableType,
|
||||
|
||||
Reference in New Issue
Block a user