export function updateMonograph()

in controlplane/src/core/bufservices/monograph/updateMonograph.ts [24:220]


export function updateMonograph(
  opts: RouterOptions,
  req: UpdateMonographRequest,
  ctx: HandlerContext,
): Promise<PlainMessage<UpdateMonographResponse>> {
  let logger = getLogger(ctx, opts.logger);

  return handleError<PlainMessage<UpdateMonographResponse>>(ctx, logger, async () => {
    const authContext = await opts.authenticator.authenticate(ctx.requestHeader);
    logger = enrichLogger(ctx, logger, authContext);

    req.namespace = req.namespace || DefaultNamespace;

    return opts.db.transaction(async (tx) => {
      const fedGraphRepo = new FederatedGraphRepository(logger, tx, authContext.organizationId);
      const subgraphRepo = new SubgraphRepository(logger, tx, authContext.organizationId);
      const auditLogRepo = new AuditLogRepository(tx);
      const orgWebhooks = new OrganizationWebhookService(
        tx,
        authContext.organizationId,
        opts.logger,
        opts.billingDefaultPlanId,
      );

      if (!authContext.hasWriteAccess) {
        return {
          response: {
            code: EnumStatusCode.ERR,
            details: `The user doesnt have the permissions to perform this operation`,
          },
          compositionErrors: [],
        };
      }

      if (req.subscriptionUrl && !isValidUrl(req.subscriptionUrl)) {
        return {
          response: {
            code: EnumStatusCode.ERR,
            details: `Subscription URL is not a valid URL`,
          },
          compositionErrors: [],
        };
      }

      if (req.routingUrl && !isValidUrl(req.routingUrl)) {
        return {
          response: {
            code: EnumStatusCode.ERR,
            details: `Routing URL is not a valid URL`,
          },
          compositionErrors: [],
        };
      }

      if (req.graphUrl && !isValidUrl(req.graphUrl)) {
        return {
          response: {
            code: EnumStatusCode.ERR,
            details: `Graph URL is not a valid URL`,
          },
          compositionErrors: [],
        };
      }

      const graph = await fedGraphRepo.byName(req.name, req.namespace, {
        supportsFederation: false,
      });
      if (!graph) {
        return {
          response: {
            code: EnumStatusCode.ERR_NOT_FOUND,
            details: `Monograph '${req.name}' not found`,
          },
          compositionErrors: [],
        };
      }

      const subgraphs = await subgraphRepo.listByFederatedGraph({
        federatedGraphTargetId: graph.targetId,
      });

      if (subgraphs.length === 0) {
        return {
          response: {
            code: EnumStatusCode.ERR_NOT_FOUND,
            details: `Monograph '${req.name}' does not have any subgraphs`,
          },
          compositionErrors: [],
        };
      }

      const subgraph = subgraphs[0];

      // check if the user is authorized to perform the action
      await opts.authorizer.authorize({
        db: opts.db,
        graph: {
          targetId: graph.targetId,
          targetType: 'federatedGraph',
        },
        headers: ctx.requestHeader,
        authContext,
      });

      await opts.authorizer.authorize({
        db: opts.db,
        graph: {
          targetId: subgraphs[0].targetId,
          targetType: 'subgraph',
        },
        headers: ctx.requestHeader,
        authContext,
      });

      await fedGraphRepo.update({
        targetId: graph.targetId,
        labelMatchers: [],
        routingUrl: req.routingUrl,
        updatedBy: authContext.userId,
        readme: req.readme,
        blobStorage: opts.blobStorage,
        namespaceId: graph.namespaceId,
        unsetLabelMatchers: false,
        admissionConfig: {
          cdnBaseUrl: opts.cdnBaseUrl,
          jwtSecret: opts.admissionWebhookJWTSecret,
        },
        admissionWebhookURL: req.admissionWebhookURL,
        admissionWebhookSecret: req.admissionWebhookSecret,
        chClient: opts.chClient!,
      });

      await subgraphRepo.update(
        {
          targetId: subgraph.targetId,
          labels: [],
          unsetLabels: false,
          subscriptionUrl: req.subscriptionUrl,
          routingUrl: req.graphUrl,
          subscriptionProtocol:
            req.subscriptionProtocol === undefined ? undefined : formatSubscriptionProtocol(req.subscriptionProtocol),
          websocketSubprotocol:
            req.websocketSubprotocol === undefined ? undefined : formatWebsocketSubprotocol(req.websocketSubprotocol),
          updatedBy: authContext.userId,
          readme: req.readme,
          namespaceId: subgraph.namespaceId,
        },
        opts.blobStorage,
        {
          cdnBaseUrl: opts.cdnBaseUrl,
          webhookJWTSecret: opts.admissionWebhookJWTSecret,
        },
        opts.chClient!,
      );

      await auditLogRepo.addAuditLog({
        organizationId: authContext.organizationId,
        auditAction: 'monograph.updated',
        action: 'updated',
        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,
      });

      orgWebhooks.send(
        {
          eventName: OrganizationEventName.MONOGRAPH_SCHEMA_UPDATED,
          payload: {
            monograph: {
              id: graph.id,
              name: graph.name,
              namespace: graph.namespace,
            },
            organization: {
              id: authContext.organizationId,
              slug: authContext.organizationSlug,
            },
            actor_id: authContext.userId,
          },
        },
        authContext.userId,
      );

      return {
        response: {
          code: EnumStatusCode.OK,
        },
        compositionErrors: [],
      };
    });
  });
}