controlplane/src/core/bufservices/monograph/createMonograph.ts (183 lines of code) (raw):

import { PlainMessage } from '@bufbuild/protobuf'; import { HandlerContext } from '@connectrpc/connect'; import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb'; import { CreateMonographRequest, CreateMonographResponse, } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb'; import { isValidUrl, joinLabel } from '@wundergraph/cosmo-shared'; import { AuditLogRepository } from '../../repositories/AuditLogRepository.js'; import { FederatedGraphRepository } from '../../repositories/FederatedGraphRepository.js'; import { DefaultNamespace, NamespaceRepository } from '../../repositories/NamespaceRepository.js'; import { OrganizationRepository } from '../../repositories/OrganizationRepository.js'; import { SubgraphRepository } from '../../repositories/SubgraphRepository.js'; import type { RouterOptions } from '../../routes.js'; import { createRandomInternalLabel, enrichLogger, formatSubscriptionProtocol, formatWebsocketSubprotocol, getLogger, handleError, isValidGraphName, } from '../../util.js'; export function createMonograph( opts: RouterOptions, req: CreateMonographRequest, ctx: HandlerContext, ): Promise<PlainMessage<CreateMonographResponse>> { let logger = getLogger(ctx, opts.logger); return handleError<PlainMessage<CreateMonographResponse>>(ctx, logger, async () => { return await opts.db.transaction(async (tx) => { req.namespace = req.namespace || DefaultNamespace; const authContext = await opts.authenticator.authenticate(ctx.requestHeader); logger = enrichLogger(ctx, logger, authContext); const orgRepo = new OrganizationRepository(logger, opts.db, opts.billingDefaultPlanId); const fedGraphRepo = new FederatedGraphRepository(logger, opts.db, authContext.organizationId); const subgraphRepo = new SubgraphRepository(logger, opts.db, authContext.organizationId); const auditLogRepo = new AuditLogRepository(opts.db); const namespaceRepo = new NamespaceRepository(opts.db, authContext.organizationId); if (!authContext.hasWriteAccess) { return { response: { code: EnumStatusCode.ERR, details: `The user doesn't have the permissions to perform this operation`, }, }; } const namespace = await namespaceRepo.byName(req.namespace); if (!namespace) { return { response: { code: EnumStatusCode.ERR_NOT_FOUND, details: `Could not find namespace ${req.namespace}`, }, }; } if (await fedGraphRepo.exists(req.name, req.namespace)) { return { response: { code: EnumStatusCode.ERR_ALREADY_EXISTS, details: `Graph '${req.name}' already exists in the namespace`, }, }; } if (await subgraphRepo.exists(req.name, req.namespace)) { return { response: { code: EnumStatusCode.ERR_ALREADY_EXISTS, details: `The subgraph ${req.name} being created for the monograph already exists in the namespace`, }, }; } if (!isValidUrl(req.routingUrl)) { return { response: { code: EnumStatusCode.ERR, details: `Routing URL is not a valid URL`, }, }; } if (!isValidUrl(req.graphUrl)) { return { response: { code: EnumStatusCode.ERR, details: `Graph URL is not a valid URL`, }, }; } if (!isValidGraphName(req.name)) { return { response: { code: EnumStatusCode.ERR_INVALID_NAME, details: `The name of the monograph is invalid. Name should start and end with an alphanumeric character. Only '.', '_', '@', '/', and '-' are allowed as separators in between and must be between 1 and 100 characters in length.`, }, compositionErrors: [], deploymentErrors: [], compositionWarnings: [], }; } const count = await fedGraphRepo.count(); const feature = await orgRepo.getFeature({ organizationId: authContext.organizationId, featureId: 'federated-graphs', }); const limit = feature?.limit === -1 ? undefined : feature?.limit; if (limit && count >= limit) { return { response: { code: EnumStatusCode.ERR_LIMIT_REACHED, details: `The organization reached the limit of federated graphs and monographs`, }, }; } const label = createRandomInternalLabel(); const labelMatchers = [joinLabel(label)]; const subgraph = await subgraphRepo.create({ name: req.name, namespace: req.namespace, namespaceId: namespace.id, createdBy: authContext.userId, labels: [label], routingUrl: req.graphUrl, isEventDrivenGraph: false, readme: req.readme, subscriptionUrl: req.subscriptionUrl, subscriptionProtocol: req.subscriptionProtocol === undefined ? undefined : formatSubscriptionProtocol(req.subscriptionProtocol), websocketSubprotocol: req.websocketSubprotocol === undefined ? undefined : formatWebsocketSubprotocol(req.websocketSubprotocol), }); if (!subgraph) { return { response: { code: EnumStatusCode.ERR, details: `Could not create monograph`, }, }; } const graph = await fedGraphRepo.create({ name: req.name, createdBy: authContext.userId, labelMatchers, routingUrl: req.routingUrl, readme: req.readme, namespace: req.namespace, namespaceId: namespace.id, admissionWebhookURL: req.admissionWebhookURL, admissionWebhookSecret: req.admissionWebhookSecret, supportsFederation: false, }); if (!graph) { return { response: { code: EnumStatusCode.ERR, details: `Could not create monograph`, }, }; } await fedGraphRepo.createGraphCryptoKeyPairs({ federatedGraphId: graph.id, organizationId: authContext.organizationId, }); await auditLogRepo.addAuditLog({ organizationId: authContext.organizationId, auditAction: 'monograph.created', action: 'created', actorId: authContext.userId, auditableType: 'monograph', auditableDisplayName: graph.name, actorDisplayName: authContext.userDisplayName, apiKeyName: authContext.apiKeyName, actorType: authContext.auth === 'api_key' ? 'api_key' : 'user', targetNamespaceId: graph.namespaceId, targetNamespaceDisplayName: graph.namespace, }); return { response: { code: EnumStatusCode.OK, }, }; }); }); }