From 76840d8a8460bd7990bbc8ae12ba2e2b5a8589cc Mon Sep 17 00:00:00 2001 From: raykast Date: Mon, 23 Oct 2023 15:50:59 -0700 Subject: [PATCH] Add pagination to slower fields. --- api/src/objects/collection.rs | 161 +++++++++++++++++++++++++++++----- api/src/objects/drop.rs | 59 +++++++++++-- 2 files changed, 190 insertions(+), 30 deletions(-) diff --git a/api/src/objects/collection.rs b/api/src/objects/collection.rs index 9399284..99e3ccb 100644 --- a/api/src/objects/collection.rs +++ b/api/src/objects/collection.rs @@ -1,10 +1,10 @@ -use async_graphql::{Context, Error, Object, Result}; -use sea_orm::entity::prelude::*; +use async_graphql::{connection, Context, Error, Object, Result}; +use sea_orm::{entity::prelude::*, JoinType, Order, QueryOrder, QuerySelect}; use super::{metadata_json::MetadataJson, CollectionMint, Drop, Holder}; use crate::{ entities::{ - collection_creators, + collection_creators, collection_mints, collections::Model, mint_histories, sea_orm_active_enums::{Blockchain, CreationStatus}, @@ -120,13 +120,48 @@ impl Collection { } /// The list of minted NFTs from the collection including the NFTs address and current owner's wallet address. - async fn mints(&self, ctx: &Context<'_>) -> Result>> { - let AppContext { - collection_mints_loader, - .. - } = ctx.data::()?; - - collection_mints_loader.load_one(self.id).await + async fn mints( + &self, + ctx: &Context<'_>, + after: Option, + first: Option, + ) -> Result> { + connection::query( + after, + None, + first, + None, + |after, before, first, last| async move { + const MAX_LIMIT: u64 = 32; + + assert!(before.is_none()); + assert!(last.is_none()); + let offset = after.map_or(0, |a| a + 1); + let limit = first + .and_then(|f| u64::try_from(f).ok()) + .map_or(MAX_LIMIT, |f| f.min(MAX_LIMIT)); + + let mut conn = connection::Connection::new(offset > 0, true); + + let AppContext { db, .. } = ctx.data::()?; + + let mints = collection_mints::Entity::find() + .filter(collection_mints::Column::Id.eq(self.id)) + .offset(offset) + .limit(limit) + .all(db.get()) + .await?; + + conn.edges + .extend(mints.into_iter().enumerate().map(|(i, m)| { + let n = offset + u64::try_from(i).unwrap(); + connection::Edge::with_additional_fields(n, n, m.into()) + })); + + Result::<_>::Ok(conn) + }, + ) + .await } /// The list of attributed creators for the collection. @@ -139,10 +174,56 @@ impl Collection { } /// The list of current holders of NFTs from the collection. - async fn holders(&self, ctx: &Context<'_>) -> Result>> { - let AppContext { holders_loader, .. } = ctx.data::()?; - - holders_loader.load_one(self.id).await + async fn holders( + &self, + ctx: &Context<'_>, + after: Option, + first: Option, + ) -> Result> { + connection::query( + after, + None, + first, + None, + |after, before, first, last| async move { + const MAX_LIMIT: u64 = 32; + + assert!(before.is_none()); + assert!(last.is_none()); + let offset = after.map_or(0, |a| a + 1); + let limit = first + .and_then(|f| u64::try_from(f).ok()) + .map_or(MAX_LIMIT, |f| f.min(MAX_LIMIT)); + + let mut conn = connection::Connection::new(offset > 0, true); + + let AppContext { db, .. } = ctx.data::()?; + + let holders = collection_mints::Entity::find() + .filter(collection_mints::Column::CollectionId.eq(self.id)) + .filter(collection_mints::Column::Owner.is_not_null()) + .select_only() + .column(collection_mints::Column::CollectionId) + .column_as(collection_mints::Column::Owner, "address") + .column_as(collection_mints::Column::Id.count(), "owns") + .group_by(collection_mints::Column::Owner) + .group_by(collection_mints::Column::CollectionId) + .offset(offset) + .limit(limit) + .into_model::() + .all(db.get()) + .await?; + + conn.edges + .extend(holders.into_iter().enumerate().map(|(i, m)| { + let n = offset + u64::try_from(i).unwrap(); + connection::Edge::with_additional_fields(n, n, m) + })); + + Result::<_>::Ok(conn) + }, + ) + .await } #[graphql(deprecation = "Use `mint_histories` instead")] @@ -160,13 +241,51 @@ impl Collection { async fn mint_histories( &self, ctx: &Context<'_>, - ) -> Result>> { - let AppContext { - collection_mint_history_loader, - .. - } = ctx.data::()?; - - collection_mint_history_loader.load_one(self.id).await + after: Option, + first: Option, + ) -> Result> + { + connection::query( + after, + None, + first, + None, + |after, before, first, last| async move { + const MAX_LIMIT: u64 = 32; + + assert!(before.is_none()); + assert!(last.is_none()); + let offset = after.map_or(0, |a| a + 1); + let limit = first + .and_then(|f| u64::try_from(f).ok()) + .map_or(MAX_LIMIT, |f| f.min(MAX_LIMIT)); + + let mut conn = connection::Connection::new(offset > 0, true); + + let AppContext { db, .. } = ctx.data::()?; + + let mint_histories = mint_histories::Entity::find() + .join( + JoinType::InnerJoin, + mint_histories::Relation::CollectionMints.def(), + ) + .filter(collection_mints::Column::CollectionId.eq(self.id)) + .order_by(mint_histories::Column::CreatedAt, Order::Desc) + .offset(offset) + .limit(limit) + .all(db.get()) + .await?; + + conn.edges + .extend(mint_histories.into_iter().enumerate().map(|(i, m)| { + let n = offset + u64::try_from(i).unwrap(); + connection::Edge::with_additional_fields(n, n, m) + })); + + Result::<_>::Ok(conn) + }, + ) + .await } async fn drop(&self, ctx: &Context<'_>) -> Result> { diff --git a/api/src/objects/drop.rs b/api/src/objects/drop.rs index d4106fb..b85eeba 100644 --- a/api/src/objects/drop.rs +++ b/api/src/objects/drop.rs @@ -1,11 +1,11 @@ -use async_graphql::{Context, Enum, Error, Object, Result}; +use async_graphql::{connection, Context, Enum, Error, Object, Result}; use hub_core::chrono::Utc; -use sea_orm::entity::prelude::*; +use sea_orm::{entity::prelude::*, JoinType, QuerySelect}; use super::{Collection, CollectionMint}; use crate::{ entities::{ - drops, mint_histories, + collection_mints, drops, mint_histories, sea_orm_active_enums::{CreationStatus, DropType}, }, AppContext, @@ -155,13 +155,54 @@ impl Drop { } } - async fn queued_mints(&self, ctx: &Context<'_>) -> Result>> { - let AppContext { - queued_mints_loader, - .. - } = ctx.data::()?; + async fn queued_mints( + &self, + ctx: &Context<'_>, + after: Option, + first: Option, + ) -> Result> { + connection::query( + after, + None, + first, + None, + |after, before, first, last| async move { + const MAX_LIMIT: u64 = 32; + + assert!(before.is_none()); + assert!(last.is_none()); + let offset = after.map_or(0, |a| a + 1); + let limit = first + .and_then(|f| u64::try_from(f).ok()) + .map_or(MAX_LIMIT, |f| f.min(MAX_LIMIT)); + + let mut conn = connection::Connection::new(offset > 0, true); - queued_mints_loader.load_one(self.id).await + let AppContext { db, .. } = ctx.data::()?; + + let mints = collection_mints::Entity::find() + .join( + JoinType::InnerJoin, + collection_mints::Relation::Collections.def(), + ) + .join(JoinType::InnerJoin, drops::Relation::Collections.def()) + .filter(drops::Column::Id.eq(self.id)) + .filter(collection_mints::Column::CreationStatus.eq(CreationStatus::Queued)) + .offset(offset) + .limit(limit) + .all(db.get()) + .await?; + + conn.edges + .extend(mints.into_iter().enumerate().map(|(i, m)| { + let n = offset + u64::try_from(i).unwrap(); + connection::Edge::with_additional_fields(n, n, m.into()) + })); + + Result::<_>::Ok(conn) + }, + ) + .await } #[graphql(deprecation = "Use `mint_histories` under `Collection` Object instead.")]