Skip to content

Commit

Permalink
Merge pull request #144 from holaplex/abdul/import-collection
Browse files Browse the repository at this point in the history
Import collection mutation & indexing
  • Loading branch information
imabdulbasit authored Jul 26, 2023
2 parents 531f348 + 12bd742 commit ea11461
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 17 deletions.
8 changes: 4 additions & 4 deletions api/proto.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ sha512 = "d75800df0d4744c6b0f4d9a9952d3bfd0bb6b24a8babd19104cc11b54a525f85551b3c

[[schemas]]
subject = "nfts"
version = 21
sha512 = "7859cdec771803b1f115375833215b89fafd553cc769a824376032f2d44494b8aee5d82f868dab8189bfd10fb09b68668ed74de916294aacf721448184d381f1"
version = 22
sha512 = "c9920f6a5792b067396c88e40b9bd2adfcb55b582734aff924a67a9d5841a5e2839fc734c1bbff66f402f9a9d8852ca5fef1339aaaa3d5b05aa7868ddfa375c1"

[[schemas]]
subject = "organization"
Expand All @@ -20,8 +20,8 @@ sha512 = "c5ddf43d2958ec690ee2261d0ff9808b67ce810d2fc4b6077f96f561929a920f03509f

[[schemas]]
subject = "solana_nfts"
version = 6
sha512 = "106f013837b6efc1449778e7beb825d501214af42bd60153c8f09dfd25e3084b46782093ab6f1e5f194b99d9a5cb5eb9b72fec2ba953ae92c1e8ce9724264b40"
version = 7
sha512 = "73570b9e58f91a06901ba6455986ce1a0d3675e33860d2447160d711a8cebcfb78cfc714fb08644ad83495dc8612b0b123203561af6d93d29ffb0256725047ba"

[[schemas]]
subject = "timestamp"
Expand Down
4 changes: 2 additions & 2 deletions api/proto.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ endpoint = "https://schemas.holaplex.tools"

