mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
feat(den-api): add marketplaces for plugin grouping (#1484)
Introduce org-scoped marketplaces so teams can curate and share groups of plugins with consistent access rules. This adds the schema, admin routes, RBAC updates, and PRD coverage needed for marketplace-backed plugin catalogs. Co-authored-by: src-opn <src-opn@users.noreply.github.com>
This commit is contained in:
@@ -4,6 +4,9 @@ import {
|
||||
ConfigObjectTable,
|
||||
ConnectorInstanceAccessGrantTable,
|
||||
ConnectorInstanceTable,
|
||||
MarketplaceAccessGrantTable,
|
||||
MarketplacePluginTable,
|
||||
MarketplaceTable,
|
||||
PluginAccessGrantTable,
|
||||
PluginConfigObjectTable,
|
||||
PluginTable,
|
||||
@@ -12,9 +15,9 @@ import type { MemberTeamSummary, OrganizationContext } from "../../../orgs.js"
|
||||
import { db } from "../../../db.js"
|
||||
import { memberHasRole } from "../shared.js"
|
||||
|
||||
export type PluginArchResourceKind = "config_object" | "connector_instance" | "plugin"
|
||||
export type PluginArchResourceKind = "config_object" | "connector_instance" | "marketplace" | "plugin"
|
||||
export type PluginArchRole = "viewer" | "editor" | "manager"
|
||||
export type PluginArchCapability = "config_object.create" | "connector_account.create" | "connector_instance.create" | "plugin.create"
|
||||
export type PluginArchCapability = "config_object.create" | "connector_account.create" | "connector_instance.create" | "marketplace.create" | "plugin.create"
|
||||
|
||||
export type PluginArchActorContext = {
|
||||
memberTeams: MemberTeamSummary[]
|
||||
@@ -24,12 +27,20 @@ export type PluginArchActorContext = {
|
||||
type MemberId = OrganizationContext["currentMember"]["id"]
|
||||
type TeamId = MemberTeamSummary["id"]
|
||||
type ConfigObjectId = typeof ConfigObjectTable.$inferSelect.id
|
||||
type MarketplaceId = typeof MarketplaceTable.$inferSelect.id
|
||||
type PluginId = typeof PluginTable.$inferSelect.id
|
||||
type ConnectorInstanceId = typeof ConnectorInstanceTable.$inferSelect.id
|
||||
type ConfigObjectGrantRow = Pick<typeof ConfigObjectAccessGrantTable.$inferSelect, "orgMembershipId" | "orgWide" | "removedAt" | "role" | "teamId">
|
||||
type MarketplaceGrantRow = Pick<typeof MarketplaceAccessGrantTable.$inferSelect, "orgMembershipId" | "orgWide" | "removedAt" | "role" | "teamId">
|
||||
type PluginGrantRow = Pick<typeof PluginAccessGrantTable.$inferSelect, "orgMembershipId" | "orgWide" | "removedAt" | "role" | "teamId">
|
||||
type ConnectorInstanceGrantRow = Pick<typeof ConnectorInstanceAccessGrantTable.$inferSelect, "orgMembershipId" | "orgWide" | "removedAt" | "role" | "teamId">
|
||||
type GrantRow = ConfigObjectGrantRow | PluginGrantRow | ConnectorInstanceGrantRow
|
||||
type GrantRow = ConfigObjectGrantRow | MarketplaceGrantRow | PluginGrantRow | ConnectorInstanceGrantRow
|
||||
|
||||
type MarketplaceResourceLookupInput = {
|
||||
context: PluginArchActorContext
|
||||
resourceId: MarketplaceId
|
||||
resourceKind: "marketplace"
|
||||
}
|
||||
|
||||
type PluginResourceLookupInput = {
|
||||
context: PluginArchActorContext
|
||||
@@ -52,6 +63,7 @@ type ConfigObjectResourceLookupInput = {
|
||||
type ResourceLookupInput =
|
||||
| PluginResourceLookupInput
|
||||
| ConnectorInstanceResourceLookupInput
|
||||
| MarketplaceResourceLookupInput
|
||||
| ConfigObjectResourceLookupInput
|
||||
|
||||
type RequireResourceRoleInput = ResourceLookupInput & { role: PluginArchRole }
|
||||
@@ -144,11 +156,48 @@ async function resolvePluginRoleForIds(context: PluginArchActorContext, pluginId
|
||||
return resolveGrantRole({ context, grants })
|
||||
}
|
||||
|
||||
async function resolveMarketplaceRoleForIds(context: PluginArchActorContext, marketplaceIds: MarketplaceId[]) {
|
||||
if (marketplaceIds.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (isPluginArchOrgAdmin(context)) {
|
||||
return "manager" satisfies PluginArchRole
|
||||
}
|
||||
|
||||
const grants = await db
|
||||
.select({
|
||||
orgMembershipId: MarketplaceAccessGrantTable.orgMembershipId,
|
||||
orgWide: MarketplaceAccessGrantTable.orgWide,
|
||||
removedAt: MarketplaceAccessGrantTable.removedAt,
|
||||
role: MarketplaceAccessGrantTable.role,
|
||||
teamId: MarketplaceAccessGrantTable.teamId,
|
||||
})
|
||||
.from(MarketplaceAccessGrantTable)
|
||||
.where(inArray(MarketplaceAccessGrantTable.marketplaceId, marketplaceIds))
|
||||
|
||||
return resolveGrantRole({ context, grants })
|
||||
}
|
||||
|
||||
export async function resolvePluginArchResourceRole(input: ResourceLookupInput) {
|
||||
if (isPluginArchOrgAdmin(input.context)) {
|
||||
return "manager" satisfies PluginArchRole
|
||||
}
|
||||
|
||||
if (input.resourceKind === "marketplace") {
|
||||
const grants = await db
|
||||
.select({
|
||||
orgMembershipId: MarketplaceAccessGrantTable.orgMembershipId,
|
||||
orgWide: MarketplaceAccessGrantTable.orgWide,
|
||||
removedAt: MarketplaceAccessGrantTable.removedAt,
|
||||
role: MarketplaceAccessGrantTable.role,
|
||||
teamId: MarketplaceAccessGrantTable.teamId,
|
||||
})
|
||||
.from(MarketplaceAccessGrantTable)
|
||||
.where(eq(MarketplaceAccessGrantTable.marketplaceId, input.resourceId))
|
||||
return resolveGrantRole({ context: input.context, grants })
|
||||
}
|
||||
|
||||
if (input.resourceKind === "plugin") {
|
||||
const grants = await db
|
||||
.select({
|
||||
@@ -160,7 +209,18 @@ export async function resolvePluginArchResourceRole(input: ResourceLookupInput)
|
||||
})
|
||||
.from(PluginAccessGrantTable)
|
||||
.where(eq(PluginAccessGrantTable.pluginId, input.resourceId))
|
||||
return resolveGrantRole({ context: input.context, grants })
|
||||
const resolved = await resolveGrantRole({ context: input.context, grants })
|
||||
if (resolved) {
|
||||
return resolved
|
||||
}
|
||||
|
||||
const memberships = await db
|
||||
.select({ marketplaceId: MarketplacePluginTable.marketplaceId })
|
||||
.from(MarketplacePluginTable)
|
||||
.where(and(eq(MarketplacePluginTable.pluginId, input.resourceId), isNull(MarketplacePluginTable.removedAt)))
|
||||
|
||||
const marketplaceRole = await resolveMarketplaceRoleForIds(input.context, memberships.map((membership) => membership.marketplaceId))
|
||||
return maxRole(resolved, marketplaceRole ? "viewer" : null)
|
||||
}
|
||||
|
||||
if (input.resourceKind === "connector_instance") {
|
||||
@@ -213,7 +273,7 @@ export async function requirePluginArchCapability(context: PluginArchActorContex
|
||||
|
||||
export async function requirePluginArchResourceRole(input: {
|
||||
context: PluginArchActorContext
|
||||
resourceId: ConfigObjectId | ConnectorInstanceId | PluginId
|
||||
resourceId: ConfigObjectId | ConnectorInstanceId | MarketplaceId | PluginId
|
||||
resourceKind: PluginArchResourceKind
|
||||
role: PluginArchRole
|
||||
}) {
|
||||
|
||||
@@ -61,6 +61,18 @@ import {
|
||||
githubWebhookIgnoredResponseSchema,
|
||||
githubWebhookRawBodySchema,
|
||||
githubWebhookUnauthorizedResponseSchema,
|
||||
marketplaceAccessGrantParamsSchema,
|
||||
marketplaceCreateSchema,
|
||||
marketplaceDetailResponseSchema,
|
||||
marketplaceListQuerySchema,
|
||||
marketplaceListResponseSchema,
|
||||
marketplaceMutationResponseSchema,
|
||||
marketplaceParamsSchema,
|
||||
marketplacePluginListResponseSchema,
|
||||
marketplacePluginMutationResponseSchema,
|
||||
marketplacePluginParamsSchema,
|
||||
marketplacePluginWriteSchema,
|
||||
marketplaceUpdateSchema,
|
||||
pluginAccessGrantParamsSchema,
|
||||
pluginConfigObjectParamsSchema,
|
||||
pluginCreateSchema,
|
||||
@@ -79,7 +91,7 @@ import { orgIdParamSchema } from "../shared.js"
|
||||
|
||||
type EndpointMethod = "DELETE" | "GET" | "PATCH" | "POST"
|
||||
type EndpointAudience = "admin" | "public_webhook"
|
||||
type EndpointTag = "Config Objects" | "Plugins" | "Connectors" | "GitHub" | "Webhooks"
|
||||
type EndpointTag = "Config Objects" | "Plugins" | "Marketplaces" | "Connectors" | "GitHub" | "Webhooks"
|
||||
|
||||
type EndpointContract = {
|
||||
audience: EndpointAudience
|
||||
@@ -139,6 +151,14 @@ export const pluginArchRoutePaths = {
|
||||
pluginReleases: `${orgBasePath}/plugins/:pluginId/releases`,
|
||||
pluginAccess: `${orgBasePath}/plugins/:pluginId/access`,
|
||||
pluginAccessGrant: `${orgBasePath}/plugins/:pluginId/access/:grantId`,
|
||||
marketplaces: `${orgBasePath}/marketplaces`,
|
||||
marketplace: `${orgBasePath}/marketplaces/:marketplaceId`,
|
||||
marketplaceArchive: `${orgBasePath}/marketplaces/:marketplaceId/archive`,
|
||||
marketplaceRestore: `${orgBasePath}/marketplaces/:marketplaceId/restore`,
|
||||
marketplacePlugins: `${orgBasePath}/marketplaces/:marketplaceId/plugins`,
|
||||
marketplacePlugin: `${orgBasePath}/marketplaces/:marketplaceId/plugins/:pluginId`,
|
||||
marketplaceAccess: `${orgBasePath}/marketplaces/:marketplaceId/access`,
|
||||
marketplaceAccessGrant: `${orgBasePath}/marketplaces/:marketplaceId/access/:grantId`,
|
||||
connectorAccounts: `${orgBasePath}/connector-accounts`,
|
||||
connectorAccount: `${orgBasePath}/connector-accounts/:connectorAccountId`,
|
||||
connectorAccountDisconnect: `${orgBasePath}/connector-accounts/:connectorAccountId/disconnect`,
|
||||
@@ -427,6 +447,114 @@ export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
|
||||
response: { description: "Plugin access grant revoked successfully.", status: 204 },
|
||||
tag: "Plugins",
|
||||
},
|
||||
listMarketplaces: {
|
||||
audience: "admin",
|
||||
description: "List accessible marketplaces for the organization.",
|
||||
method: "GET",
|
||||
path: pluginArchRoutePaths.marketplaces,
|
||||
request: { params: orgIdParamSchema, query: marketplaceListQuerySchema },
|
||||
response: { description: "Marketplace list.", schema: marketplaceListResponseSchema, status: 200 },
|
||||
tag: "Marketplaces",
|
||||
},
|
||||
getMarketplace: {
|
||||
audience: "admin",
|
||||
description: "Get one marketplace and its current metadata.",
|
||||
method: "GET",
|
||||
path: pluginArchRoutePaths.marketplace,
|
||||
request: { params: marketplaceParamsSchema },
|
||||
response: { description: "Marketplace detail.", schema: marketplaceDetailResponseSchema, status: 200 },
|
||||
tag: "Marketplaces",
|
||||
},
|
||||
createMarketplace: {
|
||||
audience: "admin",
|
||||
description: "Create a private-by-default marketplace.",
|
||||
method: "POST",
|
||||
path: pluginArchRoutePaths.marketplaces,
|
||||
request: { body: marketplaceCreateSchema, params: orgIdParamSchema },
|
||||
response: { description: "Marketplace created successfully.", schema: marketplaceMutationResponseSchema, status: 201 },
|
||||
tag: "Marketplaces",
|
||||
},
|
||||
updateMarketplace: {
|
||||
audience: "admin",
|
||||
description: "Patch marketplace metadata.",
|
||||
method: "PATCH",
|
||||
path: pluginArchRoutePaths.marketplace,
|
||||
request: { body: marketplaceUpdateSchema, params: marketplaceParamsSchema },
|
||||
response: { description: "Marketplace updated successfully.", schema: marketplaceMutationResponseSchema, status: 200 },
|
||||
tag: "Marketplaces",
|
||||
},
|
||||
archiveMarketplace: {
|
||||
audience: "admin",
|
||||
description: "Archive a marketplace without deleting membership history.",
|
||||
method: "POST",
|
||||
path: pluginArchRoutePaths.marketplaceArchive,
|
||||
request: { params: marketplaceParamsSchema },
|
||||
response: { description: "Archived marketplace detail.", schema: marketplaceMutationResponseSchema, status: 200 },
|
||||
tag: "Marketplaces",
|
||||
},
|
||||
restoreMarketplace: {
|
||||
audience: "admin",
|
||||
description: "Restore an archived or deleted marketplace.",
|
||||
method: "POST",
|
||||
path: pluginArchRoutePaths.marketplaceRestore,
|
||||
request: { params: marketplaceParamsSchema },
|
||||
response: { description: "Restored marketplace detail.", schema: marketplaceMutationResponseSchema, status: 200 },
|
||||
tag: "Marketplaces",
|
||||
},
|
||||
listMarketplacePlugins: {
|
||||
audience: "admin",
|
||||
description: "List marketplace memberships and the plugins they reference.",
|
||||
method: "GET",
|
||||
path: pluginArchRoutePaths.marketplacePlugins,
|
||||
request: { params: marketplaceParamsSchema },
|
||||
response: { description: "Marketplace plugin memberships.", schema: marketplacePluginListResponseSchema, status: 200 },
|
||||
tag: "Marketplaces",
|
||||
},
|
||||
addMarketplacePlugin: {
|
||||
audience: "admin",
|
||||
description: "Add a plugin to a marketplace using marketplace-scoped write access.",
|
||||
method: "POST",
|
||||
path: pluginArchRoutePaths.marketplacePlugins,
|
||||
request: { body: marketplacePluginWriteSchema, params: marketplaceParamsSchema },
|
||||
response: { description: "Marketplace plugin membership created successfully.", schema: marketplacePluginMutationResponseSchema, status: 201 },
|
||||
tag: "Marketplaces",
|
||||
},
|
||||
removeMarketplacePlugin: {
|
||||
audience: "admin",
|
||||
description: "Remove one plugin membership from a marketplace.",
|
||||
method: "DELETE",
|
||||
path: pluginArchRoutePaths.marketplacePlugin,
|
||||
request: { params: marketplacePluginParamsSchema },
|
||||
response: { description: "Marketplace plugin membership removed successfully.", status: 204 },
|
||||
tag: "Marketplaces",
|
||||
},
|
||||
listMarketplaceAccess: {
|
||||
audience: "admin",
|
||||
description: "List direct, team, and org-wide grants for a marketplace.",
|
||||
method: "GET",
|
||||
path: pluginArchRoutePaths.marketplaceAccess,
|
||||
request: { params: marketplaceParamsSchema },
|
||||
response: { description: "Marketplace access grants.", schema: accessGrantListResponseSchema, status: 200 },
|
||||
tag: "Marketplaces",
|
||||
},
|
||||
grantMarketplaceAccess: {
|
||||
audience: "admin",
|
||||
description: "Create one direct, team, or org-wide access grant for a marketplace.",
|
||||
method: "POST",
|
||||
path: pluginArchRoutePaths.marketplaceAccess,
|
||||
request: { body: resourceAccessGrantWriteSchema, params: marketplaceParamsSchema },
|
||||
response: { description: "Marketplace access grant created successfully.", schema: accessGrantMutationResponseSchema, status: 201 },
|
||||
tag: "Marketplaces",
|
||||
},
|
||||
revokeMarketplaceAccess: {
|
||||
audience: "admin",
|
||||
description: "Soft-revoke one marketplace access grant.",
|
||||
method: "DELETE",
|
||||
path: pluginArchRoutePaths.marketplaceAccessGrant,
|
||||
request: { params: marketplaceAccessGrantParamsSchema },
|
||||
response: { description: "Marketplace access grant revoked successfully.", status: 204 },
|
||||
tag: "Marketplaces",
|
||||
},
|
||||
listConnectorAccounts: {
|
||||
audience: "admin",
|
||||
description: "List connector accounts such as GitHub App installations available to the org.",
|
||||
|
||||
@@ -60,6 +60,18 @@ import {
|
||||
githubConnectorSetupSchema,
|
||||
githubValidateTargetResponseSchema,
|
||||
githubValidateTargetSchema,
|
||||
marketplaceAccessGrantParamsSchema,
|
||||
marketplaceCreateSchema,
|
||||
marketplaceDetailResponseSchema,
|
||||
marketplaceListQuerySchema,
|
||||
marketplaceListResponseSchema,
|
||||
marketplaceMutationResponseSchema,
|
||||
marketplaceParamsSchema,
|
||||
marketplacePluginListResponseSchema,
|
||||
marketplacePluginMutationResponseSchema,
|
||||
marketplacePluginParamsSchema,
|
||||
marketplacePluginWriteSchema,
|
||||
marketplaceUpdateSchema,
|
||||
pluginAccessGrantParamsSchema,
|
||||
pluginCreateSchema,
|
||||
pluginDetailResponseSchema,
|
||||
@@ -85,6 +97,7 @@ import {
|
||||
createConnectorInstance,
|
||||
createConnectorMapping,
|
||||
createGithubConnectorAccount,
|
||||
createMarketplace,
|
||||
createPlugin,
|
||||
createResourceAccessGrant,
|
||||
createConnectorTarget,
|
||||
@@ -98,6 +111,7 @@ import {
|
||||
getConnectorSyncEventDetail,
|
||||
getConnectorTargetDetail,
|
||||
getLatestConfigObjectVersion,
|
||||
getMarketplaceDetail,
|
||||
getPluginDetail,
|
||||
githubSetup,
|
||||
listConfigObjectPlugins,
|
||||
@@ -109,19 +123,25 @@ import {
|
||||
listConnectorSyncEvents,
|
||||
listConnectorTargets,
|
||||
listGithubRepositories,
|
||||
listMarketplaceMemberships,
|
||||
listMarketplaces,
|
||||
listPluginMemberships,
|
||||
listPlugins,
|
||||
listResourceAccess,
|
||||
attachPluginToMarketplace,
|
||||
queueConnectorTargetResync,
|
||||
removeConfigObjectFromPlugin,
|
||||
removePluginFromMarketplace,
|
||||
removePluginMembership,
|
||||
retryConnectorSyncEvent,
|
||||
setConfigObjectLifecycle,
|
||||
setConnectorInstanceLifecycle,
|
||||
setMarketplaceLifecycle,
|
||||
setPluginLifecycle,
|
||||
updateConnectorInstance,
|
||||
updateConnectorMapping,
|
||||
updateConnectorTarget,
|
||||
updateMarketplace,
|
||||
updatePlugin,
|
||||
validateGithubTarget,
|
||||
} from "./store.js"
|
||||
@@ -807,6 +827,263 @@ export function registerPluginArchRoutes<T extends { Variables: OrgRouteVariable
|
||||
}
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "get", pluginArchRoutePaths.marketplaces,
|
||||
paramValidator(marketplaceParamsSchema.pick({ orgId: true })),
|
||||
queryValidator(marketplaceListQuerySchema),
|
||||
describeRoute({
|
||||
tags: ["Marketplaces"],
|
||||
summary: "List marketplaces",
|
||||
description: "Lists marketplaces visible to the current organization member.",
|
||||
responses: {
|
||||
200: jsonResponse("Marketplaces returned successfully.", marketplaceListResponseSchema),
|
||||
400: jsonResponse("The marketplace query parameters were invalid.", invalidRequestSchema),
|
||||
401: jsonResponse("The caller must be signed in to list marketplaces.", unauthorizedSchema),
|
||||
},
|
||||
}),
|
||||
async (c: OrgContext) => {
|
||||
const query = validQuery<any>(c)
|
||||
return c.json(await listMarketplaces({ context: actorContext(c), cursor: query.cursor, limit: query.limit, q: query.q, status: query.status }))
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "post", pluginArchRoutePaths.marketplaces,
|
||||
paramValidator(marketplaceParamsSchema.pick({ orgId: true })),
|
||||
jsonValidator(marketplaceCreateSchema),
|
||||
describeRoute({
|
||||
tags: ["Marketplaces"],
|
||||
summary: "Create marketplace",
|
||||
description: "Creates a new private marketplace and grants the creator manager access.",
|
||||
responses: {
|
||||
201: jsonResponse("Marketplace created successfully.", marketplaceMutationResponseSchema),
|
||||
400: jsonResponse("The marketplace creation request was invalid.", invalidRequestSchema),
|
||||
401: jsonResponse("The caller must be signed in to create marketplaces.", unauthorizedSchema),
|
||||
403: jsonResponse("The caller lacks permission to create marketplaces.", forbiddenSchema),
|
||||
},
|
||||
}),
|
||||
async (c: OrgContext) => {
|
||||
try {
|
||||
const context = actorContext(c)
|
||||
await requirePluginArchCapability(context, "marketplace.create")
|
||||
const body = validJson<any>(c)
|
||||
return c.json({ ok: true, item: await createMarketplace({ context, description: body.description, name: body.name }) }, 201)
|
||||
} catch (error) {
|
||||
return routeErrorResponse(c, error)
|
||||
}
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "get", pluginArchRoutePaths.marketplace,
|
||||
paramValidator(marketplaceParamsSchema),
|
||||
describeRoute({
|
||||
tags: ["Marketplaces"],
|
||||
summary: "Get marketplace",
|
||||
description: "Returns one marketplace detail when the caller can view it.",
|
||||
responses: {
|
||||
200: jsonResponse("Marketplace returned successfully.", marketplaceDetailResponseSchema),
|
||||
400: jsonResponse("The marketplace path parameters were invalid.", invalidRequestSchema),
|
||||
401: jsonResponse("The caller must be signed in to view marketplaces.", unauthorizedSchema),
|
||||
404: jsonResponse("The marketplace could not be found.", notFoundSchema),
|
||||
},
|
||||
}),
|
||||
async (c: OrgContext) => {
|
||||
try {
|
||||
const params = validParam<any>(c)
|
||||
return c.json({ item: await getMarketplaceDetail(actorContext(c), params.marketplaceId) })
|
||||
} catch (error) {
|
||||
return routeErrorResponse(c, error)
|
||||
}
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "patch", pluginArchRoutePaths.marketplace,
|
||||
paramValidator(marketplaceParamsSchema),
|
||||
jsonValidator(marketplaceUpdateSchema),
|
||||
describeRoute({
|
||||
tags: ["Marketplaces"],
|
||||
summary: "Update marketplace",
|
||||
description: "Updates marketplace metadata.",
|
||||
responses: {
|
||||
200: jsonResponse("Marketplace updated successfully.", marketplaceMutationResponseSchema),
|
||||
400: jsonResponse("The marketplace update request was invalid.", invalidRequestSchema),
|
||||
401: jsonResponse("The caller must be signed in to update marketplaces.", unauthorizedSchema),
|
||||
403: jsonResponse("The caller lacks permission to edit this marketplace.", forbiddenSchema),
|
||||
404: jsonResponse("The marketplace could not be found.", notFoundSchema),
|
||||
},
|
||||
}),
|
||||
async (c: OrgContext) => {
|
||||
try {
|
||||
const params = validParam<any>(c)
|
||||
const body = validJson<any>(c)
|
||||
return c.json({ ok: true, item: await updateMarketplace({ context: actorContext(c), description: body.description, marketplaceId: params.marketplaceId, name: body.name }) })
|
||||
} catch (error) {
|
||||
return routeErrorResponse(c, error)
|
||||
}
|
||||
})
|
||||
|
||||
for (const [path, action] of [[pluginArchRoutePaths.marketplaceArchive, "archive"], [pluginArchRoutePaths.marketplaceRestore, "restore"]] as const) {
|
||||
withPluginArchOrgContext(app, "post", path,
|
||||
paramValidator(marketplaceParamsSchema),
|
||||
describeRoute({
|
||||
tags: ["Marketplaces"],
|
||||
summary: `${action} marketplace`,
|
||||
description: `${action} a marketplace without touching membership history.`,
|
||||
responses: {
|
||||
200: jsonResponse("Marketplace lifecycle updated successfully.", marketplaceMutationResponseSchema),
|
||||
400: jsonResponse("The marketplace lifecycle path parameters were invalid.", invalidRequestSchema),
|
||||
401: jsonResponse("The caller must be signed in to manage marketplaces.", unauthorizedSchema),
|
||||
403: jsonResponse("The caller lacks permission to manage this marketplace.", forbiddenSchema),
|
||||
404: jsonResponse("The marketplace could not be found.", notFoundSchema),
|
||||
},
|
||||
}),
|
||||
async (c: OrgContext) => {
|
||||
try {
|
||||
const params = validParam<any>(c)
|
||||
return c.json({ ok: true, item: await setMarketplaceLifecycle({ action, context: actorContext(c), marketplaceId: params.marketplaceId }) })
|
||||
} catch (error) {
|
||||
return routeErrorResponse(c, error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
withPluginArchOrgContext(app, "get", pluginArchRoutePaths.marketplacePlugins,
|
||||
paramValidator(marketplaceParamsSchema),
|
||||
describeRoute({
|
||||
tags: ["Marketplaces"],
|
||||
summary: "List marketplace plugins",
|
||||
description: "Lists marketplace memberships and resolved plugin projections.",
|
||||
responses: {
|
||||
200: jsonResponse("Marketplace memberships returned successfully.", marketplacePluginListResponseSchema),
|
||||
400: jsonResponse("The marketplace membership path parameters were invalid.", invalidRequestSchema),
|
||||
401: jsonResponse("The caller must be signed in to view marketplace memberships.", unauthorizedSchema),
|
||||
404: jsonResponse("The marketplace could not be found.", notFoundSchema),
|
||||
},
|
||||
}),
|
||||
async (c: OrgContext) => {
|
||||
try {
|
||||
const params = validParam<any>(c)
|
||||
return c.json(await listMarketplaceMemberships({ context: actorContext(c), includePlugins: true, marketplaceId: params.marketplaceId, onlyActive: false }))
|
||||
} catch (error) {
|
||||
return routeErrorResponse(c, error)
|
||||
}
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "post", pluginArchRoutePaths.marketplacePlugins,
|
||||
paramValidator(marketplaceParamsSchema),
|
||||
jsonValidator(marketplacePluginWriteSchema),
|
||||
describeRoute({
|
||||
tags: ["Marketplaces"],
|
||||
summary: "Add marketplace plugin",
|
||||
description: "Adds a plugin to a marketplace.",
|
||||
responses: {
|
||||
201: jsonResponse("Marketplace membership created successfully.", marketplacePluginMutationResponseSchema),
|
||||
400: jsonResponse("The marketplace membership request was invalid.", invalidRequestSchema),
|
||||
401: jsonResponse("The caller must be signed in to manage marketplace memberships.", unauthorizedSchema),
|
||||
403: jsonResponse("The caller lacks permission to edit this marketplace.", forbiddenSchema),
|
||||
404: jsonResponse("The marketplace or plugin could not be found.", notFoundSchema),
|
||||
},
|
||||
}),
|
||||
async (c: OrgContext) => {
|
||||
try {
|
||||
const params = validParam<any>(c)
|
||||
const body = validJson<any>(c)
|
||||
return c.json({ ok: true, item: await attachPluginToMarketplace({ context: actorContext(c), marketplaceId: params.marketplaceId, membershipSource: body.membershipSource, pluginId: body.pluginId }) }, 201)
|
||||
} catch (error) {
|
||||
return routeErrorResponse(c, error)
|
||||
}
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "delete", pluginArchRoutePaths.marketplacePlugin,
|
||||
paramValidator(marketplacePluginParamsSchema),
|
||||
describeRoute({
|
||||
tags: ["Marketplaces"],
|
||||
summary: "Remove marketplace plugin",
|
||||
description: "Removes one plugin from a marketplace.",
|
||||
responses: {
|
||||
204: emptyResponse("Marketplace membership removed successfully."),
|
||||
400: jsonResponse("The marketplace membership path parameters were invalid.", invalidRequestSchema),
|
||||
401: jsonResponse("The caller must be signed in to manage marketplace memberships.", unauthorizedSchema),
|
||||
403: jsonResponse("The caller lacks permission to edit this marketplace.", forbiddenSchema),
|
||||
404: jsonResponse("The marketplace membership could not be found.", notFoundSchema),
|
||||
},
|
||||
}),
|
||||
async (c: OrgContext) => {
|
||||
try {
|
||||
const params = validParam<any>(c)
|
||||
await removePluginFromMarketplace({ context: actorContext(c), marketplaceId: params.marketplaceId, pluginId: params.pluginId })
|
||||
return c.body(null, 204)
|
||||
} catch (error) {
|
||||
return routeErrorResponse(c, error)
|
||||
}
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "get", pluginArchRoutePaths.marketplaceAccess,
|
||||
paramValidator(marketplaceParamsSchema),
|
||||
describeRoute({
|
||||
tags: ["Marketplaces"],
|
||||
summary: "List marketplace access grants",
|
||||
description: "Lists direct, team, and org-wide grants for a marketplace.",
|
||||
responses: {
|
||||
200: jsonResponse("Marketplace access grants returned successfully.", accessGrantListResponseSchema),
|
||||
400: jsonResponse("The marketplace access path parameters were invalid.", invalidRequestSchema),
|
||||
401: jsonResponse("The caller must be signed in to manage marketplace access.", unauthorizedSchema),
|
||||
403: jsonResponse("The caller lacks permission to manage marketplace access.", forbiddenSchema),
|
||||
404: jsonResponse("The marketplace could not be found.", notFoundSchema),
|
||||
},
|
||||
}),
|
||||
async (c: OrgContext) => {
|
||||
try {
|
||||
const params = validParam<any>(c)
|
||||
return c.json(await listResourceAccess({ context: actorContext(c), resourceId: params.marketplaceId, resourceKind: "marketplace" }))
|
||||
} catch (error) {
|
||||
return routeErrorResponse(c, error)
|
||||
}
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "post", pluginArchRoutePaths.marketplaceAccess,
|
||||
paramValidator(marketplaceParamsSchema),
|
||||
jsonValidator(resourceAccessGrantWriteSchema),
|
||||
describeRoute({
|
||||
tags: ["Marketplaces"],
|
||||
summary: "Grant marketplace access",
|
||||
description: "Creates or reactivates one access grant for a marketplace.",
|
||||
responses: {
|
||||
201: jsonResponse("Marketplace access grant created successfully.", accessGrantMutationResponseSchema),
|
||||
400: jsonResponse("The marketplace access request was invalid.", invalidRequestSchema),
|
||||
401: jsonResponse("The caller must be signed in to manage marketplace access.", unauthorizedSchema),
|
||||
403: jsonResponse("The caller lacks permission to manage marketplace access.", forbiddenSchema),
|
||||
404: jsonResponse("The marketplace could not be found.", notFoundSchema),
|
||||
},
|
||||
}),
|
||||
async (c: OrgContext) => {
|
||||
try {
|
||||
const params = validParam<any>(c)
|
||||
return c.json({ ok: true, item: await createResourceAccessGrant({ context: actorContext(c), resourceId: params.marketplaceId, resourceKind: "marketplace", value: validJson<any>(c) }) }, 201)
|
||||
} catch (error) {
|
||||
return routeErrorResponse(c, error)
|
||||
}
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "delete", pluginArchRoutePaths.marketplaceAccessGrant,
|
||||
paramValidator(marketplaceAccessGrantParamsSchema),
|
||||
describeRoute({
|
||||
tags: ["Marketplaces"],
|
||||
summary: "Revoke marketplace access",
|
||||
description: "Soft-revokes one marketplace access grant.",
|
||||
responses: {
|
||||
204: emptyResponse("Marketplace access revoked successfully."),
|
||||
400: jsonResponse("The marketplace access path parameters were invalid.", invalidRequestSchema),
|
||||
401: jsonResponse("The caller must be signed in to manage marketplace access.", unauthorizedSchema),
|
||||
403: jsonResponse("The caller lacks permission to manage marketplace access.", forbiddenSchema),
|
||||
404: jsonResponse("The access grant could not be found.", notFoundSchema),
|
||||
},
|
||||
}),
|
||||
async (c: OrgContext) => {
|
||||
try {
|
||||
const params = validParam<any>(c)
|
||||
await deleteResourceAccessGrant({ context: actorContext(c), grantId: params.grantId, resourceId: params.marketplaceId, resourceKind: "marketplace" })
|
||||
return c.body(null, 204)
|
||||
} catch (error) {
|
||||
return routeErrorResponse(c, error)
|
||||
}
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "get", pluginArchRoutePaths.connectorAccounts,
|
||||
paramValidator(connectorAccountParamsSchema.pick({ orgId: true })),
|
||||
queryValidator(connectorAccountListQuerySchema),
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
connectorSyncStatusValues,
|
||||
connectorTargetKindValues,
|
||||
connectorTypeValues,
|
||||
marketplaceStatusValues,
|
||||
membershipSourceValues,
|
||||
pluginStatusValues,
|
||||
} from "@openwork-ee/den-db/schema"
|
||||
@@ -33,6 +34,9 @@ export const configObjectAccessGrantIdSchema = denTypeIdSchema("configObjectAcce
|
||||
export const pluginIdSchema = denTypeIdSchema("plugin")
|
||||
export const pluginConfigObjectIdSchema = denTypeIdSchema("pluginConfigObject")
|
||||
export const pluginAccessGrantIdSchema = denTypeIdSchema("pluginAccessGrant")
|
||||
export const marketplaceIdSchema = denTypeIdSchema("marketplace")
|
||||
export const marketplacePluginIdSchema = denTypeIdSchema("marketplacePlugin")
|
||||
export const marketplaceAccessGrantIdSchema = denTypeIdSchema("marketplaceAccessGrant")
|
||||
export const connectorAccountIdSchema = denTypeIdSchema("connectorAccount")
|
||||
export const connectorInstanceIdSchema = denTypeIdSchema("connectorInstance")
|
||||
export const connectorInstanceAccessGrantIdSchema = denTypeIdSchema("connectorInstanceAccessGrant")
|
||||
@@ -49,6 +53,7 @@ export const configObjectSourceModeSchema = z.enum(configObjectSourceModeValues)
|
||||
export const configObjectCreatedViaSchema = z.enum(configObjectCreatedViaValues)
|
||||
export const configObjectStatusSchema = z.enum(configObjectStatusValues)
|
||||
export const pluginStatusSchema = z.enum(pluginStatusValues)
|
||||
export const marketplaceStatusSchema = z.enum(marketplaceStatusValues)
|
||||
export const membershipSourceSchema = z.enum(membershipSourceValues)
|
||||
export const accessRoleSchema = z.enum(accessRoleValues)
|
||||
export const connectorTypeSchema = z.enum(connectorTypeValues)
|
||||
@@ -84,6 +89,11 @@ export const pluginListQuerySchema = pluginArchPaginationQuerySchema.extend({
|
||||
q: z.string().trim().min(1).max(255).optional(),
|
||||
})
|
||||
|
||||
export const marketplaceListQuerySchema = pluginArchPaginationQuerySchema.extend({
|
||||
status: marketplaceStatusSchema.optional(),
|
||||
q: z.string().trim().min(1).max(255).optional(),
|
||||
})
|
||||
|
||||
export const connectorAccountListQuerySchema = pluginArchPaginationQuerySchema.extend({
|
||||
connectorType: connectorTypeSchema.optional(),
|
||||
status: connectorAccountStatusSchema.optional(),
|
||||
@@ -128,6 +138,9 @@ export const configObjectAccessGrantParamsSchema = configObjectParamsSchema.exte
|
||||
export const pluginParamsSchema = orgIdParamSchema.extend(idParamSchema("pluginId", "plugin").shape)
|
||||
export const pluginConfigObjectParamsSchema = pluginParamsSchema.extend(idParamSchema("configObjectId", "configObject").shape)
|
||||
export const pluginAccessGrantParamsSchema = pluginParamsSchema.extend(idParamSchema("grantId", "pluginAccessGrant").shape)
|
||||
export const marketplaceParamsSchema = orgIdParamSchema.extend(idParamSchema("marketplaceId", "marketplace").shape)
|
||||
export const marketplacePluginParamsSchema = marketplaceParamsSchema.extend(idParamSchema("pluginId", "plugin").shape)
|
||||
export const marketplaceAccessGrantParamsSchema = marketplaceParamsSchema.extend(idParamSchema("grantId", "marketplaceAccessGrant").shape)
|
||||
export const connectorAccountParamsSchema = orgIdParamSchema.extend(idParamSchema("connectorAccountId", "connectorAccount").shape)
|
||||
export const connectorInstanceParamsSchema = orgIdParamSchema.extend(idParamSchema("connectorInstanceId", "connectorInstance").shape)
|
||||
export const connectorInstanceAccessGrantParamsSchema = connectorInstanceParamsSchema.extend(idParamSchema("grantId", "connectorInstanceAccessGrant").shape)
|
||||
@@ -204,11 +217,34 @@ export const pluginUpdateSchema = z.object({
|
||||
}
|
||||
})
|
||||
|
||||
export const marketplaceCreateSchema = z.object({
|
||||
name: z.string().trim().min(1).max(255),
|
||||
description: nullableStringSchema.optional(),
|
||||
})
|
||||
|
||||
export const marketplaceUpdateSchema = z.object({
|
||||
name: z.string().trim().min(1).max(255).optional(),
|
||||
description: nullableStringSchema.optional(),
|
||||
}).superRefine((value, ctx) => {
|
||||
if (value.name === undefined && value.description === undefined) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Provide at least one field to update.",
|
||||
path: ["name"],
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export const pluginMembershipWriteSchema = z.object({
|
||||
configObjectId: configObjectIdSchema,
|
||||
membershipSource: membershipSourceSchema.optional(),
|
||||
})
|
||||
|
||||
export const marketplacePluginWriteSchema = z.object({
|
||||
pluginId: pluginIdSchema,
|
||||
membershipSource: membershipSourceSchema.optional(),
|
||||
})
|
||||
|
||||
export const connectorAccountCreateSchema = z.object({
|
||||
connectorType: connectorTypeSchema,
|
||||
remoteId: z.string().trim().min(1).max(255),
|
||||
@@ -324,7 +360,7 @@ export const githubValidateTargetSchema = z.object({
|
||||
})
|
||||
|
||||
export const accessGrantSchema = z.object({
|
||||
id: z.union([configObjectAccessGrantIdSchema, pluginAccessGrantIdSchema, connectorInstanceAccessGrantIdSchema]),
|
||||
id: z.union([configObjectAccessGrantIdSchema, pluginAccessGrantIdSchema, marketplaceAccessGrantIdSchema, connectorInstanceAccessGrantIdSchema]),
|
||||
orgMembershipId: memberIdSchema.nullable(),
|
||||
teamId: teamIdSchema.nullable(),
|
||||
orgWide: z.boolean(),
|
||||
@@ -393,6 +429,30 @@ export const pluginSchema = z.object({
|
||||
memberCount: z.number().int().nonnegative().optional(),
|
||||
}).meta({ ref: "PluginArchPlugin" })
|
||||
|
||||
export const marketplacePluginSchema = z.object({
|
||||
id: marketplacePluginIdSchema,
|
||||
marketplaceId: marketplaceIdSchema,
|
||||
pluginId: pluginIdSchema,
|
||||
membershipSource: membershipSourceSchema,
|
||||
createdByOrgMembershipId: memberIdSchema.nullable(),
|
||||
createdAt: z.string().datetime({ offset: true }),
|
||||
removedAt: nullableTimestampSchema,
|
||||
plugin: pluginSchema.optional(),
|
||||
}).meta({ ref: "PluginArchMarketplacePluginMembership" })
|
||||
|
||||
export const marketplaceSchema = z.object({
|
||||
id: marketplaceIdSchema,
|
||||
organizationId: denTypeIdSchema("organization"),
|
||||
name: z.string().trim().min(1).max(255),
|
||||
description: nullableStringSchema,
|
||||
status: marketplaceStatusSchema,
|
||||
createdByOrgMembershipId: memberIdSchema,
|
||||
createdAt: z.string().datetime({ offset: true }),
|
||||
updatedAt: z.string().datetime({ offset: true }),
|
||||
deletedAt: nullableTimestampSchema,
|
||||
pluginCount: z.number().int().nonnegative().optional(),
|
||||
}).meta({ ref: "PluginArchMarketplace" })
|
||||
|
||||
export const connectorAccountSchema = z.object({
|
||||
id: connectorAccountIdSchema,
|
||||
organizationId: denTypeIdSchema("organization"),
|
||||
@@ -609,6 +669,11 @@ export const pluginMutationResponseSchema = pluginArchMutationResponseSchema("Pl
|
||||
export const pluginMembershipListResponseSchema = pluginArchListResponseSchema("PluginArchPluginMembershipListResponse", pluginMembershipSchema)
|
||||
export const pluginMembershipDetailResponseSchema = pluginArchDetailResponseSchema("PluginArchPluginMembershipDetailResponse", pluginMembershipSchema)
|
||||
export const pluginMembershipMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchPluginMembershipMutationResponse", pluginMembershipSchema)
|
||||
export const marketplaceListResponseSchema = pluginArchListResponseSchema("PluginArchMarketplaceListResponse", marketplaceSchema)
|
||||
export const marketplaceDetailResponseSchema = pluginArchDetailResponseSchema("PluginArchMarketplaceDetailResponse", marketplaceSchema)
|
||||
export const marketplaceMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchMarketplaceMutationResponse", marketplaceSchema)
|
||||
export const marketplacePluginListResponseSchema = pluginArchListResponseSchema("PluginArchMarketplacePluginListResponse", marketplacePluginSchema)
|
||||
export const marketplacePluginMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchMarketplacePluginMutationResponse", marketplacePluginSchema)
|
||||
export const accessGrantListResponseSchema = pluginArchListResponseSchema("PluginArchAccessGrantListResponse", accessGrantSchema)
|
||||
export const accessGrantMutationResponseSchema = pluginArchMutationResponseSchema("PluginArchAccessGrantMutationResponse", accessGrantSchema)
|
||||
export const connectorAccountListResponseSchema = pluginArchListResponseSchema("PluginArchConnectorAccountListResponse", connectorAccountSchema)
|
||||
|
||||
@@ -11,6 +11,9 @@ import {
|
||||
ConnectorSourceTombstoneTable,
|
||||
ConnectorSyncEventTable,
|
||||
ConnectorTargetTable,
|
||||
MarketplaceAccessGrantTable,
|
||||
MarketplacePluginTable,
|
||||
MarketplaceTable,
|
||||
PluginAccessGrantTable,
|
||||
PluginConfigObjectTable,
|
||||
PluginTable,
|
||||
@@ -25,17 +28,23 @@ type MemberId = PluginArchActorContext["organizationContext"]["currentMember"]["
|
||||
type TeamId = PluginArchActorContext["memberTeams"][number]["id"]
|
||||
type ConfigObjectRow = typeof ConfigObjectTable.$inferSelect
|
||||
type ConfigObjectVersionRow = typeof ConfigObjectVersionTable.$inferSelect
|
||||
type MarketplaceRow = typeof MarketplaceTable.$inferSelect
|
||||
type MarketplaceMembershipRow = typeof MarketplacePluginTable.$inferSelect
|
||||
type PluginRow = typeof PluginTable.$inferSelect
|
||||
type PluginMembershipRow = typeof PluginConfigObjectTable.$inferSelect
|
||||
type ConfigObjectId = ConfigObjectRow["id"]
|
||||
type ConfigObjectVersionId = ConfigObjectVersionRow["id"]
|
||||
type MarketplaceId = MarketplaceRow["id"]
|
||||
type MarketplaceMembershipId = MarketplaceMembershipRow["id"]
|
||||
type PluginId = PluginRow["id"]
|
||||
type PluginMembershipId = PluginMembershipRow["id"]
|
||||
type AccessGrantRow =
|
||||
| typeof ConfigObjectAccessGrantTable.$inferSelect
|
||||
| typeof MarketplaceAccessGrantTable.$inferSelect
|
||||
| typeof PluginAccessGrantTable.$inferSelect
|
||||
| typeof ConnectorInstanceAccessGrantTable.$inferSelect
|
||||
type ConfigObjectAccessGrantId = typeof ConfigObjectAccessGrantTable.$inferSelect.id
|
||||
type MarketplaceAccessGrantId = typeof MarketplaceAccessGrantTable.$inferSelect.id
|
||||
type PluginAccessGrantId = typeof PluginAccessGrantTable.$inferSelect.id
|
||||
type ConnectorInstanceAccessGrantId = typeof ConnectorInstanceAccessGrantTable.$inferSelect.id
|
||||
type ConnectorAccountRow = typeof ConnectorAccountTable.$inferSelect
|
||||
@@ -86,6 +95,11 @@ type PluginResourceTarget = {
|
||||
resourceKind: "plugin"
|
||||
}
|
||||
|
||||
type MarketplaceResourceTarget = {
|
||||
resourceId: MarketplaceId
|
||||
resourceKind: "marketplace"
|
||||
}
|
||||
|
||||
type ConnectorInstanceResourceTarget = {
|
||||
resourceId: ConnectorInstanceId
|
||||
resourceKind: "connector_instance"
|
||||
@@ -93,13 +107,15 @@ type ConnectorInstanceResourceTarget = {
|
||||
|
||||
type ResourceTarget =
|
||||
| ConfigObjectResourceTarget
|
||||
| MarketplaceResourceTarget
|
||||
| PluginResourceTarget
|
||||
| ConnectorInstanceResourceTarget
|
||||
|
||||
type ConfigObjectGrantTarget = ConfigObjectResourceTarget & { grantId: ConfigObjectAccessGrantId }
|
||||
type MarketplaceGrantTarget = MarketplaceResourceTarget & { grantId: MarketplaceAccessGrantId }
|
||||
type PluginGrantTarget = PluginResourceTarget & { grantId: PluginAccessGrantId }
|
||||
type ConnectorInstanceGrantTarget = ConnectorInstanceResourceTarget & { grantId: ConnectorInstanceAccessGrantId }
|
||||
type GrantTarget = ConfigObjectGrantTarget | PluginGrantTarget | ConnectorInstanceGrantTarget
|
||||
type GrantTarget = ConfigObjectGrantTarget | MarketplaceGrantTarget | PluginGrantTarget | ConnectorInstanceGrantTarget
|
||||
|
||||
export class PluginArchRouteFailure extends Error {
|
||||
constructor(
|
||||
@@ -254,6 +270,21 @@ function serializePlugin(row: PluginRow, memberCount?: number) {
|
||||
}
|
||||
}
|
||||
|
||||
function serializeMarketplace(row: MarketplaceRow, pluginCount?: number) {
|
||||
return {
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
createdByOrgMembershipId: row.createdByOrgMembershipId,
|
||||
deletedAt: row.deletedAt ? row.deletedAt.toISOString() : null,
|
||||
description: row.description,
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
organizationId: row.organizationId,
|
||||
pluginCount,
|
||||
status: row.status,
|
||||
updatedAt: row.updatedAt.toISOString(),
|
||||
}
|
||||
}
|
||||
|
||||
function serializeMembership(row: PluginMembershipRow, configObject?: ReturnType<typeof serializeConfigObject>) {
|
||||
return {
|
||||
configObject,
|
||||
@@ -268,6 +299,19 @@ function serializeMembership(row: PluginMembershipRow, configObject?: ReturnType
|
||||
}
|
||||
}
|
||||
|
||||
function serializeMarketplaceMembership(row: MarketplaceMembershipRow, plugin?: ReturnType<typeof serializePlugin>) {
|
||||
return {
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
createdByOrgMembershipId: row.createdByOrgMembershipId,
|
||||
id: row.id,
|
||||
marketplaceId: row.marketplaceId,
|
||||
membershipSource: row.membershipSource,
|
||||
plugin,
|
||||
pluginId: row.pluginId,
|
||||
removedAt: row.removedAt ? row.removedAt.toISOString() : null,
|
||||
}
|
||||
}
|
||||
|
||||
function serializeAccessGrant(row: AccessGrantRow) {
|
||||
return {
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
@@ -385,6 +429,16 @@ async function getPluginRow(organizationId: OrganizationId, pluginId: PluginId)
|
||||
return rows[0] ?? null
|
||||
}
|
||||
|
||||
async function getMarketplaceRow(organizationId: OrganizationId, marketplaceId: MarketplaceId) {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(MarketplaceTable)
|
||||
.where(and(eq(MarketplaceTable.organizationId, organizationId), eq(MarketplaceTable.id, marketplaceId)))
|
||||
.limit(1)
|
||||
|
||||
return rows[0] ?? null
|
||||
}
|
||||
|
||||
async function getConnectorAccountRow(organizationId: OrganizationId, connectorAccountId: ConnectorAccountId) {
|
||||
const rows = await db
|
||||
.select()
|
||||
@@ -456,6 +510,24 @@ async function ensureEditablePlugin(context: PluginArchActorContext, pluginId: P
|
||||
return row
|
||||
}
|
||||
|
||||
async function ensureEditableMarketplace(context: PluginArchActorContext, marketplaceId: MarketplaceId) {
|
||||
const row = await getMarketplaceRow(context.organizationContext.organization.id, marketplaceId)
|
||||
if (!row) {
|
||||
throw new PluginArchRouteFailure(404, "marketplace_not_found", "Marketplace not found.")
|
||||
}
|
||||
await requirePluginArchResourceRole({ context, resourceId: row.id, resourceKind: "marketplace", role: "editor" })
|
||||
return row
|
||||
}
|
||||
|
||||
async function ensureVisibleMarketplace(context: PluginArchActorContext, marketplaceId: MarketplaceId) {
|
||||
const row = await getMarketplaceRow(context.organizationContext.organization.id, marketplaceId)
|
||||
if (!row) {
|
||||
throw new PluginArchRouteFailure(404, "marketplace_not_found", "Marketplace not found.")
|
||||
}
|
||||
await requirePluginArchResourceRole({ context, resourceId: row.id, resourceKind: "marketplace", role: "viewer" })
|
||||
return row
|
||||
}
|
||||
|
||||
async function ensureVisiblePlugin(context: PluginArchActorContext, pluginId: PluginId) {
|
||||
const row = await getPluginRow(context.organizationContext.organization.id, pluginId)
|
||||
if (!row) {
|
||||
@@ -535,6 +607,50 @@ async function upsertGrant(input: ResourceTarget & {
|
||||
return serializeAccessGrant({ ...row, removedAt: null })
|
||||
}
|
||||
|
||||
if (input.resourceKind === "marketplace") {
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(MarketplaceAccessGrantTable)
|
||||
.where(and(
|
||||
eq(MarketplaceAccessGrantTable.marketplaceId, input.resourceId),
|
||||
input.value.orgMembershipId
|
||||
? eq(MarketplaceAccessGrantTable.orgMembershipId, input.value.orgMembershipId)
|
||||
: input.value.teamId
|
||||
? eq(MarketplaceAccessGrantTable.teamId, input.value.teamId)
|
||||
: eq(MarketplaceAccessGrantTable.orgWide, true),
|
||||
))
|
||||
.limit(1)
|
||||
|
||||
if (existing[0]) {
|
||||
await db
|
||||
.update(MarketplaceAccessGrantTable)
|
||||
.set({
|
||||
createdByOrgMembershipId,
|
||||
orgMembershipId: input.value.orgMembershipId ?? null,
|
||||
orgWide: input.value.orgWide ?? false,
|
||||
removedAt: null,
|
||||
role: input.value.role,
|
||||
teamId: input.value.teamId ?? null,
|
||||
})
|
||||
.where(eq(MarketplaceAccessGrantTable.id, existing[0].id))
|
||||
return serializeAccessGrant({ ...existing[0], createdByOrgMembershipId, orgMembershipId: input.value.orgMembershipId ?? null, orgWide: input.value.orgWide ?? false, removedAt: null, role: input.value.role, teamId: input.value.teamId ?? null })
|
||||
}
|
||||
|
||||
const row = {
|
||||
createdAt,
|
||||
createdByOrgMembershipId,
|
||||
id: createDenTypeId("marketplaceAccessGrant"),
|
||||
marketplaceId: input.resourceId,
|
||||
organizationId,
|
||||
orgMembershipId: input.value.orgMembershipId ?? null,
|
||||
orgWide: input.value.orgWide ?? false,
|
||||
role: input.value.role,
|
||||
teamId: input.value.teamId ?? null,
|
||||
}
|
||||
await db.insert(MarketplaceAccessGrantTable).values(row)
|
||||
return serializeAccessGrant({ ...row, removedAt: null })
|
||||
}
|
||||
|
||||
if (input.resourceKind === "plugin") {
|
||||
const existing = await db
|
||||
.select()
|
||||
@@ -634,6 +750,16 @@ async function removeGrant(input: GrantTarget & { context: PluginArchActorContex
|
||||
await db.update(ConfigObjectAccessGrantTable).set({ removedAt }).where(eq(ConfigObjectAccessGrantTable.id, input.grantId))
|
||||
return
|
||||
}
|
||||
if (input.resourceKind === "marketplace") {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(MarketplaceAccessGrantTable)
|
||||
.where(and(eq(MarketplaceAccessGrantTable.id, input.grantId), eq(MarketplaceAccessGrantTable.marketplaceId, input.resourceId)))
|
||||
.limit(1)
|
||||
if (!rows[0]) throw new PluginArchRouteFailure(404, "access_grant_not_found", "Access grant not found.")
|
||||
await db.update(MarketplaceAccessGrantTable).set({ removedAt }).where(eq(MarketplaceAccessGrantTable.id, input.grantId))
|
||||
return
|
||||
}
|
||||
if (input.resourceKind === "plugin") {
|
||||
const rows = await db
|
||||
.select()
|
||||
@@ -970,6 +1096,10 @@ export async function listResourceAccess(input: { context: PluginArchActorContex
|
||||
const rows = await db.select().from(ConfigObjectAccessGrantTable).where(eq(ConfigObjectAccessGrantTable.configObjectId, input.resourceId)).orderBy(desc(ConfigObjectAccessGrantTable.createdAt))
|
||||
return { items: rows.map((row) => serializeAccessGrant(row)), nextCursor: null }
|
||||
}
|
||||
if (input.resourceKind === "marketplace") {
|
||||
const rows = await db.select().from(MarketplaceAccessGrantTable).where(eq(MarketplaceAccessGrantTable.marketplaceId, input.resourceId)).orderBy(desc(MarketplaceAccessGrantTable.createdAt))
|
||||
return { items: rows.map((row) => serializeAccessGrant(row)), nextCursor: null }
|
||||
}
|
||||
if (input.resourceKind === "plugin") {
|
||||
const rows = await db.select().from(PluginAccessGrantTable).where(eq(PluginAccessGrantTable.pluginId, input.resourceId)).orderBy(desc(PluginAccessGrantTable.createdAt))
|
||||
return { items: rows.map((row) => serializeAccessGrant(row)), nextCursor: null }
|
||||
@@ -1107,6 +1237,166 @@ export async function removePluginMembership(input: { configObjectId: ConfigObje
|
||||
return removeConfigObjectFromPlugin(input)
|
||||
}
|
||||
|
||||
export async function listMarketplaces(input: { context: PluginArchActorContext; cursor?: string; limit?: number; q?: string; status?: MarketplaceRow["status"] }) {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(MarketplaceTable)
|
||||
.where(eq(MarketplaceTable.organizationId, input.context.organizationContext.organization.id))
|
||||
.orderBy(desc(MarketplaceTable.updatedAt), desc(MarketplaceTable.id))
|
||||
|
||||
const memberships = await db
|
||||
.select({ marketplaceId: MarketplacePluginTable.marketplaceId, count: MarketplacePluginTable.id })
|
||||
.from(MarketplacePluginTable)
|
||||
.where(isNull(MarketplacePluginTable.removedAt))
|
||||
|
||||
const counts = memberships.reduce((accumulator, row) => {
|
||||
accumulator.set(row.marketplaceId, (accumulator.get(row.marketplaceId) ?? 0) + 1)
|
||||
return accumulator
|
||||
}, new Map<string, number>())
|
||||
|
||||
const visible: ReturnType<typeof serializeMarketplace>[] = []
|
||||
for (const row of rows) {
|
||||
const role = await resolvePluginArchResourceRole({ context: input.context, resourceId: row.id, resourceKind: "marketplace" })
|
||||
if (!role) continue
|
||||
if (input.status && row.status !== input.status) continue
|
||||
if (input.q) {
|
||||
const haystack = `${row.name}\n${row.description ?? ""}`.toLowerCase()
|
||||
if (!haystack.includes(input.q.toLowerCase())) continue
|
||||
}
|
||||
visible.push(serializeMarketplace(row, counts.get(row.id) ?? 0))
|
||||
}
|
||||
|
||||
return pageItems(visible, input.cursor, input.limit)
|
||||
}
|
||||
|
||||
export async function getMarketplaceDetail(context: PluginArchActorContext, marketplaceId: MarketplaceId) {
|
||||
const row = await ensureVisibleMarketplace(context, marketplaceId)
|
||||
const memberships = await db
|
||||
.select({ id: MarketplacePluginTable.id })
|
||||
.from(MarketplacePluginTable)
|
||||
.where(and(eq(MarketplacePluginTable.marketplaceId, row.id), isNull(MarketplacePluginTable.removedAt)))
|
||||
return serializeMarketplace(row, memberships.length)
|
||||
}
|
||||
|
||||
export async function createMarketplace(input: { context: PluginArchActorContext; description?: string | null; name: string }) {
|
||||
const now = new Date()
|
||||
const row = {
|
||||
createdAt: now,
|
||||
createdByOrgMembershipId: input.context.organizationContext.currentMember.id,
|
||||
deletedAt: null,
|
||||
description: normalizeOptionalString(input.description ?? undefined),
|
||||
id: createDenTypeId("marketplace"),
|
||||
name: input.name.trim(),
|
||||
organizationId: input.context.organizationContext.organization.id,
|
||||
status: "active" as const,
|
||||
updatedAt: now,
|
||||
}
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
await tx.insert(MarketplaceTable).values(row)
|
||||
await tx.insert(MarketplaceAccessGrantTable).values({
|
||||
createdAt: now,
|
||||
createdByOrgMembershipId: input.context.organizationContext.currentMember.id,
|
||||
id: createDenTypeId("marketplaceAccessGrant"),
|
||||
marketplaceId: row.id,
|
||||
organizationId: input.context.organizationContext.organization.id,
|
||||
orgMembershipId: input.context.organizationContext.currentMember.id,
|
||||
orgWide: false,
|
||||
role: "manager",
|
||||
teamId: null,
|
||||
})
|
||||
})
|
||||
|
||||
return serializeMarketplace(row, 0)
|
||||
}
|
||||
|
||||
export async function updateMarketplace(input: { context: PluginArchActorContext; description?: string | null; marketplaceId: MarketplaceId; name?: string }) {
|
||||
const row = await ensureEditableMarketplace(input.context, input.marketplaceId)
|
||||
const updatedAt = new Date()
|
||||
await db.update(MarketplaceTable).set({
|
||||
description: input.description === undefined ? row.description : normalizeOptionalString(input.description ?? undefined),
|
||||
name: input.name?.trim() || row.name,
|
||||
updatedAt,
|
||||
}).where(eq(MarketplaceTable.id, row.id))
|
||||
return getMarketplaceDetail(input.context, row.id)
|
||||
}
|
||||
|
||||
export async function setMarketplaceLifecycle(input: { action: "archive" | "restore"; context: PluginArchActorContext; marketplaceId: MarketplaceId }) {
|
||||
const row = await ensureVisibleMarketplace(input.context, input.marketplaceId)
|
||||
await requirePluginArchResourceRole({ context: input.context, resourceId: row.id, resourceKind: "marketplace", role: "manager" })
|
||||
const updatedAt = new Date()
|
||||
await db.update(MarketplaceTable).set({
|
||||
deletedAt: input.action === "archive" ? row.deletedAt : null,
|
||||
status: input.action === "archive" ? "archived" : "active",
|
||||
updatedAt,
|
||||
}).where(eq(MarketplaceTable.id, row.id))
|
||||
return getMarketplaceDetail(input.context, row.id)
|
||||
}
|
||||
|
||||
export async function listMarketplaceMemberships(input: { context: PluginArchActorContext; includePlugins?: boolean; marketplaceId: MarketplaceId; onlyActive?: boolean }) {
|
||||
await ensureVisibleMarketplace(input.context, input.marketplaceId)
|
||||
const memberships = await db
|
||||
.select()
|
||||
.from(MarketplacePluginTable)
|
||||
.where(input.onlyActive ? and(eq(MarketplacePluginTable.marketplaceId, input.marketplaceId), isNull(MarketplacePluginTable.removedAt)) : eq(MarketplacePluginTable.marketplaceId, input.marketplaceId))
|
||||
.orderBy(desc(MarketplacePluginTable.createdAt))
|
||||
|
||||
if (!input.includePlugins) {
|
||||
return { items: memberships.map((membership) => serializeMarketplaceMembership(membership)), nextCursor: null }
|
||||
}
|
||||
|
||||
const plugins = memberships.length === 0
|
||||
? []
|
||||
: await db.select().from(PluginTable).where(inArray(PluginTable.id, memberships.map((membership) => membership.pluginId)))
|
||||
const byId = new Map<string, ReturnType<typeof serializePlugin>>(plugins.map((row) => [row.id, serializePlugin(row)]))
|
||||
return { items: memberships.map((membership) => serializeMarketplaceMembership(membership, byId.get(membership.pluginId))), nextCursor: null }
|
||||
}
|
||||
|
||||
export async function attachPluginToMarketplace(input: { context: PluginArchActorContext; marketplaceId: MarketplaceId; membershipSource?: MarketplaceMembershipRow["membershipSource"]; pluginId: PluginId }) {
|
||||
await ensureVisiblePlugin(input.context, input.pluginId)
|
||||
await ensureEditableMarketplace(input.context, input.marketplaceId)
|
||||
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(MarketplacePluginTable)
|
||||
.where(and(eq(MarketplacePluginTable.marketplaceId, input.marketplaceId), eq(MarketplacePluginTable.pluginId, input.pluginId)))
|
||||
.limit(1)
|
||||
|
||||
const now = new Date()
|
||||
let membershipId: MarketplaceMembershipId | null = existing[0]?.id ?? null
|
||||
if (existing[0]) {
|
||||
await db.update(MarketplacePluginTable).set({ membershipSource: input.membershipSource ?? existing[0].membershipSource, removedAt: null }).where(eq(MarketplacePluginTable.id, existing[0].id))
|
||||
} else {
|
||||
membershipId = createDenTypeId("marketplacePlugin")
|
||||
await db.insert(MarketplacePluginTable).values({
|
||||
createdAt: now,
|
||||
createdByOrgMembershipId: input.context.organizationContext.currentMember.id,
|
||||
id: membershipId,
|
||||
marketplaceId: input.marketplaceId,
|
||||
membershipSource: input.membershipSource ?? "manual",
|
||||
organizationId: input.context.organizationContext.organization.id,
|
||||
pluginId: input.pluginId,
|
||||
})
|
||||
}
|
||||
|
||||
const rows = await db.select().from(MarketplacePluginTable).where(eq(MarketplacePluginTable.id, membershipId!)).limit(1)
|
||||
return serializeMarketplaceMembership(rows[0])
|
||||
}
|
||||
|
||||
export async function removePluginFromMarketplace(input: { context: PluginArchActorContext; marketplaceId: MarketplaceId; pluginId: PluginId }) {
|
||||
await ensureVisiblePlugin(input.context, input.pluginId)
|
||||
await ensureEditableMarketplace(input.context, input.marketplaceId)
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(MarketplacePluginTable)
|
||||
.where(and(eq(MarketplacePluginTable.marketplaceId, input.marketplaceId), eq(MarketplacePluginTable.pluginId, input.pluginId), isNull(MarketplacePluginTable.removedAt)))
|
||||
.limit(1)
|
||||
if (!rows[0]) {
|
||||
throw new PluginArchRouteFailure(404, "marketplace_membership_not_found", "Marketplace membership not found.")
|
||||
}
|
||||
await db.update(MarketplacePluginTable).set({ removedAt: new Date() }).where(eq(MarketplacePluginTable.id, rows[0].id))
|
||||
}
|
||||
|
||||
export async function listConnectorAccounts(input: { context: PluginArchActorContext; connectorType?: ConnectorAccountRow["connectorType"]; cursor?: string; limit?: number; q?: string; status?: ConnectorAccountRow["status"] }) {
|
||||
const rows = await db
|
||||
.select()
|
||||
|
||||
@@ -41,6 +41,7 @@ test("org owners and admins get plugin-system capability access", () => {
|
||||
expect(accessModule.isPluginArchOrgAdmin(createActorContext({ role: "member" }))).toBe(false)
|
||||
|
||||
expect(accessModule.hasPluginArchCapability(createActorContext({ isOwner: true }), "plugin.create")).toBe(true)
|
||||
expect(accessModule.hasPluginArchCapability(createActorContext({ role: "admin" }), "marketplace.create")).toBe(true)
|
||||
expect(accessModule.hasPluginArchCapability(createActorContext({ role: "admin" }), "connector_instance.create")).toBe(true)
|
||||
expect(accessModule.hasPluginArchCapability(createActorContext({ role: "member" }), "config_object.create")).toBe(false)
|
||||
})
|
||||
|
||||
53
ee/packages/den-db/drizzle/0011_marketplaces.sql
Normal file
53
ee/packages/den-db/drizzle/0011_marketplaces.sql
Normal file
@@ -0,0 +1,53 @@
|
||||
CREATE TABLE IF NOT EXISTS `marketplace` (
|
||||
`id` varchar(64) NOT NULL,
|
||||
`organization_id` varchar(64) NOT NULL,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`description` text,
|
||||
`status` enum('active','inactive','deleted','archived') NOT NULL DEFAULT 'active',
|
||||
`created_by_org_membership_id` varchar(64) NOT NULL,
|
||||
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`updated_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
`deleted_at` timestamp(3) NULL,
|
||||
CONSTRAINT `marketplace_id` PRIMARY KEY(`id`),
|
||||
KEY `marketplace_organization_id` (`organization_id`),
|
||||
KEY `marketplace_created_by_org_membership_id` (`created_by_org_membership_id`),
|
||||
KEY `marketplace_status` (`status`),
|
||||
KEY `marketplace_name` (`name`)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `marketplace_plugin` (
|
||||
`id` varchar(64) NOT NULL,
|
||||
`organization_id` varchar(64) NOT NULL,
|
||||
`marketplace_id` varchar(64) NOT NULL,
|
||||
`plugin_id` varchar(64) NOT NULL,
|
||||
`membership_source` enum('manual','connector','api','system') NOT NULL DEFAULT 'manual',
|
||||
`created_by_org_membership_id` varchar(64),
|
||||
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`removed_at` timestamp(3) NULL,
|
||||
CONSTRAINT `marketplace_plugin_id` PRIMARY KEY(`id`),
|
||||
CONSTRAINT `marketplace_plugin_marketplace_plugin` UNIQUE(`marketplace_id`, `plugin_id`),
|
||||
KEY `marketplace_plugin_organization_id` (`organization_id`),
|
||||
KEY `marketplace_plugin_marketplace_id` (`marketplace_id`),
|
||||
KEY `marketplace_plugin_plugin_id` (`plugin_id`)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `marketplace_access_grant` (
|
||||
`id` varchar(64) NOT NULL,
|
||||
`organization_id` varchar(64) NOT NULL,
|
||||
`marketplace_id` varchar(64) NOT NULL,
|
||||
`org_membership_id` varchar(64),
|
||||
`team_id` varchar(64),
|
||||
`org_wide` boolean NOT NULL DEFAULT false,
|
||||
`role` enum('viewer','editor','manager') NOT NULL,
|
||||
`created_by_org_membership_id` varchar(64) NOT NULL,
|
||||
`created_at` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`removed_at` timestamp(3) NULL,
|
||||
CONSTRAINT `marketplace_access_grant_id` PRIMARY KEY(`id`),
|
||||
CONSTRAINT `marketplace_access_grant_marketplace_org_membership` UNIQUE(`marketplace_id`, `org_membership_id`),
|
||||
CONSTRAINT `marketplace_access_grant_marketplace_team` UNIQUE(`marketplace_id`, `team_id`),
|
||||
KEY `marketplace_access_grant_organization_id` (`organization_id`),
|
||||
KEY `marketplace_access_grant_marketplace_id` (`marketplace_id`),
|
||||
KEY `marketplace_access_grant_org_membership_id` (`org_membership_id`),
|
||||
KEY `marketplace_access_grant_team_id` (`team_id`),
|
||||
KEY `marketplace_access_grant_org_wide` (`org_wide`)
|
||||
);
|
||||
@@ -71,6 +71,13 @@
|
||||
"when": 1776427000000,
|
||||
"tag": "0010_plugin_arch",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 11,
|
||||
"version": "5",
|
||||
"when": 1776440000000,
|
||||
"tag": "0011_marketplaces",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ export const configObjectSourceModeValues = ["cloud", "import", "connector"] as
|
||||
export const configObjectStatusValues = ["active", "inactive", "deleted", "archived", "ingestion_error"] as const
|
||||
export const configObjectCreatedViaValues = ["cloud", "import", "connector", "system"] as const
|
||||
export const pluginStatusValues = ["active", "inactive", "deleted", "archived"] as const
|
||||
export const marketplaceStatusValues = ["active", "inactive", "deleted", "archived"] as const
|
||||
export const membershipSourceValues = ["manual", "connector", "api", "system"] as const
|
||||
export const accessRoleValues = ["viewer", "editor", "manager"] as const
|
||||
export const connectorTypeValues = ["github"] as const
|
||||
@@ -114,6 +115,72 @@ export const PluginTable = mysqlTable(
|
||||
],
|
||||
)
|
||||
|
||||
export const MarketplaceTable = mysqlTable(
|
||||
"marketplace",
|
||||
{
|
||||
id: denTypeIdColumn("marketplace", "id").notNull().primaryKey(),
|
||||
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
description: text("description"),
|
||||
status: mysqlEnum("status", marketplaceStatusValues).notNull().default("active"),
|
||||
createdByOrgMembershipId: denTypeIdColumn("member", "created_by_org_membership_id").notNull(),
|
||||
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { fsp: 3 }).notNull().default(sql`CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)`),
|
||||
deletedAt: timestamp("deleted_at", { fsp: 3 }),
|
||||
},
|
||||
(table) => [
|
||||
index("marketplace_organization_id").on(table.organizationId),
|
||||
index("marketplace_created_by_org_membership_id").on(table.createdByOrgMembershipId),
|
||||
index("marketplace_status").on(table.status),
|
||||
index("marketplace_name").on(table.name),
|
||||
],
|
||||
)
|
||||
|
||||
export const MarketplacePluginTable = mysqlTable(
|
||||
"marketplace_plugin",
|
||||
{
|
||||
id: denTypeIdColumn("marketplacePlugin", "id").notNull().primaryKey(),
|
||||
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
|
||||
marketplaceId: denTypeIdColumn("marketplace", "marketplace_id").notNull(),
|
||||
pluginId: denTypeIdColumn("plugin", "plugin_id").notNull(),
|
||||
membershipSource: mysqlEnum("membership_source", membershipSourceValues).notNull().default("manual"),
|
||||
createdByOrgMembershipId: denTypeIdColumn("member", "created_by_org_membership_id"),
|
||||
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
|
||||
removedAt: timestamp("removed_at", { fsp: 3 }),
|
||||
},
|
||||
(table) => [
|
||||
index("marketplace_plugin_organization_id").on(table.organizationId),
|
||||
index("marketplace_plugin_marketplace_id").on(table.marketplaceId),
|
||||
index("marketplace_plugin_plugin_id").on(table.pluginId),
|
||||
uniqueIndex("marketplace_plugin_marketplace_plugin").on(table.marketplaceId, table.pluginId),
|
||||
],
|
||||
)
|
||||
|
||||
export const MarketplaceAccessGrantTable = mysqlTable(
|
||||
"marketplace_access_grant",
|
||||
{
|
||||
id: denTypeIdColumn("marketplaceAccessGrant", "id").notNull().primaryKey(),
|
||||
organizationId: denTypeIdColumn("organization", "organization_id").notNull(),
|
||||
marketplaceId: denTypeIdColumn("marketplace", "marketplace_id").notNull(),
|
||||
orgMembershipId: denTypeIdColumn("member", "org_membership_id"),
|
||||
teamId: denTypeIdColumn("team", "team_id"),
|
||||
orgWide: boolean("org_wide").notNull().default(false),
|
||||
role: mysqlEnum("role", accessRoleValues).notNull(),
|
||||
createdByOrgMembershipId: denTypeIdColumn("member", "created_by_org_membership_id").notNull(),
|
||||
createdAt: timestamp("created_at", { fsp: 3 }).notNull().defaultNow(),
|
||||
removedAt: timestamp("removed_at", { fsp: 3 }),
|
||||
},
|
||||
(table) => [
|
||||
index("marketplace_access_grant_organization_id").on(table.organizationId),
|
||||
index("marketplace_access_grant_marketplace_id").on(table.marketplaceId),
|
||||
index("marketplace_access_grant_org_membership_id").on(table.orgMembershipId),
|
||||
index("marketplace_access_grant_team_id").on(table.teamId),
|
||||
index("marketplace_access_grant_org_wide").on(table.orgWide),
|
||||
uniqueIndex("marketplace_access_grant_marketplace_org_membership").on(table.marketplaceId, table.orgMembershipId),
|
||||
uniqueIndex("marketplace_access_grant_marketplace_team").on(table.marketplaceId, table.teamId),
|
||||
],
|
||||
)
|
||||
|
||||
export const PluginConfigObjectTable = mysqlTable(
|
||||
"plugin_config_object",
|
||||
{
|
||||
@@ -438,6 +505,7 @@ export const pluginRelations = relations(PluginTable, ({ many, one }) => ({
|
||||
fields: [PluginTable.createdByOrgMembershipId],
|
||||
references: [MemberTable.id],
|
||||
}),
|
||||
marketplaces: many(MarketplacePluginTable),
|
||||
memberships: many(PluginConfigObjectTable),
|
||||
organization: one(OrganizationTable, {
|
||||
fields: [PluginTable.organizationId],
|
||||
@@ -446,6 +514,53 @@ export const pluginRelations = relations(PluginTable, ({ many, one }) => ({
|
||||
mappings: many(ConnectorMappingTable),
|
||||
}))
|
||||
|
||||
export const marketplaceRelations = relations(MarketplaceTable, ({ many, one }) => ({
|
||||
accessGrants: many(MarketplaceAccessGrantTable),
|
||||
createdByOrgMembership: one(MemberTable, {
|
||||
fields: [MarketplaceTable.createdByOrgMembershipId],
|
||||
references: [MemberTable.id],
|
||||
}),
|
||||
memberships: many(MarketplacePluginTable),
|
||||
organization: one(OrganizationTable, {
|
||||
fields: [MarketplaceTable.organizationId],
|
||||
references: [OrganizationTable.id],
|
||||
}),
|
||||
}))
|
||||
|
||||
export const marketplacePluginRelations = relations(MarketplacePluginTable, ({ one }) => ({
|
||||
createdByOrgMembership: one(MemberTable, {
|
||||
fields: [MarketplacePluginTable.createdByOrgMembershipId],
|
||||
references: [MemberTable.id],
|
||||
}),
|
||||
marketplace: one(MarketplaceTable, {
|
||||
fields: [MarketplacePluginTable.marketplaceId],
|
||||
references: [MarketplaceTable.id],
|
||||
}),
|
||||
plugin: one(PluginTable, {
|
||||
fields: [MarketplacePluginTable.pluginId],
|
||||
references: [PluginTable.id],
|
||||
}),
|
||||
}))
|
||||
|
||||
export const marketplaceAccessGrantRelations = relations(MarketplaceAccessGrantTable, ({ one }) => ({
|
||||
createdByOrgMembership: one(MemberTable, {
|
||||
fields: [MarketplaceAccessGrantTable.createdByOrgMembershipId],
|
||||
references: [MemberTable.id],
|
||||
}),
|
||||
marketplace: one(MarketplaceTable, {
|
||||
fields: [MarketplaceAccessGrantTable.marketplaceId],
|
||||
references: [MarketplaceTable.id],
|
||||
}),
|
||||
orgMembership: one(MemberTable, {
|
||||
fields: [MarketplaceAccessGrantTable.orgMembershipId],
|
||||
references: [MemberTable.id],
|
||||
}),
|
||||
team: one(TeamTable, {
|
||||
fields: [MarketplaceAccessGrantTable.teamId],
|
||||
references: [TeamTable.id],
|
||||
}),
|
||||
}))
|
||||
|
||||
export const pluginConfigObjectRelations = relations(PluginConfigObjectTable, ({ one }) => ({
|
||||
configObject: one(ConfigObjectTable, {
|
||||
fields: [PluginConfigObjectTable.configObjectId],
|
||||
@@ -643,6 +758,9 @@ export const connectorSourceTombstoneRelations = relations(ConnectorSourceTombst
|
||||
export const configObject = ConfigObjectTable
|
||||
export const configObjectVersion = ConfigObjectVersionTable
|
||||
export const plugin = PluginTable
|
||||
export const marketplace = MarketplaceTable
|
||||
export const marketplacePlugin = MarketplacePluginTable
|
||||
export const marketplaceAccessGrant = MarketplaceAccessGrantTable
|
||||
export const pluginConfigObject = PluginConfigObjectTable
|
||||
export const configObjectAccessGrant = ConfigObjectAccessGrantTable
|
||||
export const pluginAccessGrant = PluginAccessGrantTable
|
||||
|
||||
@@ -32,6 +32,9 @@ export const idTypesMapNameToPrefix = {
|
||||
plugin: "plg",
|
||||
pluginConfigObject: "pco",
|
||||
pluginAccessGrant: "pag",
|
||||
marketplace: "mkt",
|
||||
marketplacePlugin: "mkp",
|
||||
marketplaceAccessGrant: "mag",
|
||||
connectorAccount: "cac",
|
||||
connectorInstance: "cin",
|
||||
connectorInstanceAccessGrant: "cia",
|
||||
|
||||
Reference in New Issue
Block a user