From 6a0a31f720d465fa21d34bfa323bbdf40a5ff280 Mon Sep 17 00:00:00 2001
From: Hugo Trentesaux <hugo@trentesaux.fr>
Date: Wed, 31 Jan 2024 17:19:41 +0100
Subject: [PATCH] add info on identity command

---
 README.md                   |  7 ++--
 res/indexer-queries.graphql | 29 +++++++++--------
 src/commands/identity.rs    | 64 ++++++++++++++++++++++---------------
 src/commands/smith.rs       | 19 +++--------
 src/indexer.rs              | 60 ++++++++++++++++++----------------
 5 files changed, 94 insertions(+), 85 deletions(-)

diff --git a/README.md b/README.md
index f6f9e80..e4ae8d8 100644
--- a/README.md
+++ b/README.md
@@ -74,13 +74,14 @@ Secret key format can be changed using `--secret-format` with the following valu
 - [x] migrate all xt to submit_call_and_look_event
 - [x] add transfer with unit (ĞD, UD...)
 - [x] add more runtime-info like cert count, wot and smith params...
+- [x] add more info on identity view like status, number certs emitted, received
+- [x] implement squid indexer to get cert list
+- [ ] implement indexer tx history...
 - [ ] add more info like sudo key, block interval
 - [ ] better format runtime info block number (duration in days...) and perbill (%)
 - [ ] add proper tabulation for runtime info
 - [ ] add clap complete for autocompletion
-- [ ] allow to listen to multiple events (like CertAdded and CertRenewed)
-- [ ] add more info on identity view like status, number certs emitted, received
-- [ ] implement squid indexer to get cert list + tx history...
+- [ ] allow to listen to multiple events (like SmithCertAdded and SmithMembershipAdded)
 - [ ] 
 - [ ] 
 - [ ] 
