From ef24d467297788c6f0463627177bafc15c210941 Mon Sep 17 00:00:00 2001 From: Kyle Espinola Date: Mon, 7 Aug 2023 10:39:12 +0200 Subject: [PATCH 1/3] feat: able to query the mint associated to the mint history record --- api/src/dataloaders/collection_mints.rs | 30 +++++++++++++++++++++++++ api/src/dataloaders/mod.rs | 3 ++- api/src/entities/mint_histories.rs | 20 ++++++++++++++--- api/src/lib.rs | 12 ++++++---- api/src/objects/collection.rs | 2 +- 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/api/src/dataloaders/collection_mints.rs b/api/src/dataloaders/collection_mints.rs index 3aeaf6c..8506810 100644 --- a/api/src/dataloaders/collection_mints.rs +++ b/api/src/dataloaders/collection_mints.rs @@ -81,3 +81,33 @@ impl DataLoader for OwnerLoader { })) } } + +#[derive(Debug, Clone)] +pub struct CollectionMintLoader { + pub db: Connection, +} + +impl CollectionMintLoader { + #[must_use] + pub fn new(db: Connection) -> Self { + Self { db } + } +} + +#[async_trait] +impl DataLoader for CollectionMintLoader { + type Error = FieldError; + type Value = collection_mints::CollectionMint; + + async fn load(&self, keys: &[Uuid]) -> Result, Self::Error> { + let collection_mints = collection_mints::Entity::find() + .filter(collection_mints::Column::Id.is_in(keys.iter().map(ToOwned::to_owned))) + .all(self.db.get()) + .await?; + + Ok(collection_mints + .into_iter() + .map(|collection_mint| (collection_mint.id, collection_mint.into())) + .collect()) + } +} diff --git a/api/src/dataloaders/mod.rs b/api/src/dataloaders/mod.rs index b87605c..549bf30 100644 --- a/api/src/dataloaders/mod.rs +++ b/api/src/dataloaders/mod.rs @@ -13,7 +13,8 @@ mod project_collections; pub use collection::Loader as CollectionLoader; pub use collection_drop::Loader as CollectionDropLoader; pub use collection_mints::{ - Loader as CollectionMintsLoader, OwnerLoader as CollectionMintsOwnerLoader, + CollectionMintLoader, Loader as CollectionMintsLoader, + OwnerLoader as CollectionMintsOwnerLoader, }; pub use creators::Loader as CreatorsLoader; pub use drop::Loader as DropLoader; diff --git a/api/src/entities/mint_histories.rs b/api/src/entities/mint_histories.rs index 3b55dda..0a1cdae 100644 --- a/api/src/entities/mint_histories.rs +++ b/api/src/entities/mint_histories.rs @@ -1,14 +1,15 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3 -use async_graphql::SimpleObject; +use async_graphql::{ComplexObject, Context, Result, SimpleObject}; use sea_orm::entity::prelude::*; -use super::sea_orm_active_enums::CreationStatus; +use super::{collection_mints::CollectionMint, sea_orm_active_enums::CreationStatus}; +use crate::AppContext; /// A record of a minted NFT. #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, SimpleObject)] #[sea_orm(table_name = "mint_histories")] -#[graphql(concrete(name = "MintHistory", params()))] +#[graphql(concrete(name = "MintHistory", params()), complex)] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id: Uuid, @@ -28,6 +29,19 @@ pub struct Model { pub collection_id: Uuid, } +#[ComplexObject] +impl Model { + /// The minted NFT. + async fn mint(&self, ctx: &Context<'_>) -> Result> { + let AppContext { + single_collection_mint_loader, + .. + } = ctx.data::()?; + + single_collection_mint_loader.load_one(self.mint_id).await + } +} + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( diff --git a/api/src/lib.rs b/api/src/lib.rs index d3ec7cd..03db489 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -22,10 +22,10 @@ use async_graphql::{ }; use blockchains::{polygon::Polygon, solana::Solana}; use dataloaders::{ - CollectionDropLoader, CollectionLoader, CollectionMintHistoryLoader, CollectionMintsLoader, - CollectionMintsOwnerLoader, CreatorsLoader, DropLoader, DropMintHistoryLoader, HoldersLoader, - MetadataJsonAttributesLoader, MetadataJsonLoader, ProjectCollectionLoader, - ProjectCollectionsLoader, ProjectDropsLoader, + CollectionDropLoader, CollectionLoader, CollectionMintHistoryLoader, CollectionMintLoader, + CollectionMintsLoader, CollectionMintsOwnerLoader, CreatorsLoader, DropLoader, + DropMintHistoryLoader, HoldersLoader, MetadataJsonAttributesLoader, MetadataJsonLoader, + ProjectCollectionLoader, ProjectCollectionsLoader, ProjectDropsLoader, }; use db::Connection; use hub_core::{ @@ -270,6 +270,7 @@ pub struct AppContext { metadata_json_loader: DataLoader, metadata_json_attributes_loader: DataLoader, collection_mints_loader: DataLoader, + single_collection_mint_loader: DataLoader, collection_mints_owner_loader: DataLoader, collection_drop_loader: DataLoader, drop_loader: DataLoader, @@ -311,6 +312,8 @@ impl AppContext { DataLoader::new(CollectionMintHistoryLoader::new(db.clone()), tokio::spawn); let drop_mint_history_loader = DataLoader::new(DropMintHistoryLoader::new(db.clone()), tokio::spawn); + let single_collection_mint_loader = + DataLoader::new(CollectionMintLoader::new(db.clone()), tokio::spawn); Self { db, @@ -324,6 +327,7 @@ impl AppContext { metadata_json_loader, metadata_json_attributes_loader, collection_mints_loader, + single_collection_mint_loader, collection_mints_owner_loader, collection_drop_loader, drop_loader, diff --git a/api/src/objects/collection.rs b/api/src/objects/collection.rs index 7b774b1..e86b2e8 100644 --- a/api/src/objects/collection.rs +++ b/api/src/objects/collection.rs @@ -13,7 +13,7 @@ use crate::{ }; /// An NFT collection that has either a fixed supply or unlimited mints. NFT collections are deployed to a desired blockchain. -/// On Solana, when the collection is associated to a drop it is a master_edition. When the collection is not associated to a drop it is a sized Metaplex certified collection. +/// On Solana, when the collection is associated to a drop it is a `master_edition`. When the collection is not associated to a drop it is a sized Metaplex certified collection. /// On EVM chains, the collection is a ERC-1155 token. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Collection { From 328bd9b1d6d2cc0cb19c051cb9dfa836c84af351 Mon Sep 17 00:00:00 2001 From: Kyle Espinola Date: Mon, 7 Aug 2023 14:30:42 +0200 Subject: [PATCH 2/3] feat: query mint_histories of a customer for all of their wallets --- api/src/dataloaders/mint_histories.rs | 37 +++++++++++++++++++++++++++ api/src/dataloaders/mod.rs | 4 ++- api/src/lib.rs | 6 ++++- api/src/objects/customer.rs | 29 ++++++++++++++++++++- 4 files changed, 73 insertions(+), 3 deletions(-) diff --git a/api/src/dataloaders/mint_histories.rs b/api/src/dataloaders/mint_histories.rs index 2c5b388..250d581 100644 --- a/api/src/dataloaders/mint_histories.rs +++ b/api/src/dataloaders/mint_histories.rs @@ -96,3 +96,40 @@ impl DataLoader for DropMintHistoryLoader { .collect()) } } + +#[derive(Debug, Clone)] +pub struct MinterLoader { + pub db: Connection, +} + +impl MinterLoader { + #[must_use] + pub fn new(db: Connection) -> Self { + Self { db } + } +} + +#[async_trait] +impl DataLoader for MinterLoader { + type Error = FieldError; + type Value = Vec; + + async fn load(&self, keys: &[String]) -> Result, Self::Error> { + let mint_histories = mint_histories::Entity::find() + .filter(mint_histories::Column::Wallet.is_in(keys.iter().map(ToOwned::to_owned))) + .all(self.db.get()) + .await?; + + Ok(mint_histories + .into_iter() + .fold(HashMap::new(), |mut acc, mint_history| { + acc.entry(mint_history.wallet.clone()) + .or_insert_with(Vec::new); + + acc.entry(mint_history.wallet.clone()) + .and_modify(|mint_histories| mint_histories.push(mint_history)); + + acc + })) + } +} diff --git a/api/src/dataloaders/mod.rs b/api/src/dataloaders/mod.rs index 549bf30..bd73092 100644 --- a/api/src/dataloaders/mod.rs +++ b/api/src/dataloaders/mod.rs @@ -23,6 +23,8 @@ pub use holders::Loader as HoldersLoader; pub use metadata_json::{ AttributesLoader as MetadataJsonAttributesLoader, Loader as MetadataJsonLoader, }; -pub use mint_histories::{CollectionMintHistoryLoader, DropMintHistoryLoader}; +pub use mint_histories::{ + CollectionMintHistoryLoader, DropMintHistoryLoader, MinterLoader as MinterMintHistoryLoader, +}; pub use project_collection::ProjectCollectionLoader; pub use project_collections::ProjectCollectionsLoader; diff --git a/api/src/lib.rs b/api/src/lib.rs index 03db489..d0d63f3 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -25,7 +25,7 @@ use dataloaders::{ CollectionDropLoader, CollectionLoader, CollectionMintHistoryLoader, CollectionMintLoader, CollectionMintsLoader, CollectionMintsOwnerLoader, CreatorsLoader, DropLoader, DropMintHistoryLoader, HoldersLoader, MetadataJsonAttributesLoader, MetadataJsonLoader, - ProjectCollectionLoader, ProjectCollectionsLoader, ProjectDropsLoader, + MinterMintHistoryLoader, ProjectCollectionLoader, ProjectCollectionsLoader, ProjectDropsLoader, }; use db::Connection; use hub_core::{ @@ -278,6 +278,7 @@ pub struct AppContext { holders_loader: DataLoader, collection_mint_history_loader: DataLoader, drop_mint_history_loader: DataLoader, + minter_mint_history_loader: DataLoader, } impl AppContext { @@ -314,6 +315,8 @@ impl AppContext { DataLoader::new(DropMintHistoryLoader::new(db.clone()), tokio::spawn); let single_collection_mint_loader = DataLoader::new(CollectionMintLoader::new(db.clone()), tokio::spawn); + let minter_mint_history_loader = + DataLoader::new(MinterMintHistoryLoader::new(db.clone()), tokio::spawn); Self { db, @@ -335,6 +338,7 @@ impl AppContext { holders_loader, collection_mint_history_loader, drop_mint_history_loader, + minter_mint_history_loader, } } } diff --git a/api/src/objects/customer.rs b/api/src/objects/customer.rs index 9b46dd5..6360b59 100644 --- a/api/src/objects/customer.rs +++ b/api/src/objects/customer.rs @@ -1,7 +1,10 @@ use async_graphql::{ComplexObject, Context, Result, SimpleObject}; use hub_core::uuid::Uuid; -use crate::{entities::collection_mints, AppContext}; +use crate::{ + entities::{collection_mints, mint_histories}, + AppContext, +}; /// A project customer. #[derive(SimpleObject, Debug, Clone)] @@ -40,4 +43,28 @@ impl Customer { Ok(None) } } + + /// The NFTs minted by the customer. + async fn mint_histories( + &self, + ctx: &Context<'_>, + ) -> Result>> { + let AppContext { + minter_mint_history_loader, + .. + } = ctx.data::()?; + + if let Some(addresses) = self.addresses.clone() { + Ok(Some( + minter_mint_history_loader + .load_many(addresses) + .await? + .into_values() + .flatten() + .collect(), + )) + } else { + Ok(None) + } + } } From 7bc1b2b22b4f88e8ad48c004edfd3690eafd282e Mon Sep 17 00:00:00 2001 From: Kyle Espinola Date: Mon, 7 Aug 2023 20:38:52 +0200 Subject: [PATCH 3/3] fix: add addresses to customer block to get ensure addresses are queried before requesting the mintHistories --- api/src/objects/customer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/objects/customer.rs b/api/src/objects/customer.rs index 6360b59..bf44f0c 100644 --- a/api/src/objects/customer.rs +++ b/api/src/objects/customer.rs @@ -45,6 +45,7 @@ impl Customer { } /// The NFTs minted by the customer. + #[graphql(requires = "addresses")] async fn mint_histories( &self, ctx: &Context<'_>,