use std::sync::Arc;
use async_graphql::dynamic::{Object, Schema};
use deadpool_postgres::Pool;
use tokio::sync::RwLock;
use crate::graphql;
use crate::models::config::Config;
use crate::models::table::Table;
#[derive(Clone)]
pub struct TurboGraph {
schema: Arc<RwLock<Schema>>,
}
impl TurboGraph {
pub async fn new(config: Config) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let watch_pg = config.watch_pg;
let pool = Arc::new(crate::db::pool::resolve(config.pool)?);
let built_schema = rebuild_schema(&pool, &config.schemas).await?;
let schema = Arc::new(RwLock::new(built_schema));
if let Some(url) = watch_pg {
let url = url.0;
crate::db::watch::install_triggers(&pool).await?;
crate::db::watch::start_watching(url, pool, config.schemas, schema.clone()).await?;
}
Ok(Self { schema })
}
pub async fn execute(&self, request: async_graphql::Request) -> async_graphql::Response {
let schema = self.schema.read().await;
schema.execute(request).await
}
pub fn graphiql(endpoint: &str) -> String {
async_graphql::http::GraphiQLSource::build()
.endpoint(endpoint)
.finish()
}
pub async fn schema(&self) -> Schema {
self.schema.read().await.clone()
}
}
pub(crate) async fn rebuild_schema(
pool: &Arc<Pool>,
schemas: &[String],
) -> Result<Schema, Box<dyn std::error::Error + Send + Sync>> {
let tables = crate::db::introspect::get_tables(pool, schemas).await;
let tables: Vec<Arc<Table>> = tables.into_iter().map(|t| Arc::new(t)).collect();
let mut query_root = Object::new("Query");
let mut mutation_root = Object::new("Mutation");
struct TableArtefacts {
entity: Object,
query: async_graphql::dynamic::Field,
mutation: Vec<async_graphql::dynamic::Field>,
}
let mut artefacts = Vec::new();
for table in tables.iter() {
if table.omit_read() {
continue;
}
let entity = graphql::generate_entity(table.clone());
let gq = graphql::generate_query(table.clone(), pool.clone());
let gm = if !table.omit_create() || !table.omit_update() || !table.omit_delete() {
graphql::generate_mutation(table.clone(), pool.clone())
} else {
Vec::new()
};
artefacts.push(TableArtefacts {
entity,
query: gq,
mutation: gm,
});
}
let has_mutations = artefacts.iter().any(|a| !a.mutation.is_empty());
let mut builder = Schema::build(
"Query",
if has_mutations {
Some("Mutation")
} else {
None
},
None,
);
builder = builder.register(graphql::make_page_info_type());
for table in tables.iter() {
builder = builder
.register(table.condition_type())
.register(table.order_by_enum())
.register(table.connection_type())
.register(table.edge_type());
for ft in table.condition_filter_types() {
builder = builder.register(ft);
}
if !table.omit_create() {
builder = builder.register(table.create_type());
}
if !table.omit_update() {
builder = builder.register(table.update_type());
}
}
for a in artefacts {
query_root = query_root.field(a.query);
builder = builder.register(a.entity);
for field in a.mutation {
mutation_root = mutation_root.field(field);
}
}
builder = builder.register(query_root);
if has_mutations {
builder = builder.register(mutation_root);
}
let schema = builder.finish()?;
Ok(schema)
}