diff --git a/res/indexer-queries.graphql b/res/indexer-queries.graphql
index 23e58ba..5ede81e 100644
--- a/res/indexer-queries.graphql
+++ b/res/indexer-queries.graphql
@@ -1,30 +1,31 @@
-query IdentityByIndex($index: Int!) {
+query IdentityNameByIndex($index: Int!) {
   identities(where: {index_eq: $index}) {
-    index
     name
-    account {
-      id
-    }
   }
 }
 
-query IdentityByName($name: String!) {
-  identities(where: {name_eq: $name}) {
-    index
+query IdentityInfo($index: Int!) {
+  identities(where: {index_eq: $index}) {
     name
-    account {
+    certIssued(where: {active_eq: true}) {
+      receiver {
+        name
+      }
+    }
+    certReceived(where: {active_eq: true}) {
+      issuer {
+        name
+      }
+    }
+    linkedAccount {
       id
     }
   }
 }
 
-query IdentityByPubkey($pubkey: String!) {
+query IdentityNameByPubkey($pubkey: String!) {
   identities(where: {account: {id_eq: $pubkey}}) {
-    index
     name
-    account {
-      id
-    }
   }
 }
 
diff --git a/src/commands/identity.rs b/src/commands/identity.rs
index 5755011..de00263 100644
--- a/src/commands/identity.rs
+++ b/src/commands/identity.rs
@@ -152,37 +152,35 @@ pub async fn get_identity(
 	account_id: Option<AccountId>,
 	identity_id: Option<IdtyId>,
 	username: Option<String>,
-) -> Result<(), anyhow::Error> {
+) -> Result<(), GcliError> {
 	let client = data.client();
 	let indexer = data.indexer.clone();
 
-	// fetch reachable information using Duniter only (no indexer)
-	let (idty, value) = match (&account_id, identity_id, &username) {
-		// idty_id → account_id
-		(None, Some(idty), None) => (idty, get_identity_by_index(client, idty).await?),
-		// account_id → idty_id
-		(Some(account_id), None, None) => {
-			let idty = get_idty_index_by_account_id(client, account_id)
+	// get idty_id
+	let idty =
+		match (identity_id, &account_id, &username) {
+			// idty_id
+			(Some(idty), None, None) => idty,
+			// account_id → idty_id
+			(None, Some(account_id), None) => get_idty_index_by_account_id(client, account_id)
 				.await?
-				.ok_or_else(|| anyhow!("no identity for this account id"))?;
-			(idty, get_identity_by_index(client, idty).await?)
-		}
-		// username → idty_id and account_id
-		(None, None, Some(username)) => {
-			let idty = get_idty_index_by_name(client, username)
+				.ok_or_else(|| anyhow!("no identity for account '{account_id}'"))?,
+			// username → idty_id
+			(None, None, Some(username)) => get_idty_index_by_name(client, username)
 				.await?
-				.ok_or_else(|| anyhow!("no identity found for this username"))?;
-			(idty, get_identity_by_index(client, idty).await?)
-		}
-		_ => {
-			return Err(anyhow!(
-				"One and only one argument is needed to fetch the identity."
-			));
-		}
-	};
-	let value = value.ok_or_else(|| anyhow!("no identity value"))?;
+				.ok_or_else(|| anyhow!("no identity for name '{username}'"))?,
+			_ => {
+				return Err(GcliError::Logic(
+					"One and only one argument is needed to fetch the identity.".to_string(),
+				));
+			}
+		};
+	// idty_id → value
+	let value = get_identity_by_index(client, idty)
+		.await?
+		.ok_or_else(|| anyhow!("no identity value for index {idty}"))?;
 
-	// print result
+	// --- print result ---
 	// 1. identity index
 	println!("Identity index: {idty}",);
 	// 2. username (indexer needed if not provided)
@@ -190,7 +188,7 @@ pub async fn get_identity(
 		indexer
 			.username_by_index(idty)
 			.await?
-			.ok_or_else(|| anyhow!("indexer does not have username for this index"))?
+			.ok_or_else(|| anyhow!("indexer does not have username for this index {idty}"))?
 	} else {
 		"<no indexer>".to_string()
 	});
@@ -200,6 +198,20 @@ pub async fn get_identity(
 	// 4. status
 	println!("Status:         {:?}", value.status);
 
+	// get more info
+	if let Some(indexer) = &indexer {
+		let info = indexer.identity_info(idty).await.expect("no info");
+		println!(
+			"Certifications: issued {}, received {}",
+			info.cert_issued.len(),
+			info.cert_received.len()
+		);
+		let a = info.linked_account.len();
+		if a > 1 {
+			println!("Linked accounts: {a}");
+		}
+	}
+
 	Ok(())
 }
 
diff --git a/src/commands/smith.rs b/src/commands/smith.rs
index 827e77c..163ad3d 100644
--- a/src/commands/smith.rs
+++ b/src/commands/smith.rs
@@ -281,18 +281,9 @@ pub async fn accept_invitation(data: &Data) -> Result<(), subxt::Error> {
 
 /// invite identity to join smith
 pub async fn certify_smith(data: &Data, target: IdtyId) -> Result<(), subxt::Error> {
-	// submit_call_and_look_event::<
-	// 	runtime::smith_members::events::CertificationReceived,
-	// 	Payload<runtime::smith_members::calls::types::CertifySmith>,
-	// >(data, &runtime::tx().smith_members().certify_smith(target))
-	// .await
-	let progress = submit_call(data, &runtime::tx().smith_members().certify_smith(target)).await?;
-	if data.args.no_wait {
-		return Ok(());
-	}
-	let events = track_progress(progress).await?;
-	// look for the expected event
-	look_event::<runtime::smith_members::events::SmithCertAdded>(data, &events)?;
-	look_event::<runtime::smith_members::events::SmithMembershipAdded>(data, &events)?;
-	Ok(())
+	submit_call_and_look_event::<
+		runtime::smith_members::events::SmithCertAdded,
+		Payload<runtime::smith_members::calls::types::CertifySmith>,
+	>(data, &runtime::tx().smith_members().certify_smith(target))
+	.await
 }
diff --git a/src/indexer.rs b/src/indexer.rs
index 24723d5..6ba5ff5 100644
--- a/src/indexer.rs
+++ b/src/indexer.rs
@@ -2,6 +2,7 @@ use graphql_client::{reqwest::post_graphql, GraphQLQuery};
 use sp_core::Bytes;
 
 use crate::*;
+use identity_info::*;
 
 // type used in parameters query
 // #[allow(non_camel_case_types)]
@@ -13,15 +14,15 @@ use crate::*;
 	schema_path = "res/indexer-schema.json",
 	query_path = "res/indexer-queries.graphql"
 )]
-pub struct IdentityByIndex;
+pub struct IdentityNameByIndex;
 
-// // name → identity
-// #[derive(GraphQLQuery)]
-// #[graphql(
-// 	schema_path = "res/indexer-schema.json",
-// 	query_path = "res/indexer-queries.graphql"
-// )]
-// pub struct IdentityByName;
+// index → identity info
+#[derive(GraphQLQuery)]
+#[graphql(
+	schema_path = "res/indexer-schema.json",
+	query_path = "res/indexer-queries.graphql"
+)]
+pub struct IdentityInfo;
 
 // pubkey → identity
 #[derive(GraphQLQuery)]
@@ -29,7 +30,7 @@ pub struct IdentityByIndex;
 	schema_path = "res/indexer-schema.json",
 	query_path = "res/indexer-queries.graphql"
 )]
