in controlplane/src/core/bufservices/subgraph/moveSubgraph.ts [15:192]
export function moveSubgraph(
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);
const subgraphRepo = new SubgraphRepository(logger, opts.db, authContext.organizationId);
const featureFlagRepo = new FeatureFlagRepository(logger, opts.db, authContext.organizationId);
const orgWebhooks = new OrganizationWebhookService(
opts.db,
authContext.organizationId,
opts.logger,
opts.billingDefaultPlanId,
);
const subgraph = await subgraphRepo.byName(req.name, req.namespace);
if (!subgraph) {
return {
response: {
code: EnumStatusCode.ERR_NOT_FOUND,
details: `The subgraph "${req.name}" was not found.`,
},
compositionErrors: [],
deploymentErrors: [],
compositionWarnings: [],
};
}
if (subgraph.isFeatureSubgraph) {
return {
response: {
code: EnumStatusCode.ERR,
details: `Feature subgraphs cannot be moved.`,
},
compositionErrors: [],
deploymentErrors: [],
compositionWarnings: [],
};
}
const featureSubgraphs = await featureFlagRepo.getFeatureSubgraphsByBaseSubgraphId({
baseSubgraphId: subgraph.id,
});
if (featureSubgraphs.length > 0) {
return {
response: {
code: EnumStatusCode.ERR,
details: `Subgraphs that form the base of one or more feature subgraphs cannot be moved.`,
},
compositionErrors: [],
deploymentErrors: [],
compositionWarnings: [],
};
}
await opts.authorizer.authorize({
db: opts.db,
graph: {
targetId: subgraph.targetId,
targetType: 'subgraph',
},
headers: ctx.requestHeader,
authContext,
});
const { compositionErrors, updatedFederatedGraphs, deploymentErrors, compositionWarnings } =
await opts.db.transaction(async (tx) => {
const auditLogRepo = new AuditLogRepository(tx);
const subgraphRepo = new SubgraphRepository(logger, tx, authContext.organizationId);
const namespaceRepo = new NamespaceRepository(tx, authContext.organizationId);
const exists = await subgraphRepo.exists(req.name, req.newNamespace);
if (exists) {
throw new PublicError(
EnumStatusCode.ERR_ALREADY_EXISTS,
`The subgraph "${req.name}" already exists in the namespace "${req.newNamespace}".`,
);
}
const newNamespace = await namespaceRepo.byName(req.newNamespace);
if (!newNamespace) {
throw new PublicError(EnumStatusCode.ERR_NOT_FOUND, `Could not find namespace ${req.newNamespace}`);
}
const { compositionErrors, updatedFederatedGraphs, deploymentErrors, compositionWarnings } =
await subgraphRepo.move(
{
targetId: subgraph.targetId,
updatedBy: authContext.userId,
subgraphId: subgraph.id,
subgraphLabels: subgraph.labels,
currentNamespaceId: subgraph.namespaceId,
newNamespaceId: newNamespace.id,
},
opts.blobStorage,
{
cdnBaseUrl: opts.cdnBaseUrl,
jwtSecret: opts.admissionWebhookJWTSecret,
},
opts.chClient!,
);
await auditLogRepo.addAuditLog({
organizationId: authContext.organizationId,
auditAction: 'subgraph.moved',
action: 'moved',
actorId: authContext.userId,
auditableType: 'subgraph',
auditableDisplayName: subgraph.name,
actorDisplayName: authContext.userDisplayName,
apiKeyName: authContext.apiKeyName,
actorType: authContext.auth === 'api_key' ? 'api_key' : 'user',
targetNamespaceId: newNamespace.id,
targetNamespaceDisplayName: newNamespace.name,
});
return { compositionErrors, updatedFederatedGraphs, deploymentErrors, compositionWarnings };
});
for (const graph of updatedFederatedGraphs) {
orgWebhooks.send(
{
eventName: OrganizationEventName.FEDERATED_GRAPH_SCHEMA_UPDATED,
payload: {
federated_graph: {
id: graph.id,
name: graph.name,
namespace: graph.namespace,
},
organization: {
id: authContext.organizationId,
slug: authContext.organizationSlug,
},
errors: compositionErrors.length > 0 || deploymentErrors.length > 0,
actor_id: authContext.userId,
},
},
authContext.userId,
);
}
if (compositionErrors.length > 0) {
return {
response: {
code: EnumStatusCode.ERR_SUBGRAPH_COMPOSITION_FAILED,
},
compositionErrors,
compositionWarnings,
deploymentErrors: [],
};
}
if (deploymentErrors.length > 0) {
return {
response: {
code: EnumStatusCode.ERR_DEPLOYMENT_FAILED,
},
compositionErrors: [],
deploymentErrors,
compositionWarnings,
};
}
return {
response: {
code: EnumStatusCode.OK,
},
compositionErrors: [],
deploymentErrors: [],
compositionWarnings,
};
});
}