diff --git a/backend/graphql/Cargo.toml b/backend/graphql/Cargo.toml index 8da484d64..2e0ad7d79 100644 --- a/backend/graphql/Cargo.toml +++ b/backend/graphql/Cargo.toml @@ -14,6 +14,7 @@ util = { path = "../util" } graphql_configuration = { path = "configuration" } graphql_drug_interactions = { path = "drug_interactions" } graphql_core = { path = "core" } +graphql_v1_core = { path = "../graphql_v1/core" } graphql_barcode = { path = "barcode" } graphql_types = { path = "types" } graphql_general = { path = "general" } diff --git a/backend/graphql/core/Cargo.toml b/backend/graphql/core/Cargo.toml index 6df2528ac..cb7e4ea25 100644 --- a/backend/graphql/core/Cargo.toml +++ b/backend/graphql/core/Cargo.toml @@ -12,6 +12,7 @@ doctest = false repository = { path = "../../repository" } service = { path = "../../service" } util = { path = "../../util" } +graphql_v1_core = { path = "../../graphql_v1/core" } actix-web = { version = "4.0.1", default-features = false, features = ["macros", "cookies"] } anymap = "0.12" @@ -24,4 +25,4 @@ serde = "1.0.126" serde_json = "1.0.66" thiserror = "1.0.30" tokio = { version = "1.29", features = ["macros" ] } -async-std = "1.12" +async-std = { version = "1", features = ["attributes", "tokio1"] } diff --git a/backend/graphql/core/src/lib.rs b/backend/graphql/core/src/lib.rs index 88b7e64c1..42dc999ac 100644 --- a/backend/graphql/core/src/lib.rs +++ b/backend/graphql/core/src/lib.rs @@ -5,6 +5,8 @@ pub mod simple_generic_errors; pub mod standard_graphql_error; pub mod test_helpers; +use graphql_v1_core::loader::LoaderRegistry as LoaderRegistryV1; + use std::sync::Arc; use actix_web::cookie::Cookie; @@ -34,6 +36,7 @@ pub trait SelfRequest: Send + Sync { pub trait ContextExt { fn get_connection_manager(&self) -> &StorageConnectionManager; fn get_loader(&self) -> &T; + fn get_loader_v1(&self) -> &T; fn service_provider(&self) -> Arc; fn service_context( &self, @@ -55,6 +58,10 @@ impl<'a> ContextExt for Context<'a> { self.data_unchecked::>().get::() } + fn get_loader_v1(&self) -> &T { + self.data_unchecked::>().get::() + } + fn service_provider(&self) -> Arc { self.data_unchecked::>() .clone() diff --git a/backend/graphql/lib.rs b/backend/graphql/lib.rs index f568c1ba7..ad8f6f269 100644 --- a/backend/graphql/lib.rs +++ b/backend/graphql/lib.rs @@ -16,6 +16,7 @@ use graphql_general::GeneralQueries; use graphql_universal_codes::{UniversalCodesMutations, UniversalCodesQueries}; use graphql_universal_codes_v1::UniversalCodesV1Queries; use graphql_user_account::{UserAccountMutations, UserAccountQueries}; +use graphql_v1_core::loader::LoaderRegistry as LoaderRegistryV1; use logger::{RequestLogger, ResponseLogger}; use repository::StorageConnectionManager; @@ -76,6 +77,7 @@ pub fn schema_builder() -> Builder { pub fn build_schema( connection_manager: Data, loader_registry: Data, + loader_registry_v1: Data, service_provider: Data, auth_data: Data, settings_data: Data, @@ -86,6 +88,7 @@ pub fn build_schema( let mut builder = schema_builder() .data(connection_manager) .data(loader_registry) + .data(loader_registry_v1) .data(service_provider) .data(auth_data) .data(settings_data) @@ -120,6 +123,7 @@ impl SelfRequest for SelfRequestImpl { pub fn config( connection_manager: Data, loader_registry: Data, + loader_registry_v1: Data, service_provider: Data, auth_data: Data, settings_data: Data, @@ -130,6 +134,7 @@ pub fn config( schema: build_schema( connection_manager.clone(), loader_registry.clone(), + loader_registry_v1.clone(), service_provider.clone(), auth_data.clone(), settings_data.clone(), @@ -142,6 +147,7 @@ pub fn config( let schema = build_schema( connection_manager, loader_registry, + loader_registry_v1, service_provider, auth_data, settings_data, diff --git a/backend/graphql_v1/Cargo.toml b/backend/graphql_v1/Cargo.toml index c7fc9f7c7..f32b168ec 100644 --- a/backend/graphql_v1/Cargo.toml +++ b/backend/graphql_v1/Cargo.toml @@ -23,6 +23,7 @@ async-graphql = { version = "3.0.35", features = [ ] } async-graphql-actix-web = "3.0.35" async-trait = "0.1.30" +tokio = { version = "1.29", features = ["macros"] } log = "0.4.14" diff --git a/backend/graphql_v1/core/Cargo.toml b/backend/graphql_v1/core/Cargo.toml index 47285fb74..b18d3e84b 100644 --- a/backend/graphql_v1/core/Cargo.toml +++ b/backend/graphql_v1/core/Cargo.toml @@ -9,7 +9,13 @@ doctest = false [dependencies] +dgraph = { path = "../../dgraph" } service = { path = "../../service" } actix-web = { version = "4.0.1", default-features = false, features = ["macros", "cookies"] } +anymap = "0.12" async-graphql = { version = "3.0.35", features = ["dataloader", "chrono"] } +async-graphql-actix-web = "3.0.35" +async-trait = "0.1.30" +tokio = { version = "1.29", features = ["macros" ] } +async-std = { version = "1", features = ["attributes", "tokio1"] } diff --git a/backend/graphql_v1/core/src/lib.rs b/backend/graphql_v1/core/src/lib.rs index 97120f939..edc881120 100644 --- a/backend/graphql_v1/core/src/lib.rs +++ b/backend/graphql_v1/core/src/lib.rs @@ -3,15 +3,23 @@ use std::sync::Arc; use actix_web::web::Data; use async_graphql::Context; +pub mod loader; + +use loader::LoaderRegistry; use service::service_provider::ServiceProvider; #[allow(clippy::borrowed_box)] // Sugar that helps make things neater and avoid errors that would only crop up at runtime. pub trait ContextExt { fn service_provider(&self) -> Arc; + fn get_loader_v1(&self) -> &T; } impl<'a> ContextExt for Context<'a> { + fn get_loader_v1(&self) -> &T { + self.data_unchecked::>().get::() + } + fn service_provider(&self) -> Arc { self.data_unchecked::>() .clone() diff --git a/backend/graphql_v1/core/src/loader/loader_registry.rs b/backend/graphql_v1/core/src/loader/loader_registry.rs new file mode 100644 index 000000000..a45344b28 --- /dev/null +++ b/backend/graphql_v1/core/src/loader/loader_registry.rs @@ -0,0 +1,39 @@ +use actix_web::web::Data; +use anymap::{any::Any, Map}; +use async_graphql::dataloader::DataLoader; + +use service::service_provider::ServiceProvider; + +use super::ProductLoader; + +pub type LoaderMap = Map; +pub type AnyLoader = dyn Any + Send + Sync; + +pub struct LoaderRegistry { + pub loaders: LoaderMap, +} + +impl LoaderRegistry { + pub fn get(&self) -> &T { + match self.loaders.get::() { + Some(loader) => loader, + None => unreachable!("{} not found", std::any::type_name::()), + } + } +} + +pub async fn get_loaders_v1(service_provider: Data) -> LoaderMap { + let mut loaders: LoaderMap = LoaderMap::new(); + + let dgraph_client = service_provider.dgraph_client(); + + let product_loader = DataLoader::new( + ProductLoader { + dgraph_client: dgraph_client.clone(), + }, + async_std::task::spawn, + ); + loaders.insert(product_loader); + + loaders +} diff --git a/backend/graphql_v1/core/src/loader/mod.rs b/backend/graphql_v1/core/src/loader/mod.rs new file mode 100644 index 000000000..577fb9b90 --- /dev/null +++ b/backend/graphql_v1/core/src/loader/mod.rs @@ -0,0 +1,5 @@ +mod loader_registry; +mod product; + +pub use loader_registry::{get_loaders_v1, LoaderMap, LoaderRegistry}; +pub use product::*; diff --git a/backend/graphql_v1/core/src/loader/product.rs b/backend/graphql_v1/core/src/loader/product.rs new file mode 100644 index 000000000..f90c29264 --- /dev/null +++ b/backend/graphql_v1/core/src/loader/product.rs @@ -0,0 +1,49 @@ +use async_graphql::dataloader::*; +use async_graphql::*; +use dgraph::{entity_with_parents_by_code, DgraphClient, Entity}; +use std::collections::HashMap; + +pub struct ProductLoader { + pub dgraph_client: DgraphClient, +} + +#[async_trait::async_trait] +impl Loader for ProductLoader { + type Value = Option; + type Error = async_graphql::Error; + + async fn load( + &self, + record_ids: &[String], + ) -> Result, Self::Error> { + let mut result_map: HashMap> = HashMap::new(); + + for record_id in record_ids { + // Get the entity with all parents with the given record_id + let entity = + entity_with_parents_by_code(&self.dgraph_client, record_id.to_owned()).await?; + let product_entity = get_product_from_entity(entity); + + result_map.insert(record_id.to_string(), product_entity); + } + Ok(result_map) + } +} + +// Recursively get the product entity from the given entity +fn get_product_from_entity(entity: Option) -> Option { + println!("get_product_from_entity: {:?}", entity); + + let entity = match entity { + Some(entity) => entity, + None => return None, + }; + if entity.r#type == "Product" { + return Some(entity); + } + + if entity.parents.len() > 0 { + return get_product_from_entity(Some(entity.parents[0].clone())); + } + None +} diff --git a/backend/graphql_v1/lib.rs b/backend/graphql_v1/lib.rs index 11f1d1e63..fbaf5127c 100644 --- a/backend/graphql_v1/lib.rs +++ b/backend/graphql_v1/lib.rs @@ -9,6 +9,7 @@ use async_graphql::{EmptyMutation, EmptySubscription, MergedObject, SchemaBuilde use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse}; use graphql_universal_codes_v1::UniversalCodesV1Queries; +use graphql_v1_core::loader::LoaderRegistry; use logger::{RequestLogger, ResponseLogger}; use repository::StorageConnectionManager; use service::service_provider::ServiceProvider; @@ -33,12 +34,14 @@ pub fn schema_builder() -> Builder { pub fn build_schema( connection_manager: Data, + loader_registry: Data, service_provider: Data, settings_data: Data, include_logger: bool, ) -> Schema { let mut builder = schema_builder() .data(connection_manager) + .data(loader_registry) .data(service_provider) .data(settings_data); @@ -51,11 +54,18 @@ pub fn build_schema( pub fn config( connection_manager: Data, + loader_registry: Data, service_provider: Data, settings_data: Data, ) -> impl FnOnce(&mut actix_web::web::ServiceConfig) { |cfg| { - let schema = build_schema(connection_manager, service_provider, settings_data, true); + let schema = build_schema( + connection_manager, + loader_registry, + service_provider, + settings_data, + true, + ); cfg.app_data(Data::new(schema)) .service( diff --git a/backend/graphql_v1/universal_codes/src/types/entity.rs b/backend/graphql_v1/universal_codes/src/types/entity.rs index cef1d7e6f..b49ca58b4 100644 --- a/backend/graphql_v1/universal_codes/src/types/entity.rs +++ b/backend/graphql_v1/universal_codes/src/types/entity.rs @@ -1,6 +1,11 @@ +use async_graphql::dataloader::DataLoader; use async_graphql::*; + use dgraph::Entity; +use graphql_v1_core::loader::ProductLoader; +use graphql_v1_core::ContextExt; + use crate::AlternativeNameType; use crate::BarcodeType; @@ -87,9 +92,17 @@ impl EntityType { &self.alternative_names } - pub async fn product(&self) -> Option { - // TODO: Probably a loader? Implement Product Lookup - None + pub async fn product( + &self, + ctx: &Context<'_>, + ) -> Result, async_graphql::Error> { + let loader = ctx.get_loader_v1::>(); + let result = loader + .load_one(self.code.to_string()) + .await? + .unwrap_or_default(); + + Ok(result.map(EntityType::from_domain)) } pub async fn parents(&self) -> &Vec { diff --git a/backend/server/Cargo.toml b/backend/server/Cargo.toml index 03026fc86..600b8e84b 100644 --- a/backend/server/Cargo.toml +++ b/backend/server/Cargo.toml @@ -34,6 +34,7 @@ maintainer-scripts = "debian/scripts" graphql = { path = "../graphql" } graphql_v1 = { path = "../graphql_v1" } graphql_core = { path = "../graphql/core" } +graphql_v1_core = { path = "../graphql_v1/core" } graphql_types = { path = "../graphql/types" } dgraph = {path = "../dgraph"} repository = { path = "../repository" } diff --git a/backend/server/src/lib.rs b/backend/server/src/lib.rs index 6fc741d90..bfefd3323 100644 --- a/backend/server/src/lib.rs +++ b/backend/server/src/lib.rs @@ -5,6 +5,8 @@ use crate::{ use self::middleware::{compress as compress_middleware, logger as logger_middleware}; use graphql_core::loader::{get_loaders, LoaderRegistry}; +use graphql_v1_core::loader::get_loaders_v1; +use graphql_v1_core::loader::LoaderRegistry as LoaderRegistryV1; use graphql::config as graphql_config; use graphql_v1::config as graphql_v1_config; @@ -69,6 +71,11 @@ async fn run_server( let loaders = get_loaders(&connection_manager, service_provider_data.clone()).await; let loader_registry_data = Data::new(LoaderRegistry { loaders }); + let loaders_v1 = get_loaders_v1(service_provider_data.clone()).await; + let loader_registry_data_v1 = Data::new(LoaderRegistryV1 { + loaders: loaders_v1, + }); + let settings_data = Data::new(config_settings.clone()); let restart_switch = Data::new(restart_switch); @@ -140,6 +147,7 @@ async fn run_server( .configure(graphql_config( connection_manager_data_app.clone(), loader_registry_data.clone(), + loader_registry_data_v1.clone(), service_provider_data.clone(), auth_data.clone(), settings_data.clone(), @@ -147,6 +155,7 @@ async fn run_server( )) .configure(graphql_v1_config( connection_manager_data_app.clone(), + loader_registry_data_v1.clone(), service_provider_data.clone(), settings_data.clone(), )) diff --git a/backend/service/src/service_provider.rs b/backend/service/src/service_provider.rs index 597065806..29ad84247 100644 --- a/backend/service/src/service_provider.rs +++ b/backend/service/src/service_provider.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use dgraph::DgraphClient; use repository::{RepositoryError, StorageConnection, StorageConnectionManager}; use crate::{ @@ -87,4 +88,14 @@ impl ServiceProvider { pub fn connection(&self) -> Result { self.connection_manager.connection() } + + pub fn dgraph_client(&self) -> DgraphClient { + let url = format!( + "{}:{}/graphql", + self.settings.dgraph.host.clone(), + self.settings.dgraph.port + ); + + DgraphClient::new(&url) + } }