diff --git a/src/upstream/lensv2/mod.rs b/src/upstream/lensv2/mod.rs index 2fd15811..4c498894 100644 --- a/src/upstream/lensv2/mod.rs +++ b/src/upstream/lensv2/mod.rs @@ -11,18 +11,12 @@ use crate::tigergraph::vertex::Identity; use crate::upstream::{ DataFetcher, DataSource, DomainNameSystem, Fetcher, Platform, Target, TargetProcessedList, }; -use crate::util::{ - make_client, make_http_client, naive_now, option_naive_datetime_from_utc_string, parse_body, -}; +use crate::util::{make_http_client, naive_now, utc_to_naive}; use async_trait::async_trait; -use cynic::QueryFragment; use cynic::{http::SurfExt, QueryBuilder}; -use http::uri::InvalidUri; -use hyper::body; -use hyper::Method; -use hyper::{client::HttpConnector, Body, Client}; -use serde::{Deserialize, Serialize}; -use tracing::{debug, info, warn}; +use hyper::{client::HttpConnector, Client}; +use tracing::{trace, warn}; +use uuid::Uuid; mod schema { cynic::use_schema!("src/upstream/lensv2/schema.graphql"); @@ -31,7 +25,11 @@ mod schema { // Query by Handles #[derive(cynic::QueryVariables, Debug, Default)] pub struct ProfilesRequestVariables { + #[cynic(skip_serializing_if = "Option::is_none")] pub handles: Option>, + #[cynic(skip_serializing_if = "Option::is_none")] + #[cynic(rename = "ownedBy")] + pub owned_by: Option>, } #[derive(cynic::QueryFragment, Debug)] @@ -41,7 +39,7 @@ pub struct ProfilesRequestVariables { variables = "ProfilesRequestVariables" )] pub struct ProfileQueryByHandles { - #[arguments(request: { where: { handles: $handles}} )] + #[arguments(request: { where: { handles: $handles, ownedBy: $owned_by}} )] pub profiles: PaginatedProfileResult, } @@ -51,7 +49,7 @@ pub struct PaginatedProfileResult { pub items: Vec, } -#[derive(cynic::QueryFragment, Debug)] +#[derive(cynic::QueryFragment, Debug, Clone)] #[cynic(schema_path = "src/upstream/lensv2/schema.graphql")] pub struct Profile { pub id: ProfileId, @@ -62,20 +60,20 @@ pub struct Profile { pub tx_hash: TxHash, } -#[derive(cynic::QueryFragment, Debug)] +#[derive(cynic::QueryFragment, Debug, Clone)] #[cynic(schema_path = "src/upstream/lensv2/schema.graphql")] pub struct ProfileMetadata { pub display_name: Option, } -#[derive(cynic::QueryFragment, Debug)] +#[derive(cynic::QueryFragment, Debug, Clone)] #[cynic(schema_path = "src/upstream/lensv2/schema.graphql")] pub struct NetworkAddress { pub address: EvmAddress, pub chain_id: ChainId, } -#[derive(cynic::QueryFragment, Debug)] +#[derive(cynic::QueryFragment, Debug, Clone)] #[cynic(schema_path = "src/upstream/lensv2/schema.graphql")] pub struct HandleInfo { pub id: TokenId, @@ -86,7 +84,7 @@ pub struct HandleInfo { } #[derive(cynic::Scalar, Debug, Clone)] -pub struct ChainId(pub String); +pub struct ChainId(pub u32); #[derive(cynic::Scalar, Debug, Clone)] pub struct DateTime(pub String); @@ -129,12 +127,12 @@ impl Fetcher for LensV2 { async fn fetch_by_lens_handle(target: &Target) -> Result { let target_var = target.identity()?; - let handle = target_var.trim_end_matches(".lens"); - let full_handle = format!("lens/{}", handle); + let handle_name = target_var.trim_end_matches(".lens"); + let full_handle = format!("lens/{}", handle_name); let operation = ProfileQueryByHandles::build(ProfilesRequestVariables { - handles: Some(vec![Handle(full_handle)]), + handles: Some(vec![Handle(full_handle.clone())]), + owned_by: None, }); - println!("{}", operation.query); let response = surf::post(C.upstream.lens_api.url.clone()) .run_graphql(operation) .await; @@ -146,10 +144,136 @@ async fn fetch_by_lens_handle(target: &Target) -> Result = Vec::new(); + for profile in profiles.iter() { + let t = save_profile(&cli, profile).await?; + if let Some(t) = t { + next_targets.push(t); + } + } + + Ok(next_targets) } async fn fetch_by_wallet(target: &Target) -> Result { - todo!() + let target_var = target.identity()?; + let owned_by_evm = target_var.to_lowercase(); + let operation = ProfileQueryByHandles::build(ProfilesRequestVariables { + handles: None, + owned_by: Some(vec![EvmAddress(owned_by_evm.clone())]), + }); + let response = surf::post(C.upstream.lens_api.url.clone()) + .run_graphql(operation) + .await; + + if response.is_err() { + warn!( + "LensV2 target {} | Failed to fetch: {}", + target, + response.unwrap_err(), + ); + return Ok(vec![]); + } + let cli = make_http_client(); + let profiles = response + .unwrap() + .data + .map_or(vec![], |data| data.profiles.items); + let mut next_targets: Vec = Vec::new(); + for profile in profiles.iter() { + let t = save_profile(&cli, profile).await?; + if let Some(t) = t { + next_targets.push(t); + } + } + + Ok(next_targets) +} + +async fn save_profile( + client: &Client, + profile: &Profile, +) -> Result, Error> { + if profile.handle.clone().is_none() { + return Ok(None); + } + let handle_info = profile.handle.clone().unwrap(); + let owner = profile.owned_by.address.0.to_ascii_lowercase(); + let lens_handle = format!("{}.{}", handle_info.local_name, handle_info.namespace); + let lens_display_name = profile + .metadata + .clone() + .map_or(None, |metadata| metadata.display_name); + let created_at = utc_to_naive(profile.created_at.clone().0)?; + + let addr: Identity = Identity { + uuid: Some(Uuid::new_v4()), + platform: Platform::Ethereum, + identity: owner.clone(), + uid: None, + created_at: None, + display_name: None, + added_at: naive_now(), + avatar_url: None, + profile_url: None, + updated_at: naive_now(), + expired_at: None, + reverse: Some(false), + }; + + let lens: Identity = Identity { + uuid: Some(Uuid::new_v4()), + platform: Platform::Lens, + identity: lens_handle.clone(), + uid: Some(profile.id.clone().0.to_string()), + created_at: Some(created_at), + display_name: lens_display_name, + added_at: naive_now(), + avatar_url: None, + profile_url: Some("https://hey.xyz/u/".to_owned() + &handle_info.local_name), + updated_at: naive_now(), + expired_at: None, + reverse: Some(true), + }; + + let hold: Hold = Hold { + uuid: Uuid::new_v4(), + source: DataSource::Lens, + transaction: Some(profile.tx_hash.clone().0), + id: profile.id.clone().0.to_string(), + created_at: Some(created_at), + updated_at: naive_now(), + fetcher: DataFetcher::RelationService, + expired_at: None, + }; + + let resolve: Resolve = Resolve { + uuid: Uuid::new_v4(), + source: DataSource::Lens, + system: DomainNameSystem::Lens, + name: lens_handle.clone(), + fetcher: DataFetcher::RelationService, + updated_at: naive_now(), + }; + + // field `is_default` has been canceled in lens-v2-api + let reverse: Resolve = Resolve { + uuid: Uuid::new_v4(), + source: DataSource::Lens, + system: DomainNameSystem::Lens, + name: lens_handle.clone(), + fetcher: DataFetcher::RelationService, + updated_at: naive_now(), + }; + trace!("LensV2 Ethereum({}) handle: {}", owner, lens_handle); + create_identity_to_identity_hold_record(client, &addr, &lens, &hold).await?; + create_identity_domain_resolve_record(client, &lens, &addr, &resolve).await?; + create_identity_domain_reverse_resolve_record(client, &addr, &lens, &reverse).await?; + Ok(Some(Target::Identity(Platform::Ethereum, owner.clone()))) } diff --git a/src/upstream/lensv2/tests.rs b/src/upstream/lensv2/tests.rs index e9ee462d..3dd827ce 100644 --- a/src/upstream/lensv2/tests.rs +++ b/src/upstream/lensv2/tests.rs @@ -9,19 +9,34 @@ mod tests { util::make_http_client, }; + #[tokio::test] + async fn test_fetch_by_wallet() -> Result<(), Error> { + let target = Target::Identity( + Platform::Ethereum, + String::from("0x934B510D4C9103E6a87AEf13b816fb080286D649"), + ); + let _ = LensV2::fetch(&target).await?; + let client = make_http_client(); + let found = Identity::find_by_platform_identity(&client, &Platform::Lens, "sujiyan.lens") + .await? + .expect("Record not found"); + print!("found: {:?}", found); + Ok(()) + } + #[tokio::test] async fn test_fetch_by_lens_handle() -> Result<(), Error> { let target = Target::Identity(Platform::Lens, String::from("sujiyan.lens")); let _ = LensV2::fetch(&target).await?; - // let client = make_http_client(); - // let found = Identity::find_by_platform_identity( - // &client, - // &Platform::Ethereum, - // "0x0fefed77bb715e96f1c35c1a4e0d349563d6f6c0", - // ) - // .await? - // .expect("Record not found"); - // print!("found: {:?}", found); + let client = make_http_client(); + let found = Identity::find_by_platform_identity( + &client, + &Platform::Ethereum, + &String::from("0x934B510D4C9103E6a87AEf13b816fb080286D649").to_lowercase(), + ) + .await? + .expect("Record not found"); + print!("found: {:?}", found); Ok(()) } }