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:
martmull
2026-07-08 17:43:17 +02:00
committed by GitHub
parent b1dee7cf26
commit 9423af7f67
17 changed files with 292 additions and 60 deletions
@@ -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,