From eaa9cbc621c88cb45aabd9fed3e5228d5f7445c7 Mon Sep 17 00:00:00 2001 From: iamvigneshwars Date: Wed, 21 Feb 2024 12:02:55 +0000 Subject: [PATCH 1/7] Initial Table, graphql endpoints and policy for compound soaking service --- backend/Cargo.lock | 21 ++++ backend/Cargo.toml | 1 + backend/compound_soaking/Cargo.toml | 23 ++++ backend/compound_soaking/README.md | 3 + backend/compound_soaking/src/entities/mod.rs | 1 + .../src/entities/soak_compound.rs | 28 +++++ backend/compound_soaking/src/graphql/mod.rs | 15 +++ .../src/graphql/soak_compound_res.rs | 76 ++++++++++++ backend/compound_soaking/src/main.rs | 114 ++++++++++++++++++ backend/compound_soaking/src/migrator.rs | 30 +++++ policies/compound_soaking.rego | 14 +++ 11 files changed, 326 insertions(+) create mode 100644 backend/compound_soaking/Cargo.toml create mode 100644 backend/compound_soaking/README.md create mode 100644 backend/compound_soaking/src/entities/mod.rs create mode 100644 backend/compound_soaking/src/entities/soak_compound.rs create mode 100644 backend/compound_soaking/src/graphql/mod.rs create mode 100644 backend/compound_soaking/src/graphql/soak_compound_res.rs create mode 100644 backend/compound_soaking/src/main.rs create mode 100644 backend/compound_soaking/src/migrator.rs create mode 100644 policies/compound_soaking.rego diff --git a/backend/Cargo.lock b/backend/Cargo.lock index adfec3fc..aee8176e 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1255,6 +1255,27 @@ dependencies = [ "uuid", ] +[[package]] +name = "compound_soaking" +version = "0.1.0" +dependencies = [ + "async-graphql", + "axum", + "chrono", + "clap 4.5.1", + "dotenvy", + "graphql_endpoints", + "opa_client", + "sea-orm", + "sea-orm-migration", + "the_paginator", + "tokio", + "tracing", + "tracing-subscriber", + "url", + "uuid", +] + [[package]] name = "concurrent-queue" version = "2.4.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index ffa16f80..aad0aa66 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -5,6 +5,7 @@ members = [ "chimp_protocol", "crystal_library", "compound_library", + "compound_soaking", "graphql_endpoints", "graphql_event_broker", "opa_client", diff --git a/backend/compound_soaking/Cargo.toml b/backend/compound_soaking/Cargo.toml new file mode 100644 index 00000000..4b3c059d --- /dev/null +++ b/backend/compound_soaking/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "compound_soaking" +version = "0.1.0" +edition = "2021" + +[dependencies] +async-graphql = { workspace = true } +axum = { workspace = true } +clap = { workspace = true } +chrono ={ workspace = true } +dotenvy = { workspace = true } +graphql_endpoints = { path = "../graphql_endpoints" } +opa_client = { path = "../opa_client", features = ["graphql"] } +sea-orm = { workspace = true, features = ["sqlx-postgres"] } +sea-orm-migration = { workspace = true } +the_paginator = { version = "0.1.0", path = "../the_paginator", features = [ + "async-graphql", +] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +tokio = { workspace = true } +url = { workspace = true } +uuid = { workspace = true } diff --git a/backend/compound_soaking/README.md b/backend/compound_soaking/README.md new file mode 100644 index 00000000..c27bab40 --- /dev/null +++ b/backend/compound_soaking/README.md @@ -0,0 +1,3 @@ +# Compound Soaking Service + +This service keeps track of all the soaked compounds by various projects at the Diamond Light Source. diff --git a/backend/compound_soaking/src/entities/mod.rs b/backend/compound_soaking/src/entities/mod.rs new file mode 100644 index 00000000..f073bd5b --- /dev/null +++ b/backend/compound_soaking/src/entities/mod.rs @@ -0,0 +1 @@ +pub mod soak_compound; diff --git a/backend/compound_soaking/src/entities/soak_compound.rs b/backend/compound_soaking/src/entities/soak_compound.rs new file mode 100644 index 00000000..723d005c --- /dev/null +++ b/backend/compound_soaking/src/entities/soak_compound.rs @@ -0,0 +1,28 @@ +use async_graphql::SimpleObject; +use chrono::{DateTime, Utc}; +use sea_orm::{ + ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait, + EnumIter, PrimaryKeyTrait, +}; +use uuid::Uuid; + +#[derive(Clone, Debug, DeriveEntityModel, Eq, PartialEq, SimpleObject)] +#[sea_orm(table_name = "soak_compound")] +#[graphql(name = "soak_compound")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub compound_plate_id: Uuid, + #[sea_orm(primary_key, auto_increment = false)] + pub compound_well_number: i16, + #[sea_orm(primary_key, auto_increment = false)] + pub crystal_plate_id: Uuid, + #[sea_orm(primary_key, auto_increment = false)] + pub crystal_well_number: i16, + pub operator_id: String, + pub timestamp: DateTime, +} + +#[derive(Clone, Copy, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/backend/compound_soaking/src/graphql/mod.rs b/backend/compound_soaking/src/graphql/mod.rs new file mode 100644 index 00000000..238dbf29 --- /dev/null +++ b/backend/compound_soaking/src/graphql/mod.rs @@ -0,0 +1,15 @@ +pub mod soak_compound_res; + +use async_graphql::{EmptySubscription, MergedObject, Schema, SchemaBuilder}; +use soak_compound_res::{SoakCompoundMutation, SoakCompoundQuery}; + +#[derive(Debug, Clone, MergedObject, Default)] +pub struct Query(SoakCompoundQuery); + +#[derive(Debug, Clone, MergedObject, Default)] +pub struct Mutation(SoakCompoundMutation); + +pub type RootSchema = Schema; +pub fn root_schema_builder() -> SchemaBuilder { + Schema::build(Query::default(), Mutation::default(), EmptySubscription).enable_federation() +} diff --git a/backend/compound_soaking/src/graphql/soak_compound_res.rs b/backend/compound_soaking/src/graphql/soak_compound_res.rs new file mode 100644 index 00000000..30f6c9fc --- /dev/null +++ b/backend/compound_soaking/src/graphql/soak_compound_res.rs @@ -0,0 +1,76 @@ +use crate::entities::soak_compound; +use async_graphql::{Context, Object}; +use chrono::Utc; +use opa_client::subject_authorization; +use sea_orm::{ActiveValue, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter}; +use the_paginator::graphql::{CursorInput, ModelConnection}; +use uuid::Uuid; + +#[derive(Debug, Clone, Default)] +pub struct SoakCompoundQuery; + +#[derive(Debug, Clone, Default)] +pub struct SoakCompoundMutation; + +#[Object] +impl SoakCompoundQuery { + async fn soaked_compounds( + &self, + ctx: &Context<'_>, + cursor: CursorInput, + ) -> async_graphql::Result> { + subject_authorization!("xchemlab.compound_soaking.read_soaked_compound", ctx).await?; + let db = ctx.data::()?; + Ok(cursor + .try_into_query_cursor::()? + .all(db) + .await? + .try_into_connection()?) + } + + async fn soaked_compound( + &self, + ctx: &Context<'_>, + compound_plate_id: Uuid, + compound_well_number: i16, + crystal_plate_id: Uuid, + crystal_well_number: i16, + ) -> async_graphql::Result> { + subject_authorization!("xchemlab.compound_soaking.read_soaked_compound", ctx).await?; + let db = ctx.data::()?; + Ok(soak_compound::Entity::find() + .filter(soak_compound::Column::CompoundPlateId.eq(compound_plate_id)) + .filter(soak_compound::Column::CompoundWellNumber.eq(compound_well_number)) + .filter(soak_compound::Column::CrystalPlateId.eq(crystal_plate_id)) + .filter(soak_compound::Column::CrystalWellNumber.eq(crystal_well_number)) + .one(db) + .await?) + } +} + +#[Object] +impl SoakCompoundMutation { + async fn add_soaked_compound( + &self, + ctx: &Context<'_>, + compound_plate_id: Uuid, + compound_well_number: i16, + crystal_plate_id: Uuid, + crystal_well_number: i16, + ) -> async_graphql::Result { + let operator_id = + subject_authorization!("xchemlab.compound_soaking.write_soaked_compound", ctx).await?; + let db = ctx.data::()?; + let crystal = soak_compound::ActiveModel { + compound_plate_id: ActiveValue::Set(compound_plate_id), + compound_well_number: ActiveValue::Set(compound_well_number), + crystal_plate_id: ActiveValue::Set(crystal_plate_id), + crystal_well_number: ActiveValue::Set(crystal_well_number), + operator_id: ActiveValue::Set(operator_id), + timestamp: ActiveValue::Set(Utc::now()), + }; + Ok(soak_compound::Entity::insert(crystal) + .exec_with_returning(db) + .await?) + } +} diff --git a/backend/compound_soaking/src/main.rs b/backend/compound_soaking/src/main.rs new file mode 100644 index 00000000..e0b42fe3 --- /dev/null +++ b/backend/compound_soaking/src/main.rs @@ -0,0 +1,114 @@ +mod entities; +mod graphql; +mod migrator; + +use async_graphql::extensions::Tracing; +use axum::{routing::get, Router, Server}; +use clap::Parser; +use graphql::{root_schema_builder, RootSchema}; +use graphql_endpoints::{GraphQLHandler, GraphQLSubscription, GraphiQLHandler}; +use opa_client::OPAClient; +use sea_orm::{ConnectOptions, Database, DatabaseConnection, DbErr, TransactionError}; +use sea_orm_migration::MigratorTrait; +use std::{ + fs::File, + io::Write, + net::{Ipv4Addr, SocketAddr, SocketAddrV4}, + path::PathBuf, +}; +use url::Url; + +#[derive(Debug, Parser)] +#[command(author, version, about, long_about = None)] +enum Cli { + Serve(ServeArgs), + Schema(SchemaArgs), +} + +#[derive(Debug, Parser)] +#[allow(clippy::missing_docs_in_private_items)] +struct ServeArgs { + #[arg(short, long, default_value_t = 80)] + port: u16, + #[arg(long, env)] + database_url: Url, + #[arg(long, env)] + opa_url: Url, +} + +#[derive(Debug, Parser)] +struct SchemaArgs { + #[arg(short, long)] + path: Option, +} + +async fn setup_database( + mut database_url: Url, +) -> Result> { + if database_url.path().is_empty() { + database_url.set_path("compound_soaking"); + } + let connection_options = ConnectOptions::new(database_url.to_string()); + let connection = Database::connect(connection_options).await?; + migrator::Migrator::up(&connection, None).await?; + Ok(connection) +} + +fn setup_router(schema: RootSchema) -> Router { + const GRAPHQL_ENDPOINT: &str = "/"; + const SUBSCRIPTION_ENDPOINT: &str = "/ws"; + + Router::new() + .route( + GRAPHQL_ENDPOINT, + get(GraphiQLHandler::new( + GRAPHQL_ENDPOINT, + SUBSCRIPTION_ENDPOINT, + )) + .post(GraphQLHandler::new(schema.clone())), + ) + .route_service(SUBSCRIPTION_ENDPOINT, GraphQLSubscription::new(schema)) +} + +async fn serve(router: Router, port: u16) { + let socket_addr: SocketAddr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port)); + println!("GraphiQL IDE: {}", socket_addr); + Server::bind(&socket_addr) + .serve(router.into_make_service()) + .await + .unwrap(); +} + +#[tokio::main] +async fn main() { + dotenvy::dotenv().ok(); + let args = Cli::parse(); + let tracing_subscriber = tracing_subscriber::FmtSubscriber::builder() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .finish(); + tracing::subscriber::set_global_default(tracing_subscriber).unwrap(); + + match args { + Cli::Serve(args) => { + let db = setup_database(args.database_url).await.unwrap(); + let opa_client = OPAClient::new(args.opa_url); + let schema = root_schema_builder() + .data(db) + .data(opa_client) + .extension(Tracing) + .finish(); + let router = setup_router(schema); + serve(router, args.port).await; + } + Cli::Schema(args) => { + let schema = root_schema_builder().finish(); + let schema_string = schema.sdl(); + if let Some(path) = args.path { + let mut file = File::create(path).unwrap(); + file.write_all(schema_string.as_bytes()).unwrap(); + } else { + println!("{}", schema_string); + } + } + } +} diff --git a/backend/compound_soaking/src/migrator.rs b/backend/compound_soaking/src/migrator.rs new file mode 100644 index 00000000..a4ee24e4 --- /dev/null +++ b/backend/compound_soaking/src/migrator.rs @@ -0,0 +1,30 @@ +use crate::entities::soak_compound; +use axum::async_trait; +use sea_orm::{DbErr, DeriveMigrationName, Schema}; +use sea_orm_migration::{MigrationTrait, MigratorTrait, SchemaManager}; + +pub struct Migrator; + +#[derive(DeriveMigrationName)] +struct Initial; + +#[async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![Box::new(Initial)] + } +} + +#[async_trait] +impl MigrationTrait for Initial { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let backend = manager.get_database_backend(); + let schema = Schema::new(backend); + + manager + .create_table(schema.create_table_from_entity(soak_compound::Entity)) + .await?; + + Ok(()) + } +} diff --git a/policies/compound_soaking.rego b/policies/compound_soaking.rego new file mode 100644 index 00000000..287ec64a --- /dev/null +++ b/policies/compound_soaking.rego @@ -0,0 +1,14 @@ +package xchemlab.compound_soaking + +import data.xchemlab + +default read_soaked_compound = {"allowed" : false} +default write_soaked_compound = {"allowed" : false} + +read_soaked_compound = {"allowed": true, "subject": xchemlab.subject} { + xchemlab.valid_token +} + +write_soaked_compound = {"allowed" : true, "subject" : xchemlab.subject} { + xchemlab.valid_token +} From dab4e197b19ef9b37d558d22933130e618ee81b7 Mon Sep 17 00:00:00 2001 From: iamvigneshwars Date: Thu, 22 Feb 2024 14:52:37 +0000 Subject: [PATCH 2/7] minor reafctor --- backend/compound_soaking/src/entities/mod.rs | 1 - backend/compound_soaking/src/graphql/mod.rs | 7 +++++ .../src/graphql/soak_compound_res.rs | 7 ++++- backend/compound_soaking/src/main.rs | 28 ++++++++++++++++++- backend/compound_soaking/src/migrator.rs | 5 +++- backend/compound_soaking/src/tables/mod.rs | 2 ++ .../src/{entities => tables}/soak_compound.rs | 8 ++++++ 7 files changed, 54 insertions(+), 4 deletions(-) delete mode 100644 backend/compound_soaking/src/entities/mod.rs create mode 100644 backend/compound_soaking/src/tables/mod.rs rename backend/compound_soaking/src/{entities => tables}/soak_compound.rs (66%) diff --git a/backend/compound_soaking/src/entities/mod.rs b/backend/compound_soaking/src/entities/mod.rs deleted file mode 100644 index f073bd5b..00000000 --- a/backend/compound_soaking/src/entities/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod soak_compound; diff --git a/backend/compound_soaking/src/graphql/mod.rs b/backend/compound_soaking/src/graphql/mod.rs index 238dbf29..48b0f7a3 100644 --- a/backend/compound_soaking/src/graphql/mod.rs +++ b/backend/compound_soaking/src/graphql/mod.rs @@ -1,15 +1,22 @@ +/// A collection of resolvers relating to soaked compounds pub mod soak_compound_res; use async_graphql::{EmptySubscription, MergedObject, Schema, SchemaBuilder}; use soak_compound_res::{SoakCompoundMutation, SoakCompoundQuery}; +/// Combines all query resolvers into a single GraphQL `Query` type. #[derive(Debug, Clone, MergedObject, Default)] pub struct Query(SoakCompoundQuery); +/// Combines all mutations resolvers into a single GraphQL `Mutation` type. #[derive(Debug, Clone, MergedObject, Default)] pub struct Mutation(SoakCompoundMutation); +/// Type alias for the complete GraphQL schema. pub type RootSchema = Schema; + +/// Initializes the schema with default instances of `Query`, +/// `Mutation`, and `EmptySubscription`. pub fn root_schema_builder() -> SchemaBuilder { Schema::build(Query::default(), Mutation::default(), EmptySubscription).enable_federation() } diff --git a/backend/compound_soaking/src/graphql/soak_compound_res.rs b/backend/compound_soaking/src/graphql/soak_compound_res.rs index 30f6c9fc..7c2ec4eb 100644 --- a/backend/compound_soaking/src/graphql/soak_compound_res.rs +++ b/backend/compound_soaking/src/graphql/soak_compound_res.rs @@ -1,4 +1,4 @@ -use crate::entities::soak_compound; +use crate::tables::soak_compound; use async_graphql::{Context, Object}; use chrono::Utc; use opa_client::subject_authorization; @@ -6,14 +6,17 @@ use sea_orm::{ActiveValue, ColumnTrait, DatabaseConnection, EntityTrait, QueryFi use the_paginator::graphql::{CursorInput, ModelConnection}; use uuid::Uuid; +/// SoakCompundQuery is a type that represents all the queries for the compound soaking. #[derive(Debug, Clone, Default)] pub struct SoakCompoundQuery; +/// SoakCompundMutation is a type that represents all the mutations for the compound soaking. #[derive(Debug, Clone, Default)] pub struct SoakCompoundMutation; #[Object] impl SoakCompoundQuery { + /// Fetches all soaked compounds from the database async fn soaked_compounds( &self, ctx: &Context<'_>, @@ -28,6 +31,7 @@ impl SoakCompoundQuery { .try_into_connection()?) } + /// Fetches a single soaked compound from the database async fn soaked_compound( &self, ctx: &Context<'_>, @@ -50,6 +54,7 @@ impl SoakCompoundQuery { #[Object] impl SoakCompoundMutation { + /// Adds a soaked compound to the database async fn add_soaked_compound( &self, ctx: &Context<'_>, diff --git a/backend/compound_soaking/src/main.rs b/backend/compound_soaking/src/main.rs index e0b42fe3..9a7baa3e 100644 --- a/backend/compound_soaking/src/main.rs +++ b/backend/compound_soaking/src/main.rs @@ -1,6 +1,16 @@ -mod entities; +#![forbid(unsafe_code)] +#![warn(missing_docs)] +#![warn(clippy::missing_docs_in_private_items)] +#![doc=include_str!("../README.md")] + +/// This module is responsible for defining and applying database migrations. mod graphql; +/// This module defines the structure and schema of the database tables +/// through various entity structs. mod migrator; +/// This module sets up the GraphQL schema, including queries, mutations, +/// and subscriptions. It defines how data is queried and mutated through the API. +mod tables; use async_graphql::extensions::Tracing; use axum::{routing::get, Router, Server}; @@ -18,10 +28,13 @@ use std::{ }; use url::Url; +/// A service for tracking crystals available in the XChem lab #[derive(Debug, Parser)] #[command(author, version, about, long_about = None)] enum Cli { + /// Starts a webserver serving the GraphQL API Serve(ServeArgs), + /// Prints the GraphQL API to stdout Schema(SchemaArgs), } @@ -36,12 +49,20 @@ struct ServeArgs { opa_url: Url, } +/// Arguments for the `schema` command #[derive(Debug, Parser)] struct SchemaArgs { + /// Specifies an optional path to the file to save the schema #[arg(short, long)] path: Option, } +/// Sets up the database connection and performs the migrations +/// The database name is set of compound_library if not provided +/// +/// Returns a `Result` with a `DatabaseConnection` on success, +/// or a `TransactionError` if connecting to the database or running +/// migrations fails async fn setup_database( mut database_url: Url, ) -> Result> { @@ -54,8 +75,12 @@ async fn setup_database( Ok(connection) } +/// Sets up the router for handling GraphQL queries and subscriptions +/// Returns a `Router` configured with routes fn setup_router(schema: RootSchema) -> Router { + /// The endpoint for handling GraphQL queries and mutations const GRAPHQL_ENDPOINT: &str = "/"; + /// The endpoint for establishing WebSocket connections for GraphQL subscriptions const SUBSCRIPTION_ENDPOINT: &str = "/ws"; Router::new() @@ -70,6 +95,7 @@ fn setup_router(schema: RootSchema) -> Router { .route_service(SUBSCRIPTION_ENDPOINT, GraphQLSubscription::new(schema)) } +/// Starts a web server to handle HTTP requests as defined in the provided `router` async fn serve(router: Router, port: u16) { let socket_addr: SocketAddr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port)); println!("GraphiQL IDE: {}", socket_addr); diff --git a/backend/compound_soaking/src/migrator.rs b/backend/compound_soaking/src/migrator.rs index a4ee24e4..c0c3ab21 100644 --- a/backend/compound_soaking/src/migrator.rs +++ b/backend/compound_soaking/src/migrator.rs @@ -1,10 +1,13 @@ -use crate::entities::soak_compound; +use crate::tables::soak_compound; use axum::async_trait; use sea_orm::{DbErr, DeriveMigrationName, Schema}; use sea_orm_migration::{MigrationTrait, MigratorTrait, SchemaManager}; +/// Migrator for managing and applying database migrations. pub struct Migrator; +/// This struct is used to define the very first migration that sets up +/// the initial database schema. #[derive(DeriveMigrationName)] struct Initial; diff --git a/backend/compound_soaking/src/tables/mod.rs b/backend/compound_soaking/src/tables/mod.rs new file mode 100644 index 00000000..dd358cc2 --- /dev/null +++ b/backend/compound_soaking/src/tables/mod.rs @@ -0,0 +1,2 @@ +/// The table storing soaked compound information +pub mod soak_compound; diff --git a/backend/compound_soaking/src/entities/soak_compound.rs b/backend/compound_soaking/src/tables/soak_compound.rs similarity index 66% rename from backend/compound_soaking/src/entities/soak_compound.rs rename to backend/compound_soaking/src/tables/soak_compound.rs index 723d005c..99121c6d 100644 --- a/backend/compound_soaking/src/entities/soak_compound.rs +++ b/backend/compound_soaking/src/tables/soak_compound.rs @@ -6,22 +6,30 @@ use sea_orm::{ }; use uuid::Uuid; +/// Represents a soaked compound within the database. #[derive(Clone, Debug, DeriveEntityModel, Eq, PartialEq, SimpleObject)] #[sea_orm(table_name = "soak_compound")] #[graphql(name = "soak_compound")] pub struct Model { + /// ID of the plate on which the compound is located. #[sea_orm(primary_key, auto_increment = false)] pub compound_plate_id: Uuid, + /// The well on the plate which the compound is located. #[sea_orm(primary_key, auto_increment = false)] pub compound_well_number: i16, + /// ID of the plate on which the crystal is located. #[sea_orm(primary_key, auto_increment = false)] pub crystal_plate_id: Uuid, + /// The well on the plate which the crystal is located. #[sea_orm(primary_key, auto_increment = false)] pub crystal_well_number: i16, + /// The identifier of the operator which added this entry. pub operator_id: String, + /// The date and time when the compound instance was added. pub timestamp: DateTime, } +#[allow(clippy::missing_docs_in_private_items)] #[derive(Clone, Copy, Debug, EnumIter, DeriveRelation)] pub enum Relation {} From 05e445073a2614b99ebefbf329d67c8b5f8f398e Mon Sep 17 00:00:00 2001 From: iamvigneshwars Date: Fri, 23 Feb 2024 18:01:00 +0000 Subject: [PATCH 3/7] Federated graph initial --- .devcontainer/Dockerfile.backend | 4 ++++ .gitignore | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.devcontainer/Dockerfile.backend b/.devcontainer/Dockerfile.backend index e775bf00..a68c3801 100644 --- a/.devcontainer/Dockerfile.backend +++ b/.devcontainer/Dockerfile.backend @@ -7,3 +7,7 @@ RUN apt-get update \ sqlite3 pre-commit \ libopencv-dev clang libclang-dev \ && rm -rf /var/lib/apt/lists/* + +# Install Rover CLI for composing and precompiles rust router (gateway) for subgraphs +RUN curl -sSL https://rover.apollo.dev/nix/latest | sh\ + && curl -sSL https://router.apollo.dev/download/nix/latest | sh diff --git a/.gitignore b/.gitignore index 31281df2..27e4cdc3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ # Developer Tooling .vscode + +# Apollo router +router +backend/router From a40eca1b41e9f6528799990a30d02db1afea00aa Mon Sep 17 00:00:00 2001 From: iamvigneshwars Date: Mon, 26 Feb 2024 15:46:55 +0000 Subject: [PATCH 4/7] Extend crystal and compound library subgraphs in compound soaking subgraph --- .github/workflows/container.yml | 1 + .gitignore | 2 + backend/Dockerfile | 10 +++ backend/compound_library/src/entities/mod.rs | 4 -- .../src/graphql/compound_instances_res.rs | 21 +++++- .../src/graphql/compound_types_res.rs | 2 +- backend/compound_library/src/graphql/mod.rs | 2 +- backend/compound_library/src/main.rs | 6 +- backend/compound_library/src/migrator.rs | 2 +- .../compound_instances.rs | 2 +- .../{entities => tables}/compound_types.rs | 0 backend/compound_library/src/tables/mod.rs | 4 ++ backend/compound_soaking/src/graphql/mod.rs | 4 +- .../src/graphql/soak_compound_res.rs | 67 +++++++++++++++++-- .../src/graphql/subgraph_extensions.rs | 22 ++++++ .../src/tables/soak_compound.rs | 4 +- .../src/graphql/crystal_wells_res.rs | 17 +++++ backend/crystal_library/src/graphql/mod.rs | 4 +- .../src/tables/crystal_wells.rs | 2 +- backend/router.yaml | 5 ++ backend/supergraph-config.yaml | 14 ++++ 21 files changed, 172 insertions(+), 23 deletions(-) delete mode 100644 backend/compound_library/src/entities/mod.rs rename backend/compound_library/src/{entities => tables}/compound_instances.rs (96%) rename backend/compound_library/src/{entities => tables}/compound_types.rs (100%) create mode 100644 backend/compound_library/src/tables/mod.rs create mode 100644 backend/compound_soaking/src/graphql/subgraph_extensions.rs create mode 100644 backend/router.yaml create mode 100644 backend/supergraph-config.yaml diff --git a/.github/workflows/container.yml b/.github/workflows/container.yml index 7e7999a7..3a99d9cc 100644 --- a/.github/workflows/container.yml +++ b/.github/workflows/container.yml @@ -14,6 +14,7 @@ jobs: - chimp_chomp - chimp_controller - compound_library + - compound_soaking - crystal_library - pin_packing - soakdb_sync diff --git a/.gitignore b/.gitignore index 27e4cdc3..25e65aeb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ # Apollo router router backend/router +# Generated supergraph schema +backend/supergraph.graphql diff --git a/backend/Dockerfile b/backend/Dockerfile index f63e9f2f..3526705e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -12,6 +12,7 @@ COPY chimp_chomp/Cargo.toml chimp_chomp/Cargo.toml COPY chimp_controller/Cargo.toml chimp_controller/Cargo.toml COPY chimp_protocol/Cargo.toml chimp_protocol/Cargo.toml COPY compound_library/Cargo.toml compound_library/Cargo.toml +COPY compound_soaking/Cargo.toml compound_soaking/Cargo.toml COPY crystal_library/Cargo.toml crystal_library/Cargo.toml COPY graphql_endpoints/Cargo.toml graphql_endpoints/Cargo.toml COPY graphql_event_broker/Cargo.toml graphql_event_broker/Cargo.toml @@ -29,6 +30,8 @@ RUN mkdir chimp_chomp/src \ && echo "fn main() {}" > chimp_controller/src/main.rs \ && mkdir compound_library/src \ && echo "fn main() {}" > compound_library/src/main.rs \ + && mkdir compound_soaking/src \ + && echo "fn main() {}" > compound_soaking/src/main.rs \ && mkdir crystal_library/src \ && echo "fn main() {}" > crystal_library/src/main.rs \ && mkdir graphql_endpoints/src \ @@ -56,6 +59,7 @@ RUN touch chimp_chomp/src/main.rs \ && touch chimp_protocol/src/lib.rs \ && touch chimp_controller/src/main.rs \ && touch compound_library/src/main.rs \ + && touch compound_soaking/src/main.rs \ && touch crystal_library/src/main.rs \ && touch graphql_endpoints/src/lib.rs \ && touch graphql_event_broker/src/lib.rs \ @@ -101,6 +105,12 @@ COPY --from=build /app/target/release/compound_library /compound_library ENTRYPOINT ["/compound_library"] +FROM gcr.io/distroless/cc as compound_soaking + +COPY --from=build /app/target/release/compound_soaking /compound_soaking + +ENTRYPOINT ["/compound_soaking"] + FROM gcr.io/distroless/cc as crystal_library COPY --from=build /app/target/release/crystal_library /crystal_library diff --git a/backend/compound_library/src/entities/mod.rs b/backend/compound_library/src/entities/mod.rs deleted file mode 100644 index 1e6615a3..00000000 --- a/backend/compound_library/src/entities/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -/// The compound instances module -pub mod compound_instances; -/// The compound types module -pub mod compound_types; diff --git a/backend/compound_library/src/graphql/compound_instances_res.rs b/backend/compound_library/src/graphql/compound_instances_res.rs index 8df1fc4d..ea550587 100644 --- a/backend/compound_library/src/graphql/compound_instances_res.rs +++ b/backend/compound_library/src/graphql/compound_instances_res.rs @@ -1,4 +1,4 @@ -use crate::entities::{compound_instances, compound_types}; +use crate::tables::{compound_instances, compound_types}; use async_graphql::{ComplexObject, Context, Object}; use chrono::Utc; use opa_client::subject_authorization; @@ -57,6 +57,23 @@ impl CompoundInstanceQuery { .one(db) .await?) } + + /// Reference resolver for compound instance in compound library subgraph + #[graphql(entity)] + async fn get_compound_instance_by_id( + &self, + ctx: &Context<'_>, + plate_id: Uuid, + well_number: i16, + ) -> async_graphql::Result> { + subject_authorization!("xchemlab.compound_library.read_compound", ctx).await?; + let db = ctx.data::()?; + Ok(compound_instances::Entity::find() + .filter(compound_instances::Column::PlateId.eq(plate_id)) + .filter(compound_instances::Column::WellNumber.eq(well_number)) + .all(db) + .await?) + } } #[Object] @@ -66,7 +83,7 @@ impl CompoundInstanceMutation { &self, ctx: &Context<'_>, plate_id: Uuid, - well_number: i16, + #[graphql(validator(minimum = 1, maximum = 288))] well_number: i16, compound_type: String, ) -> async_graphql::Result { let operator_id = diff --git a/backend/compound_library/src/graphql/compound_types_res.rs b/backend/compound_library/src/graphql/compound_types_res.rs index 42c12323..cf47651b 100644 --- a/backend/compound_library/src/graphql/compound_types_res.rs +++ b/backend/compound_library/src/graphql/compound_types_res.rs @@ -1,4 +1,4 @@ -use crate::entities::{compound_instances, compound_types}; +use crate::tables::{compound_instances, compound_types}; use async_graphql::{ComplexObject, Context, CustomValidator, InputValueError, Object}; use chrono::Utc; use opa_client::subject_authorization; diff --git a/backend/compound_library/src/graphql/mod.rs b/backend/compound_library/src/graphql/mod.rs index 48541fa0..b281f0f7 100644 --- a/backend/compound_library/src/graphql/mod.rs +++ b/backend/compound_library/src/graphql/mod.rs @@ -22,5 +22,5 @@ pub type RootSchema = Schema; /// This function initializes the schema with default instances of `Query`, /// `Mutation`, and `EmptySubscription`. pub fn root_schema_builder() -> SchemaBuilder { - Schema::build(Query::default(), Mutation::default(), EmptySubscription) + Schema::build(Query::default(), Mutation::default(), EmptySubscription).enable_federation() } diff --git a/backend/compound_library/src/main.rs b/backend/compound_library/src/main.rs index 206a53d2..93cd63cb 100644 --- a/backend/compound_library/src/main.rs +++ b/backend/compound_library/src/main.rs @@ -3,14 +3,14 @@ #![warn(clippy::missing_docs_in_private_items)] #![doc=include_str!("../README.md")] -/// This module defines the structure and schema of the database tables -/// through various entity structs. -mod entities; /// This module sets up the GraphQL schema, including queries, mutations, /// and subscriptions. It defines how data is queried and mutated through the API. mod graphql; /// This module is responsible for defining and applying database migrations. mod migrator; +/// This module defines the structure and schema of the database tables +/// through various entity structs. +mod tables; use async_graphql::extensions::Tracing; use axum::{routing::get, Router, Server}; diff --git a/backend/compound_library/src/migrator.rs b/backend/compound_library/src/migrator.rs index 8429fce9..58519f57 100644 --- a/backend/compound_library/src/migrator.rs +++ b/backend/compound_library/src/migrator.rs @@ -1,4 +1,4 @@ -use crate::entities::{compound_instances, compound_types}; +use crate::tables::{compound_instances, compound_types}; use axum::async_trait; use sea_orm::{DbErr, DeriveMigrationName, Schema}; use sea_orm_migration::{MigrationTrait, MigratorTrait, SchemaManager}; diff --git a/backend/compound_library/src/entities/compound_instances.rs b/backend/compound_library/src/tables/compound_instances.rs similarity index 96% rename from backend/compound_library/src/entities/compound_instances.rs rename to backend/compound_library/src/tables/compound_instances.rs index 40ac67db..0fba8a11 100644 --- a/backend/compound_library/src/entities/compound_instances.rs +++ b/backend/compound_library/src/tables/compound_instances.rs @@ -10,7 +10,7 @@ use uuid::Uuid; /// Represents a compound instance within the database. #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, SimpleObject)] #[sea_orm(table_name = "compound_instances")] -#[graphql(name = "compund_instances", complex)] +#[graphql(name = "compound_instances", complex, shareable)] pub struct Model { /// ID of the plate on which the compound instance is located. #[sea_orm(primary_key, auto_increment = false)] diff --git a/backend/compound_library/src/entities/compound_types.rs b/backend/compound_library/src/tables/compound_types.rs similarity index 100% rename from backend/compound_library/src/entities/compound_types.rs rename to backend/compound_library/src/tables/compound_types.rs diff --git a/backend/compound_library/src/tables/mod.rs b/backend/compound_library/src/tables/mod.rs new file mode 100644 index 00000000..4d220a52 --- /dev/null +++ b/backend/compound_library/src/tables/mod.rs @@ -0,0 +1,4 @@ +/// The table storing compound instances information +pub mod compound_instances; +/// The table storing the compound types information +pub mod compound_types; diff --git a/backend/compound_soaking/src/graphql/mod.rs b/backend/compound_soaking/src/graphql/mod.rs index 48b0f7a3..e2d15bea 100644 --- a/backend/compound_soaking/src/graphql/mod.rs +++ b/backend/compound_soaking/src/graphql/mod.rs @@ -1,5 +1,7 @@ /// A collection of resolvers relating to soaked compounds -pub mod soak_compound_res; +mod soak_compound_res; +/// A collection of subgraphs extended from other services +mod subgraph_extensions; use async_graphql::{EmptySubscription, MergedObject, Schema, SchemaBuilder}; use soak_compound_res::{SoakCompoundMutation, SoakCompoundQuery}; diff --git a/backend/compound_soaking/src/graphql/soak_compound_res.rs b/backend/compound_soaking/src/graphql/soak_compound_res.rs index 7c2ec4eb..ce6ee592 100644 --- a/backend/compound_soaking/src/graphql/soak_compound_res.rs +++ b/backend/compound_soaking/src/graphql/soak_compound_res.rs @@ -1,19 +1,52 @@ +use super::subgraph_extensions::{CompoundInstances, CrystalWells}; use crate::tables::soak_compound; -use async_graphql::{Context, Object}; +use async_graphql::{ComplexObject, Context, Object}; use chrono::Utc; use opa_client::subject_authorization; use sea_orm::{ActiveValue, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter}; use the_paginator::graphql::{CursorInput, ModelConnection}; use uuid::Uuid; -/// SoakCompundQuery is a type that represents all the queries for the compound soaking. +/// SoakCompoundQuery is a type that represents all the queries for the compound soaking. #[derive(Debug, Clone, Default)] pub struct SoakCompoundQuery; -/// SoakCompundMutation is a type that represents all the mutations for the compound soaking. +/// SoakCompoundMutation is a type that represents all the mutations for the compound soaking. #[derive(Debug, Clone, Default)] pub struct SoakCompoundMutation; +#[ComplexObject] +impl CrystalWells { + /// Fetches all the compounds soaked in a crystal well + async fn compound_soaked( + &self, + ctx: &Context<'_>, + ) -> async_graphql::Result> { + let db = ctx.data::()?; + Ok(soak_compound::Entity::find() + .filter(soak_compound::Column::CrystalPlateId.eq(self.plate_id)) + .filter(soak_compound::Column::CrystalWellNumber.eq(self.well_number)) + .all(db) + .await?) + } +} + +#[ComplexObject] +impl CompoundInstances { + /// Fetches all the crystals soaked with the compounds + async fn crystal_soaked( + &self, + ctx: &Context<'_>, + ) -> async_graphql::Result> { + let db = ctx.data::()?; + Ok(soak_compound::Entity::find() + .filter(soak_compound::Column::CompoundPlateId.eq(self.plate_id)) + .filter(soak_compound::Column::CompoundWellNumber.eq(self.well_number)) + .all(db) + .await?) + } +} + #[Object] impl SoakCompoundQuery { /// Fetches all soaked compounds from the database @@ -50,6 +83,28 @@ impl SoakCompoundQuery { .one(db) .await?) } + + /// Reference resolver for crystal wells + #[graphql(entity)] + async fn get_crystal_well_by_plate_id(&self, plate_id: Uuid, well_number: i16) -> CrystalWells { + CrystalWells { + plate_id, + well_number, + } + } + + /// Reference resolver for compound wells + #[graphql(entity)] + async fn get_compound_instances_by_plate_id( + &self, + plate_id: Uuid, + well_number: i16, + ) -> CompoundInstances { + CompoundInstances { + plate_id, + well_number, + } + } } #[Object] @@ -59,9 +114,10 @@ impl SoakCompoundMutation { &self, ctx: &Context<'_>, compound_plate_id: Uuid, - compound_well_number: i16, + #[graphql(validator(minimum = 1, maximum = 288))] compound_well_number: i16, crystal_plate_id: Uuid, - crystal_well_number: i16, + #[graphql(validator(minimum = 1, maximum = 288))] crystal_well_number: i16, + volume: f32, ) -> async_graphql::Result { let operator_id = subject_authorization!("xchemlab.compound_soaking.write_soaked_compound", ctx).await?; @@ -71,6 +127,7 @@ impl SoakCompoundMutation { compound_well_number: ActiveValue::Set(compound_well_number), crystal_plate_id: ActiveValue::Set(crystal_plate_id), crystal_well_number: ActiveValue::Set(crystal_well_number), + volume: ActiveValue::set(volume), operator_id: ActiveValue::Set(operator_id), timestamp: ActiveValue::Set(Utc::now()), }; diff --git a/backend/compound_soaking/src/graphql/subgraph_extensions.rs b/backend/compound_soaking/src/graphql/subgraph_extensions.rs new file mode 100644 index 00000000..d2c4f6ac --- /dev/null +++ b/backend/compound_soaking/src/graphql/subgraph_extensions.rs @@ -0,0 +1,22 @@ +use async_graphql::SimpleObject; +use uuid::Uuid; + +/// CrystalWell is an extension from the crystal library subgraph +#[derive(SimpleObject)] +#[graphql(name = "crystal_wells", complex)] +pub struct CrystalWells { + /// ID of the plate on which the crystal is located + pub plate_id: Uuid, + /// Well on the plate in which crystal is located + pub well_number: i16, +} + +/// CompoundInstances is an extension of compound library subgraph +#[derive(SimpleObject)] +#[graphql(name = "compound_instances", complex)] +pub struct CompoundInstances { + /// ID of the plate on which the compound is located + pub plate_id: Uuid, + /// Well on the plate in which compound is located + pub well_number: i16, +} diff --git a/backend/compound_soaking/src/tables/soak_compound.rs b/backend/compound_soaking/src/tables/soak_compound.rs index 99121c6d..cea975b1 100644 --- a/backend/compound_soaking/src/tables/soak_compound.rs +++ b/backend/compound_soaking/src/tables/soak_compound.rs @@ -7,7 +7,7 @@ use sea_orm::{ use uuid::Uuid; /// Represents a soaked compound within the database. -#[derive(Clone, Debug, DeriveEntityModel, Eq, PartialEq, SimpleObject)] +#[derive(Clone, Debug, DeriveEntityModel, PartialEq, SimpleObject)] #[sea_orm(table_name = "soak_compound")] #[graphql(name = "soak_compound")] pub struct Model { @@ -23,6 +23,8 @@ pub struct Model { /// The well on the plate which the crystal is located. #[sea_orm(primary_key, auto_increment = false)] pub crystal_well_number: i16, + /// The volume of compounds soaked. + pub volume: f32, /// The identifier of the operator which added this entry. pub operator_id: String, /// The date and time when the compound instance was added. diff --git a/backend/crystal_library/src/graphql/crystal_wells_res.rs b/backend/crystal_library/src/graphql/crystal_wells_res.rs index 16e6cd08..6b0b3bc4 100644 --- a/backend/crystal_library/src/graphql/crystal_wells_res.rs +++ b/backend/crystal_library/src/graphql/crystal_wells_res.rs @@ -59,6 +59,23 @@ impl CrystalWellsQuery { .one(db) .await?) } + + /// Reference resolver for crystal wells in crystal library graphql subgraph + #[graphql(entity)] + async fn find_crystal_wells_by_id( + &self, + ctx: &Context<'_>, + plate_id: Uuid, + well_number: i16, + ) -> async_graphql::Result> { + subject_authorization!("xchemlab.crystal_library.read_crystal_wells", ctx).await?; + let db = ctx.data::()?; + Ok(crystal_wells::Entity::find() + .filter(crystal_wells::Column::PlateId.eq(plate_id)) + .filter(crystal_wells::Column::WellNumber.eq(well_number)) + .one(db) + .await?) + } } #[Object] diff --git a/backend/crystal_library/src/graphql/mod.rs b/backend/crystal_library/src/graphql/mod.rs index 77b81771..cfa18cfa 100644 --- a/backend/crystal_library/src/graphql/mod.rs +++ b/backend/crystal_library/src/graphql/mod.rs @@ -1,7 +1,7 @@ /// A collection of resolvers relating to crystal plates -pub mod crystal_plates_res; +mod crystal_plates_res; /// A collection of resolvers relating to crystal wells -pub mod crystal_wells_res; +mod crystal_wells_res; use async_graphql::{EmptySubscription, MergedObject, Schema, SchemaBuilder}; use crystal_plates_res::{CrystalPlatesMutation, CrystalPlatesQuery}; diff --git a/backend/crystal_library/src/tables/crystal_wells.rs b/backend/crystal_library/src/tables/crystal_wells.rs index 2b087db2..3e6cfb01 100644 --- a/backend/crystal_library/src/tables/crystal_wells.rs +++ b/backend/crystal_library/src/tables/crystal_wells.rs @@ -10,7 +10,7 @@ use uuid::Uuid; /// Represents a crystal within the database. #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, SimpleObject)] #[sea_orm(table_name = "crystal_wells")] -#[graphql(name = "crystal_wells", complex)] +#[graphql(name = "crystal_wells", complex, shareable)] pub struct Model { /// ID of the plate on which the crystal is located. #[sea_orm(primary_key, auto_increment = false)] diff --git a/backend/router.yaml b/backend/router.yaml new file mode 100644 index 00000000..59dab80b --- /dev/null +++ b/backend/router.yaml @@ -0,0 +1,5 @@ +headers: + all: # Header rules for all subgraphs + request: + - propagate: + matching: .* \ No newline at end of file diff --git a/backend/supergraph-config.yaml b/backend/supergraph-config.yaml new file mode 100644 index 00000000..a3b9245c --- /dev/null +++ b/backend/supergraph-config.yaml @@ -0,0 +1,14 @@ +federation_version: 2 +subgraphs: + crystal_library: + routing_url: http://localhost:80 + schema: + subgraph_url: http://localhost:80 + compound_library: + routing_url: http://localhost:81 + schema: + subgraph_url: http://localhost:81 + compound_soaking: + routing_url: http://localhost:82 + schema: + subgraph_url: http://localhost:82 \ No newline at end of file From 78deea61684d5d88c1b3cd5fdad5c732056f3ee9 Mon Sep 17 00:00:00 2001 From: iamvigneshwars Date: Mon, 26 Feb 2024 17:58:03 +0000 Subject: [PATCH 5/7] Resolve compound and crystal library in compound soaking --- .../src/graphql/compound_instances_res.rs | 4 ++-- .../src/graphql/soak_compound_res.rs | 21 +++++++++++++++++++ .../src/tables/soak_compound.rs | 4 ++-- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/backend/compound_library/src/graphql/compound_instances_res.rs b/backend/compound_library/src/graphql/compound_instances_res.rs index ea550587..c8135180 100644 --- a/backend/compound_library/src/graphql/compound_instances_res.rs +++ b/backend/compound_library/src/graphql/compound_instances_res.rs @@ -65,13 +65,13 @@ impl CompoundInstanceQuery { ctx: &Context<'_>, plate_id: Uuid, well_number: i16, - ) -> async_graphql::Result> { + ) -> async_graphql::Result> { subject_authorization!("xchemlab.compound_library.read_compound", ctx).await?; let db = ctx.data::()?; Ok(compound_instances::Entity::find() .filter(compound_instances::Column::PlateId.eq(plate_id)) .filter(compound_instances::Column::WellNumber.eq(well_number)) - .all(db) + .one(db) .await?) } } diff --git a/backend/compound_soaking/src/graphql/soak_compound_res.rs b/backend/compound_soaking/src/graphql/soak_compound_res.rs index ce6ee592..c3120e19 100644 --- a/backend/compound_soaking/src/graphql/soak_compound_res.rs +++ b/backend/compound_soaking/src/graphql/soak_compound_res.rs @@ -47,6 +47,27 @@ impl CompoundInstances { } } +#[ComplexObject] +impl soak_compound::Model { + /// Fetches the information of the crystals soaked from + /// crystal library subgraph + async fn crystals(&self) -> async_graphql::Result { + Ok(CrystalWells { + plate_id: self.crystal_plate_id, + well_number: self.crystal_well_number, + }) + } + + /// Fetches the information of the compounds soaked from + /// compound library subgraph + async fn compounds(&self) -> async_graphql::Result { + Ok(CompoundInstances { + plate_id: self.compound_plate_id, + well_number: self.compound_well_number, + }) + } +} + #[Object] impl SoakCompoundQuery { /// Fetches all soaked compounds from the database diff --git a/backend/compound_soaking/src/tables/soak_compound.rs b/backend/compound_soaking/src/tables/soak_compound.rs index cea975b1..d752e2d4 100644 --- a/backend/compound_soaking/src/tables/soak_compound.rs +++ b/backend/compound_soaking/src/tables/soak_compound.rs @@ -8,8 +8,8 @@ use uuid::Uuid; /// Represents a soaked compound within the database. #[derive(Clone, Debug, DeriveEntityModel, PartialEq, SimpleObject)] -#[sea_orm(table_name = "soak_compound")] -#[graphql(name = "soak_compound")] +#[sea_orm(table_name = "soaked_compounds")] +#[graphql(name = "soaked_compounds", complex)] pub struct Model { /// ID of the plate on which the compound is located. #[sea_orm(primary_key, auto_increment = false)] From ea05643a16192595b9efcc8383dab9e2fbf84ad9 Mon Sep 17 00:00:00 2001 From: iamvigneshwars Date: Thu, 29 Feb 2024 12:58:16 +0000 Subject: [PATCH 6/7] Containerize apollo router for compound soaking --- .devcontainer/Dockerfile.backend | 5 ++--- .devcontainer/docker-compose.yaml | 17 +++++++++++++++++ .devcontainer/router.yaml | 7 +++++++ .gitignore | 5 +---- .../src/graphql/compound_instances_res.rs | 2 +- backend/compound_library/src/main.rs | 2 +- .../src/graphql/soak_compound_res.rs | 8 ++++---- backend/compound_soaking/src/main.rs | 2 +- .../src/tables/soak_compound.rs | 4 ++-- .../src/graphql/crystal_wells_res.rs | 2 +- backend/crystal_library/src/main.rs | 2 +- backend/router.yaml | 5 ----- backend/supergraph-config.yaml | 12 ++++++------ 13 files changed, 44 insertions(+), 29 deletions(-) create mode 100644 .devcontainer/router.yaml delete mode 100644 backend/router.yaml diff --git a/.devcontainer/Dockerfile.backend b/.devcontainer/Dockerfile.backend index a68c3801..7f6f6045 100644 --- a/.devcontainer/Dockerfile.backend +++ b/.devcontainer/Dockerfile.backend @@ -8,6 +8,5 @@ RUN apt-get update \ libopencv-dev clang libclang-dev \ && rm -rf /var/lib/apt/lists/* -# Install Rover CLI for composing and precompiles rust router (gateway) for subgraphs -RUN curl -sSL https://rover.apollo.dev/nix/latest | sh\ - && curl -sSL https://router.apollo.dev/download/nix/latest | sh +# Install Rover CLI for composing subgraphs +RUN curl -sSL https://rover.apollo.dev/nix/latest | sh diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml index 79a156b1..e517c248 100644 --- a/.devcontainer/docker-compose.yaml +++ b/.devcontainer/docker-compose.yaml @@ -1,6 +1,20 @@ version: "3.8" services: + apollo-router: + image: ghcr.io/apollographql/router:v1.41.0-rc.2 + volumes: + - ./supergraph.graphql:/dist/schema/supergraph.graphql + - ./router.yaml:/dist/config/router.yaml + ports: + - 4001:4000 + command: + - --dev + - -config + - config/router.yaml + - -supergraph + - schema/supergraph.graphql + backend: build: context: . @@ -12,6 +26,9 @@ services: OPA_URL: http://opa:8181 DATABASE_URL: postgres://postgres:password@postgres RABBITMQ_URL: amqp://rabbitmq:password@rabbitmq + CRYSTAL_LIBRARY_PORT: 8000 + COMPOUND_LIBRARY_PORT: 8001 + COMPOUND_SOAKING_PORT: 8002 frontend: image: docker.io/library/node:20.6.0-bookworm diff --git a/.devcontainer/router.yaml b/.devcontainer/router.yaml new file mode 100644 index 00000000..5bab262f --- /dev/null +++ b/.devcontainer/router.yaml @@ -0,0 +1,7 @@ +supergraph: + listen: 0.0.0.0:4000 +headers: + all: + request: + - propagate: + matching: .* diff --git a/.gitignore b/.gitignore index 25e65aeb..8df3c614 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,5 @@ # Developer Tooling .vscode -# Apollo router -router -backend/router # Generated supergraph schema -backend/supergraph.graphql +.devcontainer/supergraph.graphql diff --git a/backend/compound_library/src/graphql/compound_instances_res.rs b/backend/compound_library/src/graphql/compound_instances_res.rs index c8135180..622669ed 100644 --- a/backend/compound_library/src/graphql/compound_instances_res.rs +++ b/backend/compound_library/src/graphql/compound_instances_res.rs @@ -60,7 +60,7 @@ impl CompoundInstanceQuery { /// Reference resolver for compound instance in compound library subgraph #[graphql(entity)] - async fn get_compound_instance_by_id( + async fn route_compound_instance( &self, ctx: &Context<'_>, plate_id: Uuid, diff --git a/backend/compound_library/src/main.rs b/backend/compound_library/src/main.rs index 93cd63cb..de8d5322 100644 --- a/backend/compound_library/src/main.rs +++ b/backend/compound_library/src/main.rs @@ -43,7 +43,7 @@ enum Cli { #[allow(clippy::missing_docs_in_private_items)] struct ServeArgs { /// The port number to serve on. - #[arg(short, long, default_value_t = 80)] + #[arg(short, long, default_value_t = 80, env = "COMPOUND_LIBRARY_PORT")] port: u16, /// URL for the OPA server #[arg(long, env)] diff --git a/backend/compound_soaking/src/graphql/soak_compound_res.rs b/backend/compound_soaking/src/graphql/soak_compound_res.rs index c3120e19..f9dc2c44 100644 --- a/backend/compound_soaking/src/graphql/soak_compound_res.rs +++ b/backend/compound_soaking/src/graphql/soak_compound_res.rs @@ -107,7 +107,7 @@ impl SoakCompoundQuery { /// Reference resolver for crystal wells #[graphql(entity)] - async fn get_crystal_well_by_plate_id(&self, plate_id: Uuid, well_number: i16) -> CrystalWells { + async fn route_crystal_well(&self, plate_id: Uuid, well_number: i16) -> CrystalWells { CrystalWells { plate_id, well_number, @@ -116,7 +116,7 @@ impl SoakCompoundQuery { /// Reference resolver for compound wells #[graphql(entity)] - async fn get_compound_instances_by_plate_id( + async fn route_compound_instances( &self, plate_id: Uuid, well_number: i16, @@ -131,14 +131,14 @@ impl SoakCompoundQuery { #[Object] impl SoakCompoundMutation { /// Adds a soaked compound to the database - async fn add_soaked_compound( + async fn soak_compound( &self, ctx: &Context<'_>, compound_plate_id: Uuid, #[graphql(validator(minimum = 1, maximum = 288))] compound_well_number: i16, crystal_plate_id: Uuid, #[graphql(validator(minimum = 1, maximum = 288))] crystal_well_number: i16, - volume: f32, + volume: u32, ) -> async_graphql::Result { let operator_id = subject_authorization!("xchemlab.compound_soaking.write_soaked_compound", ctx).await?; diff --git a/backend/compound_soaking/src/main.rs b/backend/compound_soaking/src/main.rs index 9a7baa3e..0a3664de 100644 --- a/backend/compound_soaking/src/main.rs +++ b/backend/compound_soaking/src/main.rs @@ -41,7 +41,7 @@ enum Cli { #[derive(Debug, Parser)] #[allow(clippy::missing_docs_in_private_items)] struct ServeArgs { - #[arg(short, long, default_value_t = 80)] + #[arg(short, long, default_value_t = 80, env = "COMPOUND_SOAKING_PORT")] port: u16, #[arg(long, env)] database_url: Url, diff --git a/backend/compound_soaking/src/tables/soak_compound.rs b/backend/compound_soaking/src/tables/soak_compound.rs index d752e2d4..b5b717b8 100644 --- a/backend/compound_soaking/src/tables/soak_compound.rs +++ b/backend/compound_soaking/src/tables/soak_compound.rs @@ -23,8 +23,8 @@ pub struct Model { /// The well on the plate which the crystal is located. #[sea_orm(primary_key, auto_increment = false)] pub crystal_well_number: i16, - /// The volume of compounds soaked. - pub volume: f32, + /// The volume of compounds transferred into the crystal well in mircolitres. + pub volume: u32, /// The identifier of the operator which added this entry. pub operator_id: String, /// The date and time when the compound instance was added. diff --git a/backend/crystal_library/src/graphql/crystal_wells_res.rs b/backend/crystal_library/src/graphql/crystal_wells_res.rs index 6b0b3bc4..905ebe97 100644 --- a/backend/crystal_library/src/graphql/crystal_wells_res.rs +++ b/backend/crystal_library/src/graphql/crystal_wells_res.rs @@ -62,7 +62,7 @@ impl CrystalWellsQuery { /// Reference resolver for crystal wells in crystal library graphql subgraph #[graphql(entity)] - async fn find_crystal_wells_by_id( + async fn route_crystal_wells( &self, ctx: &Context<'_>, plate_id: Uuid, diff --git a/backend/crystal_library/src/main.rs b/backend/crystal_library/src/main.rs index c656482c..ed11e825 100644 --- a/backend/crystal_library/src/main.rs +++ b/backend/crystal_library/src/main.rs @@ -42,7 +42,7 @@ enum Cli { #[allow(clippy::missing_docs_in_private_items)] struct ServeArgs { /// The port number to serve on - #[arg(short, long, default_value_t = 80)] + #[arg(short, long, default_value_t = 80, env = "CRYSTAL_LIBRARY_PORT")] port: u16, /// URL for the database #[arg(long, env)] diff --git a/backend/router.yaml b/backend/router.yaml deleted file mode 100644 index 59dab80b..00000000 --- a/backend/router.yaml +++ /dev/null @@ -1,5 +0,0 @@ -headers: - all: # Header rules for all subgraphs - request: - - propagate: - matching: .* \ No newline at end of file diff --git a/backend/supergraph-config.yaml b/backend/supergraph-config.yaml index a3b9245c..c77877e8 100644 --- a/backend/supergraph-config.yaml +++ b/backend/supergraph-config.yaml @@ -1,14 +1,14 @@ federation_version: 2 subgraphs: crystal_library: - routing_url: http://localhost:80 + routing_url: http://backend:8000 schema: - subgraph_url: http://localhost:80 + subgraph_url: http://backend:8000 compound_library: - routing_url: http://localhost:81 + routing_url: http://backend:8001 schema: - subgraph_url: http://localhost:81 + subgraph_url: http://backend:8001 compound_soaking: - routing_url: http://localhost:82 + routing_url: http://backend:8002 schema: - subgraph_url: http://localhost:82 \ No newline at end of file + subgraph_url: http://backend:8002 From e9815000fb371a0e058ce1104e1b1047102172b7 Mon Sep 17 00:00:00 2001 From: iamvigneshwars Date: Thu, 29 Feb 2024 17:16:00 +0000 Subject: [PATCH 7/7] Set env variables for pin packing and targeting ports --- .devcontainer/docker-compose.yaml | 2 ++ backend/pin_packing/src/main.rs | 2 +- backend/targeting/src/main.rs | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml index e517c248..fd99f802 100644 --- a/.devcontainer/docker-compose.yaml +++ b/.devcontainer/docker-compose.yaml @@ -29,6 +29,8 @@ services: CRYSTAL_LIBRARY_PORT: 8000 COMPOUND_LIBRARY_PORT: 8001 COMPOUND_SOAKING_PORT: 8002 + PIN_PACKING_PORT: 8003 + TARGITING_PORT: 8004 frontend: image: docker.io/library/node:20.6.0-bookworm diff --git a/backend/pin_packing/src/main.rs b/backend/pin_packing/src/main.rs index ca51ba54..23ac36e3 100644 --- a/backend/pin_packing/src/main.rs +++ b/backend/pin_packing/src/main.rs @@ -72,7 +72,7 @@ enum Cli { #[derive(Debug, Parser)] struct ServeArgs { /// The port number to serve on. - #[arg(short, long, default_value_t = 80)] + #[arg(short, long, default_value_t = 80, env = "PIN_PACKING_PORT")] port: u16, /// The URL of a postgres database which will be used to persist service data. #[arg(long, env)] diff --git a/backend/targeting/src/main.rs b/backend/targeting/src/main.rs index 738b54b7..2d2e4f52 100644 --- a/backend/targeting/src/main.rs +++ b/backend/targeting/src/main.rs @@ -20,7 +20,7 @@ enum Cli { #[derive(Debug, Parser)] struct ServeArgs { /// The port number to serve on. - #[arg(short, long, default_value_t = 80)] + #[arg(short, long, default_value_t = 80, env = "TARGETING_PORT")] port: u16, /// The URL of a postgres database which will be used to persist service data. #[arg(long, env)]