in controlplane/src/core/bufservices/federated-graph/moveFederatedGraph.ts [20:239]
export function moveFederatedGraph(
opts: RouterOptions,
req: MoveGraphRequest,
ctx: HandlerContext,
): Promise<PlainMessage<MoveGraphResponse>> {
let logger = getLogger(ctx, opts.logger);
return handleError<PlainMessage<MoveGraphResponse>>(ctx, logger, async () => {
const authContext = await opts.authenticator.authenticate(ctx.requestHeader);
logger = enrichLogger(ctx, logger, authContext);
return opts.db.transaction(async (tx) => {
const fedGraphRepo = new FederatedGraphRepository(logger, tx, authContext.organizationId);
const contractRepo = new ContractRepository(logger, tx, authContext.organizationId);
const orgWebhooks = new OrganizationWebhookService(
tx,
authContext.organizationId,
opts.logger,
opts.billingDefaultPlanId,
);
const auditLogRepo = new AuditLogRepository(tx);
const namespaceRepo = new NamespaceRepository(tx, authContext.organizationId);
const graph = await fedGraphRepo.byName(req.name, req.namespace, {
supportsFederation: true,
});
if (!graph) {
return {
response: {
code: EnumStatusCode.ERR_NOT_FOUND,
details: `Federated graph '${req.name}' not found`,
},
compositionErrors: [],
deploymentErrors: [],
compositionWarnings: [],
};
}
if (graph.contract?.id) {
return {
response: {
code: EnumStatusCode.ERR,
details: `Contract graphs cannot be moved individually. They will automatically be moved with the source graph.`,
},
compositionErrors: [],
deploymentErrors: [],
compositionWarnings: [],
};
}
const exists = await fedGraphRepo.exists(req.name, req.newNamespace);
if (exists) {
return {
response: {
code: EnumStatusCode.ERR_ALREADY_EXISTS,
details: `A federated graph '${req.name}' already exists in the namespace ${req.newNamespace}`,
},
compositionErrors: [],
deploymentErrors: [],
compositionWarnings: [],
};
}
await opts.authorizer.authorize({
db: opts.db,
graph: {
targetId: graph.targetId,
targetType: 'federatedGraph',
},
headers: ctx.requestHeader,
authContext,
});
const newNamespace = await namespaceRepo.byName(req.newNamespace);
if (!newNamespace) {
return {
response: {
code: EnumStatusCode.ERR_NOT_FOUND,
details: `Could not find namespace ${req.newNamespace}`,
},
compositionErrors: [],
deploymentErrors: [],
compositionWarnings: [],
};
}
const { compositionErrors, deploymentErrors, compositionWarnings } = await fedGraphRepo.move(
{
targetId: graph.targetId,
newNamespaceId: newNamespace.id,
updatedBy: authContext.userId,
federatedGraph: graph,
},
opts.blobStorage,
{
cdnBaseUrl: opts.cdnBaseUrl,
jwtSecret: opts.admissionWebhookJWTSecret,
},
opts.chClient!,
);
const allDeploymentErrors: PlainMessage<DeploymentError>[] = [];
const allCompositionErrors: PlainMessage<CompositionError>[] = [];
const allCompositionWarnings: PlainMessage<CompositionWarning>[] = [];
allCompositionErrors.push(...compositionErrors);
allDeploymentErrors.push(...deploymentErrors);
allCompositionWarnings.push(...compositionWarnings);
const movedGraphs = [graph];
const contracts = await contractRepo.bySourceFederatedGraphId(graph.id);
for (const contract of contracts) {
const contractGraph = await fedGraphRepo.byId(contract.downstreamFederatedGraphId);
if (!contractGraph) {
continue;
}
const {
compositionErrors: contractErrors,
deploymentErrors: contractDeploymentErrors,
compositionWarnings: contractWarnings,
} = await fedGraphRepo.move(
{
targetId: contractGraph.targetId,
newNamespaceId: newNamespace.id,
updatedBy: authContext.userId,
federatedGraph: contractGraph,
skipDeployment: compositionErrors.length > 0,
},
opts.blobStorage,
{
cdnBaseUrl: opts.cdnBaseUrl,
jwtSecret: opts.admissionWebhookJWTSecret,
},
opts.chClient!,
);
allCompositionErrors.push(...contractErrors);
allDeploymentErrors.push(...contractDeploymentErrors);
allCompositionWarnings.push(...contractWarnings);
movedGraphs.push(contractGraph);
}
for (const movedGraph of movedGraphs) {
await auditLogRepo.addAuditLog({
organizationId: authContext.organizationId,
auditAction: 'federated_graph.moved',
action: 'moved',
actorId: authContext.userId,
auditableType: 'federated_graph',
auditableDisplayName: movedGraph.name,
actorDisplayName: authContext.userDisplayName,
apiKeyName: authContext.apiKeyName,
actorType: authContext.auth === 'api_key' ? 'api_key' : 'user',
targetNamespaceId: newNamespace.id,
targetNamespaceDisplayName: newNamespace.name,
});
// Skip webhook since we do not deploy contracts on composition errors
if (movedGraph.contract && compositionErrors.length > 0) {
continue;
}
orgWebhooks.send(
{
eventName: OrganizationEventName.FEDERATED_GRAPH_SCHEMA_UPDATED,
payload: {
federated_graph: {
id: movedGraph.id,
name: movedGraph.name,
namespace: movedGraph.namespace,
},
organization: {
id: authContext.organizationId,
slug: authContext.organizationSlug,
},
errors: compositionErrors.length > 0 || deploymentErrors.length > 0,
actor_id: authContext.userId,
},
},
authContext.userId,
);
}
if (allCompositionErrors.length > 0) {
return {
response: {
code: EnumStatusCode.ERR_SUBGRAPH_COMPOSITION_FAILED,
},
deploymentErrors: [],
compositionErrors: allCompositionErrors,
compositionWarnings: allCompositionWarnings,
};
}
if (allDeploymentErrors.length > 0) {
return {
response: {
code: EnumStatusCode.ERR_DEPLOYMENT_FAILED,
},
deploymentErrors: allDeploymentErrors,
compositionErrors: [],
compositionWarnings: allCompositionWarnings,
};
}
return {
response: {
code: EnumStatusCode.OK,
},
compositionErrors: [],
deploymentErrors: [],
compositionWarnings: allCompositionWarnings,
};
});
});
}