From 548e93a323a5769bf3ea2794512433e68f9175bc Mon Sep 17 00:00:00 2001
From: librelois <elois@ifee.fr>
Date: Sat, 16 Nov 2019 23:32:50 +0100
Subject: [PATCH] [feat] gva: add queries node summary and block by number

---
 lib/modules-lib/bc-db-reader/src/blocks.rs |   6 +-
 lib/modules-lib/bc-db-reader/src/lib.rs    |   2 +-
 lib/modules/gva/Cargo.toml                 |   4 +-
 lib/modules/gva/resources/schema.gql       |  24 +++++
 lib/modules/gva/src/context.rs             |  22 +++-
 lib/modules/gva/src/schema.rs              | 118 +++++++++++++--------
 lib/modules/gva/src/schema/block.rs        |  83 +++++++++++++++
 lib/modules/gva/src/webserver.rs           |   2 +-
 8 files changed, 204 insertions(+), 57 deletions(-)
 create mode 100644 lib/modules/gva/src/schema/block.rs

diff --git a/lib/modules-lib/bc-db-reader/src/blocks.rs b/lib/modules-lib/bc-db-reader/src/blocks.rs
index b280dee7..d23a149f 100644
--- a/lib/modules-lib/bc-db-reader/src/blocks.rs
+++ b/lib/modules-lib/bc-db-reader/src/blocks.rs
@@ -100,7 +100,7 @@ pub fn get_block<DB: DbReadable>(
     blockstamp: Blockstamp,
 ) -> Result<Option<DbBlock>, DbError> {
     db.read(|r| {
-        let opt_dal_block = get_dal_block_in_local_blockchain(db, r, blockstamp.id)?;
+        let opt_dal_block = get_db_block_in_local_blockchain(db, r, blockstamp.id)?;
         if opt_dal_block.is_none() {
             get_fork_block(db, r, blockstamp)
         } else {
@@ -145,11 +145,11 @@ pub fn get_block_in_local_blockchain<DB: DbReadable, R: DbReader>(
     r: &R,
     block_number: BlockNumber,
 ) -> Result<Option<BlockDocument>, DbError> {
-    Ok(get_dal_block_in_local_blockchain(db, r, block_number)?.map(|dal_block| dal_block.block))
+    Ok(get_db_block_in_local_blockchain(db, r, block_number)?.map(|dal_block| dal_block.block))
 }
 
 /// Get block in local blockchain
-pub fn get_dal_block_in_local_blockchain<DB: DbReadable, R: DbReader>(
+pub fn get_db_block_in_local_blockchain<DB: DbReadable, R: DbReader>(
     db: &DB,
     r: &R,
     block_number: BlockNumber,
diff --git a/lib/modules-lib/bc-db-reader/src/lib.rs b/lib/modules-lib/bc-db-reader/src/lib.rs
index 9364d67a..00297e27 100644
--- a/lib/modules-lib/bc-db-reader/src/lib.rs
+++ b/lib/modules-lib/bc-db-reader/src/lib.rs
@@ -39,9 +39,9 @@ pub use durs_dbs_tools::kv_db::{
     KvFileDbRead as DbReadable, KvFileDbRoHandler as BcDbRo, KvFileDbSchema, KvFileDbStoreType,
     KvFileDbValue as DbValue, Readable as DbReader,
 };
+pub use durs_dbs_tools::DbError;
 
 use constants::*;
-use durs_dbs_tools::DbError;
 use maplit::hashmap;
 use std::path::Path;
 
diff --git a/lib/modules/gva/Cargo.toml b/lib/modules/gva/Cargo.toml
index ea24ecd6..894509e3 100644
--- a/lib/modules/gva/Cargo.toml
+++ b/lib/modules/gva/Cargo.toml
@@ -10,7 +10,6 @@ edition = "2018"
 path = "src/lib.rs"
 
 [dependencies]
-proc-macro2 = "1.0.6"
 actix-web = "1.0.8"
 dubp-block-doc = { path = "../../dubp/block-doc"} #, version = "0.1.0" }
 durs-bc-db-reader = { path = "../../modules-lib/bc-db-reader" }
@@ -22,13 +21,14 @@ durs-network-documents = { path = "../../dunp/network-documents" }
 dubp-common-doc = { path = "../../dubp/common-doc"} #, version = "0.1.0" }
 durs-common-tools = { path = "../../tools/common-tools" }
 dubp-currency-params = { path = "../../dubp/currency-params" }
+chrono = "0.4.9"
 failure = "0.1.5"
 futures = "0.1"
 futures-cpupool = "0.1"
 juniper = "0.14.0"
-
 juniper-from-schema =  "0.5.0"
 log = "0.4.8"
+proc-macro2 = "1.0.6"
 serde = "1.0.102"
 serde_derive = "1.0.102"
 serde_json = "1.0.41"
diff --git a/lib/modules/gva/resources/schema.gql b/lib/modules/gva/resources/schema.gql
index 8115701e..d9147a4f 100644
--- a/lib/modules/gva/resources/schema.gql
+++ b/lib/modules/gva/resources/schema.gql
@@ -5,16 +5,40 @@ schema {
 }
 
 type Query {
+  node: Node! @juniper(ownership: "owned")
   current: Block @juniper(ownership: "owned")
+  block(number: Int!): Block @juniper(ownership: "owned")
 }
 
 type Mutation {
   noop: Boolean!
 }
 
+#################################
+# NODE types
+#################################
+
+type Summary {
+  software: String! @juniper(infallible: true, ownership: "owned")
+  version: String! @juniper(infallible: true, ownership: "owned")
+}
+
+
+type Node {
+  summary: Summary! @juniper(infallible: true)
+}
+
+#################################
+# Block type
+#################################
+
+scalar DateTimeUtc @juniper(with_time_zone: false)
+
 type Block {
   version: Int!
   currency: String!
   issuer: String!
   number: Int!
+  hash: String!,
+  commonTime: DateTimeUtc!
 }
diff --git a/lib/modules/gva/src/context.rs b/lib/modules/gva/src/context.rs
index 29955f75..6e27d678 100644
--- a/lib/modules/gva/src/context.rs
+++ b/lib/modules/gva/src/context.rs
@@ -22,23 +22,37 @@ static mut CONTEXT: Option<Context> = None;
 #[derive(Debug)]
 pub struct Context {
     db: BcDbRo,
+    software_name: &'static str,
+    software_version: &'static str,
 }
 
 impl juniper::Context for Context {}
 
 impl Context {
-    pub fn new(db: BcDbRo) -> Self {
-        Context { db }
+    pub fn new(db: BcDbRo, software_name: &'static str, software_version: &'static str) -> Self {
+        Context {
+            db,
+            software_name,
+            software_version,
+        }
     }
 
     pub fn get_db(&self) -> &BcDbRo {
         &self.db
     }
+
+    pub fn get_software_name(&self) -> &'static str {
+        &self.software_name
+    }
+
+    pub fn get_software_version(&self) -> &'static str {
+        &self.software_version
+    }
 }
 
-pub fn init(db: BcDbRo) {
+pub fn init(db: BcDbRo, soft_name: &'static str, soft_version: &'static str) {
     unsafe {
-        CONTEXT.replace(Context::new(db));
+        CONTEXT.replace(Context::new(db, soft_name, soft_version));
     }
 }
 
diff --git a/lib/modules/gva/src/schema.rs b/lib/modules/gva/src/schema.rs
index 8f2ee9f1..842a3873 100644
--- a/lib/modules/gva/src/schema.rs
+++ b/lib/modules/gva/src/schema.rs
@@ -12,13 +12,15 @@
 //
 // You should have received a copy of the GNU Affero General Public License
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
-//
-// model and resolvers implementation
 
+// ! model and resolvers implementation
+
+mod block;
+
+use self::block::Block;
 use crate::context::Context;
-use dubp_block_doc::block::BlockDocumentTrait;
-use dubp_common_doc::traits::Document;
-use durs_bc_db_reader::BcDbRo;
+use dubp_common_doc::BlockNumber;
+use durs_bc_db_reader::{BcDbRo, DbError, DbReadable};
 use juniper::Executor;
 use juniper::FieldResult;
 use juniper_from_schema::graphql_schema_from_file;
@@ -28,61 +30,85 @@ graphql_schema_from_file!("resources/schema.gql");
 
 pub struct Query;
 
-pub struct Block {
-    version: i32,
-    currency: String,
-    issuer: String,
-    number: i32,
+pub struct Summary {
+    software: &'static str,
+    version: &'static str,
+}
+
+pub struct Node {
+    summary: Summary,
+}
+
+fn db_err_to_juniper_err(e: DbError) -> juniper::FieldError {
+    juniper::FieldError::from(format!("Db error: {:?}", e))
 }
 
 impl QueryFields for Query {
+    fn field_node(
+        &self,
+        executor: &Executor<'_, Context>,
+        _trail: &QueryTrail<'_, Node, Walked>,
+    ) -> FieldResult<Node> {
+        Ok(Node {
+            summary: Summary {
+                software: executor.context().get_software_name(),
+                version: executor.context().get_software_version(),
+            },
+        })
+    }
     fn field_current(
         &self,
-        _executor: &Executor<'_, Context>,
+        executor: &Executor<'_, Context>,
         _trail: &QueryTrail<'_, Block, Walked>,
     ) -> FieldResult<Option<Block>> {
-        let db: &BcDbRo = &_executor.context().get_db();
-        let current_blockstamp = durs_bc_db_reader::current_meta_datas::get_current_blockstamp(db);
-
-        match current_blockstamp {
-            Ok(option) => match option {
-                Some(v) => {
-                    let current_block = durs_bc_db_reader::blocks::get_block(db, v);
-                    match current_block {
-                        Ok(current_block_option) => match current_block_option {
-                            Some(block) => Ok(Some(Block {
-                                version: block.block.version() as i32,
-                                currency: block.block.currency().to_string(),
-                                issuer: block.block.issuers()[0].to_string(),
-                                number: block.block.number().0 as i32,
-                            })),
-                            None => Ok(None),
-                        },
-                        Err(_e) => Err(juniper::FieldError::from("No current block available")),
-                    }
-                }
-                None => Ok(None),
-            },
-            Err(_e) => Err(juniper::FieldError::from("No current block available")),
-        }
-    }
-}
+        let db: &BcDbRo = &executor.context().get_db();
 
-impl BlockFields for Block {
-    fn field_version(&self, _executor: &Executor<'_, Context>) -> FieldResult<&i32> {
-        Ok(&self.version)
+        db.read(|r| {
+            if let Some(current_blockstamp) =
+                durs_bc_db_reader::current_meta_datas::get_current_blockstamp_(db, r)?
+            {
+                block::get_block(db, r, current_blockstamp.id)
+            } else {
+                Ok(None)
+            }
+        })
+        .map_err(db_err_to_juniper_err)
     }
+    fn field_block(
+        &self,
+        executor: &Executor<'_, Context>,
+        _trail: &QueryTrail<'_, Block, Walked>,
+        number: i32,
+    ) -> FieldResult<Option<Block>> {
+        let db: &BcDbRo = &executor.context().get_db();
+
+        let block_number = if number >= 0 {
+            BlockNumber(number as u32)
+        } else {
+            return Err(juniper::FieldError::from("Block number must be positive."));
+        };
 
-    fn field_currency(&self, _executor: &Executor<'_, Context>) -> FieldResult<&String> {
-        Ok(&self.currency)
+        db.read(|r| block::get_block(db, r, block_number))
+            .map_err(|e| juniper::FieldError::from(format!("Db error: {:?}", e)))
     }
+}
 
-    fn field_issuer(&self, _executor: &Executor<'_, Context>) -> FieldResult<&String> {
-        Ok(&self.issuer)
+impl NodeFields for Node {
+    fn field_summary(
+        &self,
+        _executor: &Executor<'_, Context>,
+        _trail: &QueryTrail<'_, Summary, Walked>,
+    ) -> &Summary {
+        &self.summary
     }
+}
 
-    fn field_number(&self, _executor: &Executor<'_, Context>) -> FieldResult<&i32> {
-        Ok(&self.number)
+impl SummaryFields for Summary {
+    fn field_software(&self, _executor: &Executor<'_, Context>) -> String {
+        self.software.to_owned()
+    }
+    fn field_version(&self, _executor: &Executor<'_, Context>) -> String {
+        self.version.to_owned()
     }
 }
 
diff --git a/lib/modules/gva/src/schema/block.rs b/lib/modules/gva/src/schema/block.rs
new file mode 100644
index 00000000..b155e8d5
--- /dev/null
+++ b/lib/modules/gva/src/schema/block.rs
@@ -0,0 +1,83 @@
+//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+// ! Block model and resolvers
+
+use crate::context::Context;
+use chrono::NaiveDateTime;
+use dubp_block_doc::block::BlockDocumentTrait;
+use dubp_common_doc::traits::Document;
+use dubp_common_doc::BlockNumber;
+use durs_bc_db_reader::{BcDbRo, DbError, DbReader};
+use durs_common_tools::fatal_error;
+use juniper::{Executor, FieldResult};
+
+pub struct Block {
+    version: i32,
+    currency: String,
+    issuer: String,
+    number: i32,
+    hash: String,
+    common_time: NaiveDateTime,
+}
+
+impl super::BlockFields for Block {
+    fn field_version(&self, _executor: &Executor<'_, Context>) -> FieldResult<&i32> {
+        Ok(&self.version)
+    }
+
+    fn field_currency(&self, _executor: &Executor<'_, Context>) -> FieldResult<&String> {
+        Ok(&self.currency)
+    }
+
+    fn field_issuer(&self, _executor: &Executor<'_, Context>) -> FieldResult<&String> {
+        Ok(&self.issuer)
+    }
+
+    fn field_number(&self, _executor: &Executor<'_, Context>) -> FieldResult<&i32> {
+        Ok(&self.number)
+    }
+
+    fn field_hash(&self, _executor: &Executor<'_, Context>) -> FieldResult<&String> {
+        Ok(&self.hash)
+    }
+
+    fn field_common_time(&self, _executor: &Executor<'_, Context>) -> FieldResult<&NaiveDateTime> {
+        Ok(&self.common_time)
+    }
+}
+
+pub fn get_block<R: DbReader>(
+    db: &BcDbRo,
+    r: &R,
+    block_number: BlockNumber,
+) -> Result<Option<Block>, DbError> {
+    durs_bc_db_reader::blocks::get_db_block_in_local_blockchain(db, r, block_number).map(
+        |block_opt| {
+            block_opt.map(|db_block| Block {
+                version: db_block.block.version() as i32,
+                currency: db_block.block.currency().to_string(),
+                issuer: db_block.block.issuers()[0].to_string(),
+                number: db_block.block.number().0 as i32,
+                hash: db_block
+                    .block
+                    .hash()
+                    .unwrap_or_else(|| fatal_error!("DbBlock without hash."))
+                    .to_string(),
+                common_time: NaiveDateTime::from_timestamp(db_block.block.common_time() as i64, 0),
+            })
+        },
+    )
+}
diff --git a/lib/modules/gva/src/webserver.rs b/lib/modules/gva/src/webserver.rs
index 89606c6b..fe065674 100644
--- a/lib/modules/gva/src/webserver.rs
+++ b/lib/modules/gva/src/webserver.rs
@@ -68,7 +68,7 @@ pub fn start_web_server(
     // Instanciate the context
     let db_path = durs_conf::get_blockchain_db_path(soft_meta_datas.profile_path.clone());
     if let Ok(db) = durs_bc_db_reader::open_db_ro(&std::path::Path::new(&db_path)) {
-        context::init(db);
+        context::init(db, soft_meta_datas.soft_name, soft_meta_datas.soft_version);
     } else {
         fatal_error!("GVA: fail to open DB.");
     };
-- 
GitLab