-pub struct IdentityByPubkey;
+pub struct IdentityNameByPubkey;
 
 #[derive(GraphQLQuery)]
 #[graphql(
@@ -52,45 +53,48 @@ pub struct Indexer {
 }
 
 impl Indexer {
+	/// index → name
 	pub async fn username_by_index(&self, index: u32) -> anyhow::Result<Option<String>> {
-		Ok(post_graphql::<IdentityByIndex, _>(
+		Ok(post_graphql::<IdentityNameByIndex, _>(
 			&self.gql_client,
 			&self.gql_url,
-			identity_by_index::Variables {
+			identity_name_by_index::Variables {
 				index: index.into(),
 			},
 		)
 		.await?
 		.data
-		.and_then(|data| data.identities.first().map(|idty| idty.name.clone())))
+		.and_then(move |mut data| data.identities.pop().map(|idty| idty.name)))
 	}
 
+	/// pubkey → name
 	pub async fn username_by_pubkey(&self, pubkey: &str) -> anyhow::Result<Option<String>> {
-		Ok(post_graphql::<IdentityByPubkey, _>(
+		Ok(post_graphql::<IdentityNameByPubkey, _>(
 			&self.gql_client,
 			&self.gql_url,
-			identity_by_pubkey::Variables {
+			identity_name_by_pubkey::Variables {
 				pubkey: pubkey.to_string(),
 			},
 		)
 		.await?
 		.data
-		.and_then(|data| data.identities.first().map(|idty| idty.name.clone())))
+		.and_then(move |mut data| data.identities.pop().map(|idty| idty.name)))
 	}
 
-	// // not used anymore because available with Duniter
-	// pub async fn pubkey_by_username(&self, username: &str) -> anyhow::Result<Option<String>> {
-	// 	Ok(post_graphql::<IdentityByName, _>(
-	// 		&self.gql_client,
-	// 		self.gql_url.clone(),
-	// 		identity_by_name::Variables {
-	// 			name: username.to_string(),
-	// 		},
-	// 	)
-	// 	.await?
-	// 	.data
-	// 	.and_then(|data| data.identity.into_iter().next().map(|idty| idty.pubkey)))
-	// }
+	/// index → info
+	pub async fn identity_info(&self, index: u32) -> Option<IdentityInfoIdentities> {
+		post_graphql::<IdentityInfo, _>(
+			&self.gql_client,
+			&self.gql_url,
+			identity_info::Variables {
+				index: index.into(),
+			},
+		)
+		.await
+		.expect("problem")
+		.data
+		.and_then(move |mut data| data.identities.pop())
+	}
 
 	/// fetch latest block number
 	pub async fn fetch_latest_block(&self) -> Result<u64, anyhow::Error> {
-- 
GitLab