pub mod blob; pub mod branch; pub mod cache_helper; pub mod commit; pub mod tag; pub mod tree; use std::path::PathBuf; use actix_web::{HttpResponse, web}; use cache::AppCache; use db::database::AppDatabase; use juniper::{ EmptyMutation, EmptySubscription, FieldResult, RootNode, graphql_object, }; use serde_json::json; use track::CounterVec; use crate::{ bare::GitBare, graphql::{ blob::{BlobResult, resolve_blob, resolve_blob_size}, branch::{ BranchGql, BranchSummaryGql, resolve_branch, resolve_branch_summary, resolve_branches, resolve_head_branch, }, commit::{ CommitGql, CommitSummaryGql, resolve_commit, resolve_commit_history, resolve_commit_summary, }, tag::{ TagGql, TagSummaryGql, resolve_tag, resolve_tag_summary, resolve_tags, }, tree::{TreeEntryGql, TreeInfoGql, resolve_tree, resolve_tree_entries}, }, }; type Schema = RootNode< GraphqlQuery, EmptyMutation, EmptySubscription, >; fn schema() -> Schema { Schema::new( GraphqlQuery, EmptyMutation::::new(), EmptySubscription::::new(), ) } pub async fn graphql_handle( path: web::Path<(String, String)>, state: web::Data, body: web::Json, ) -> HttpResponse { let (wk, repo_name) = path.into_inner(); let repo = match state.git_state.repo(wk, repo_name).await { Ok(repo) => repo, Err(err) => { return HttpResponse::InternalServerError().json(json!({ "message": err.to_string() })); } }; let ctx = GraphqlContext { repo: GitBare { bare_dir: PathBuf::from(&repo.repo.storage_path), }, cache: state.git_state.cache.clone(), db: state.git_state.db.clone(), }; let schema = schema(); let response = body.execute(&schema, &ctx).await; let status_code = if response.is_ok() { 200 } else { 400 }; record_graphql_metric(&state, response.is_ok()); HttpResponse::build( actix_web::http::StatusCode::from_u16(status_code).unwrap(), ) .content_type("application/json") .json(response) } #[derive(Clone)] pub struct GraphqlContext { pub repo: GitBare, pub cache: AppCache, pub db: AppDatabase, } impl juniper::Context for GraphqlContext {} pub struct GraphqlQuery; fn to_field_error(e: anyhow::Error) -> juniper::FieldError { juniper::FieldError::new(e.to_string(), juniper::Value::null()) } #[graphql_object] #[graphql(context = GraphqlContext)] impl GraphqlQuery { fn api_version() -> &'static str { env!("CARGO_PKG_VERSION") } async fn head_branch(ctx: &GraphqlContext) -> FieldResult { resolve_head_branch(ctx).await.map_err(to_field_error) } async fn branches(ctx: &GraphqlContext) -> FieldResult> { resolve_branches(ctx).await.map_err(to_field_error) } async fn branch( ctx: &GraphqlContext, name: String, ) -> FieldResult { resolve_branch(ctx, name).await.map_err(to_field_error) } async fn branch_summary( ctx: &GraphqlContext, ) -> FieldResult { resolve_branch_summary(ctx).await.map_err(to_field_error) } async fn tags(ctx: &GraphqlContext) -> FieldResult> { resolve_tags(ctx).await.map_err(to_field_error) } async fn tag(ctx: &GraphqlContext, name: String) -> FieldResult { resolve_tag(ctx, name).await.map_err(to_field_error) } async fn tag_summary(ctx: &GraphqlContext) -> FieldResult { resolve_tag_summary(ctx).await.map_err(to_field_error) } async fn commit( ctx: &GraphqlContext, oid: String, ) -> FieldResult { resolve_commit(ctx, oid).await.map_err(to_field_error) } async fn commit_history( ctx: &GraphqlContext, limit: Option, skip: Option, sort: Option, ) -> FieldResult> { resolve_commit_history(ctx, limit, skip, sort) .await .map_err(to_field_error) } async fn commit_summary( ctx: &GraphqlContext, ) -> FieldResult { resolve_commit_summary(ctx).await.map_err(to_field_error) } async fn tree( ctx: &GraphqlContext, oid: String, ) -> FieldResult { resolve_tree(ctx, oid).await.map_err(to_field_error) } async fn tree_entries( ctx: &GraphqlContext, tree_oid: String, ) -> FieldResult> { resolve_tree_entries(ctx, tree_oid) .await .map_err(to_field_error) } async fn blob_size(ctx: &GraphqlContext, oid: String) -> FieldResult { resolve_blob_size(ctx, oid).await.map_err(to_field_error) } async fn blob( ctx: &GraphqlContext, oid: String, ) -> FieldResult { resolve_blob(ctx, oid).await.map_err(to_field_error) } } fn record_graphql_metric(state: &crate::http::HttpAppState, ok: bool) { if let Some(reg) = &state.metrics { graphql_counter(reg) .with_label_values(&[if ok { "success" } else { "error" }]) .inc(); } } fn graphql_counter(registry: &track::MetricsRegistry) -> CounterVec { registry .register_counter_vec( "git_graphql_queries_total", "Total Git GraphQL queries", &["outcome"], ) .expect("failed to register git_graphql_queries_total") }