[schemas]
organization = 5
nfts = 21
nfts = 22
customer = 2
treasury = 17
solana_nfts = 6
solana_nfts = 7
polygon_nfts = 6
timestamp = 1
209 changes: 203 additions & 6 deletions api/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ use sea_orm::{
use crate::{
db::Connection,
entities::{
collection_mints, collections, customer_wallets, drops, nft_transfers,
collection_creators, collection_mints, collections, customer_wallets, drops,
metadata_json_attributes, metadata_json_files, metadata_jsons, mint_creators,
nft_transfers,
prelude::{CollectionMints, Purchases},
project_wallets, purchases,
sea_orm_active_enums::{Blockchain, CreationStatus},
Expand All @@ -27,10 +29,10 @@ use crate::{
Blockchain as ProtoBlockchainEnum, CustomerWallet, Event as TreasuryEvent,
PolygonTransactionResult, ProjectWallet, TransactionStatus,
},
CreationStatus as NftCreationStatus, DropCreation, MintCollectionCreation, MintCreation,
MintOwnershipUpdate, MintedTokensOwnershipUpdate, NftEventKey, NftEvents,
SolanaCompletedMintTransaction, SolanaCompletedTransferTransaction, SolanaNftEventKey,
TreasuryEventKey,
Attribute, CreationStatus as NftCreationStatus, DropCreation, File, Metadata,
MintCollectionCreation, MintCreation, MintOwnershipUpdate, MintedTokensOwnershipUpdate,
NftEventKey, NftEvents, SolanaCollectionPayload, SolanaCompletedMintTransaction,
SolanaCompletedTransferTransaction, SolanaMintPayload, SolanaNftEventKey, TreasuryEventKey,
},
Actions, Services,
};
Expand Down Expand Up @@ -96,7 +98,14 @@ impl Processor {
},
None | Some(_) => Ok(()),
},
Services::Solana(SolanaNftEventKey { id, .. }, e) => match e.event {
Services::Solana(
SolanaNftEventKey {
id,
project_id,
user_id,
},
e,
) => match e.event {
Some(
SolanaNftsEvent::CreateDropSubmitted(payload)
| SolanaNftsEvent::RetryCreateDropSubmitted(payload),
Expand Down Expand Up @@ -153,6 +162,12 @@ impl Processor {
self.drop_minted(id, MintResult::Failure).await
},
Some(SolanaNftsEvent::UpdateMintOwner(e)) => self.update_mint_owner(id, e).await,
Some(SolanaNftsEvent::ImportedExternalCollection(e)) => {
self.index_collection(id, project_id, user_id, e).await
},
Some(SolanaNftsEvent::ImportedExternalMint(e)) => {
self.index_mint(id, user_id, e).await
},
None | Some(_) => Ok(()),
},
Services::Polygon(_, e) => match e.event {
Expand All @@ -164,6 +179,154 @@ impl Processor {
}
}

async fn index_collection(
&self,
id: String,
project_id: String,
created_by: String,
payload: SolanaCollectionPayload,
) -> Result<()> {
let SolanaCollectionPayload {
supply,
mint_address,
seller_fee_basis_points,
creators,
metadata,
files,
..
} = payload;

let metadata = metadata.context("no collection metadata found")?;

let Metadata {
name,
description,
symbol,
attributes,
uri,
image,
} = metadata;

let collection_am = collections::ActiveModel {
id: Set(id.parse()?),
blockchain: Set(Blockchain::Solana),
supply: Set(supply.map(Into::into)),
project_id: Set(project_id.parse()?),
credits_deduction_id: Set(None),
creation_status: Set(CreationStatus::Created),
total_mints: Set(-1),
address: Set(Some(mint_address)),
signature: Set(None),
seller_fee_basis_points: Set(seller_fee_basis_points.try_into()?),
created_by: Set(created_by.parse()?),
created_at: Set(Utc::now().into()),
};

collection_am.insert(self.db.get()).await?;

let metadata_json = metadata_jsons::ActiveModel {
id: Set(id.parse()?),
name: Set(name),
uri: Set(uri),
symbol: Set(symbol),
description: Set(description.unwrap_or_default()),
image: Set(image),
animation_url: Set(None),
external_url: Set(None),
..Default::default()
};

let json_model = metadata_json.insert(self.db.get()).await?;
for creator in creators {
let collection_creator = collection_creators::ActiveModel {
collection_id: Set(id.parse()?),
address: Set(creator.address),
verified: Set(creator.verified),
share: Set(creator.share.try_into()?),
};
collection_creator.insert(self.db.get()).await?;
}
index_attributes(&self.db, json_model.id, attributes).await?;
index_files(&self.db, json_model.id, files).await?;

Ok(())
}

async fn index_mint(
&self,
id: String,
created_by: String,
payload: SolanaMintPayload,
) -> Result<()> {
let SolanaMintPayload {
collection_id,
mint_address,
owner,
seller_fee_basis_points,
compressed,
creators,
files,
metadata,
..
} = payload;

let metadata = metadata.context("no collection metadata found")?;

let Metadata {
name,
description,
symbol,
attributes,
uri,
image,
} = metadata;

let mint_am = collection_mints::ActiveModel {
id: Set(id.parse()?),
collection_id: Set(collection_id.parse()?),
address: Set(Some(mint_address)),
owner: Set(owner),
creation_status: Set(CreationStatus::Created),
created_by: Set(created_by.parse()?),
created_at: Set(Utc::now().into()),
signature: Set(None),
edition: Set(-1),
seller_fee_basis_points: Set(seller_fee_basis_points.try_into()?),
credits_deduction_id: Set(None),
compressed: Set(compressed),
};

let mint_model = mint_am.insert(self.db.get()).await?;

let metadata_json = metadata_jsons::ActiveModel {
id: Set(id.parse()?),
name: Set(name),
uri: Set(uri),
symbol: Set(symbol),
description: Set(description.unwrap_or_default()),
image: Set(image),
animation_url: Set(None),
external_url: Set(None),
..Default::default()
};

let json_model = metadata_json.insert(self.db.get()).await?;

for creator in creators {
let mint_creator_am = mint_creators::ActiveModel {
collection_mint_id: Set(mint_model.id),
address: Set(creator.address),
verified: Set(creator.verified),
share: Set(creator.share.try_into()?),
};
mint_creator_am.insert(self.db.get()).await?;
}
index_attributes(&self.db, json_model.id, attributes).await?;
index_files(&self.db, json_model.id, files).await?;

Ok(())
}

async fn update_mint_owner(&self, id: String, payload: MintOwnershipUpdate) -> Result<()> {
let id = Uuid::from_str(&id)?;
let db = self.db.get();
Expand Down Expand Up @@ -607,3 +770,37 @@ impl From<PolygonTransactionResult> for TransferResult {
}
}
}

async fn index_attributes(
db: &Connection,
json_id: Uuid,
attributes: Vec<Attribute>,
) -> Result<()> {
for attr in attributes {
let attribute = metadata_json_attributes::ActiveModel {
metadata_json_id: Set(json_id),
trait_type: Set(attr.trait_type),
value: Set(attr.value),
..Default::default()
};

attribute.insert(db.get()).await?;
}

Ok(())
}

async fn index_files(db: &Connection, json_id: Uuid, files: Vec<File>) -> Result<()> {
for file in files {
let file_am = metadata_json_files::ActiveModel {
metadata_json_id: Set(json_id),
uri: Set(Some(file.uri)),
file_type: Set(file.mime),
..Default::default()
};

file_am.insert(db.get()).await?;
}

Ok(())
}
90 changes: 85 additions & 5 deletions api/src/mutations/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ use crate::{
collection::Collection,
db::Connection,
entities::{
collection_creators, collections, metadata_jsons,
prelude::{CollectionCreators, Collections, MetadataJsons},
collection_creators, collection_mints, collections, metadata_jsons,
prelude::{CollectionCreators, CollectionMints, Collections, MetadataJsons},
project_wallets,
sea_orm_active_enums::{Blockchain as BlockchainEnum, CreationStatus},
},
metadata_json::MetadataJson,
objects::{Collection as CollectionObject, Creator, MetadataJsonInput},
proto::{
nft_events::Event as NftEvent, CollectionCreation, CreationStatus as NftCreationStatus,
Creator as ProtoCreator, MasterEdition, MetaplexMasterEditionTransaction, NftEventKey,
NftEvents,
nft_events::Event as NftEvent, CollectionCreation, CollectionImport,
CreationStatus as NftCreationStatus, Creator as ProtoCreator, MasterEdition,
MetaplexMasterEditionTransaction, NftEventKey, NftEvents,
},
Actions, AppContext, NftStorageClient, OrganizationId, UserID,
};
Expand Down Expand Up @@ -255,6 +255,74 @@ impl Mutation {
})
}

pub async fn import_solana_collection(

Check warning on line 258 in api/src/mutations/collection.rs

View workflow job for this annotation

GitHub Actions / clippy/check/doc

docs for function returning `Result` missing `# Errors` section
&self,
ctx: &Context<'_>,
input: ImportCollectionInput,
) -> Result<ImportCollectionPayload> {
let nfts_producer = ctx.data::<Producer<NftEvents>>()?;
let AppContext { db, user_id, .. } = ctx.data::<AppContext>()?;
let user_id = user_id.0.ok_or(Error::new("X-USER-ID header not found"))?;

let txn = db.get().begin().await?;

validate_solana_address(&input.collection)?;

let collection = Collections::find()
.filter(collections::Column::Address.eq(input.collection.clone()))
.one(db.get())
.await?;

if let Some(collection) = collection {
let mints = CollectionMints::find()
.filter(collection_mints::Column::CollectionId.eq(collection.id))
.all(&txn)
.await?;

if let Some(collection_json) =
MetadataJsons::find_by_id(collection.id).one(&txn).await?
{
collection_json.delete(&txn).await?;
}

let mint_ids = mints.iter().map(|m| m.id).collect::<Vec<_>>();

let mint_jsons = MetadataJsons::find()
.filter(metadata_jsons::Column::Id.is_in(mint_ids))
.all(&txn)
.await?;

for json in mint_jsons {
json.delete(&txn).await?;
}

collection.delete(&txn).await?;

txn.commit().await?;
}

nfts_producer
.send(
Some(&NftEvents {
event: Some(NftEvent::StartedImportingSolanaCollection(
CollectionImport {
mint_address: input.collection,
},
)),
}),
Some(&NftEventKey {
id: String::new(),
project_id: input.project.to_string(),
user_id: user_id.to_string(),
}),
)
.await?;

Ok(ImportCollectionPayload {
status: CreationStatus::Pending,
})
}

/// This mutation allows updating a drop and it's associated collection by ID.
/// It returns an error if it fails to reach the database, emit update events or assemble the on-chain transaction.
/// Returns the `PatchDropPayload` object on success.
Expand Down Expand Up @@ -656,3 +724,15 @@ pub struct PatchCollectionPayload {
/// The drop that has been patched.
collection: CollectionObject,
}

#[derive(Debug, Clone, Serialize, Deserialize, InputObject)]
pub struct ImportCollectionInput {
project: Uuid,
// Mint address of Metaplex Certified Collection NFT
collection: String,
}

#[derive(Debug, Clone, SimpleObject)]
pub struct ImportCollectionPayload {
status: CreationStatus,
}

0 comments on commit ea11461

Please sign in to comment.