bc_web/
main.rs

1//!
2//! Main Web runner for the Blockchain Application.<br>
3//! Function main.<br>
4//! List, transact and reset the blockchain sample application.
5//!
6
7use actix_web::web::Json;
8use actix_web::{middleware::Logger, web, App, HttpResponse, HttpServer, Responder};
9use actix_web::{Error, HttpRequest};
10use actix_web_actors::ws;
11use bc_db::mongo_utils;
12use bc_utils::{utils, wsactor};
13use block_chain::blockchain::Blockchain;
14use block_chain::wallet::Wallet;
15use bson::Document;
16use env_logger;
17use include_dir::{include_dir, Dir};
18use mime_guess;
19use mongodb::{Client, Collection};
20use serde::{Deserialize, Serialize};
21use serde_json::json;
22use std::collections::HashMap;
23use std::sync::Mutex;
24
25///
26/// Struct SendCryptoRequest.
27///
28#[derive(Serialize, Deserialize)]
29struct SendCryptoRequest {
30    address_s: String,
31    address_r: String,
32    amount: f64,
33    fee: f64,
34}
35
36///
37/// Struct AppState.
38///
39struct AppState {
40    blockchain: Mutex<Blockchain>,
41    blocks_collection: Collection<Document>,
42    wallets_collection: Collection<Document>,
43    blockchain_collection: Collection<Document>,
44}
45
46///
47/// List the blockchain.
48///
49async fn list_blockchain(data: web::Data<AppState>) -> impl Responder {
50    // Load blockchain from database
51    let blockchain = mongo_utils::load_blockchain(
52        data.blocks_collection.clone(),
53        data.blockchain_collection.clone(),
54    )
55    .await
56    .unwrap();
57
58    let output = utils::list_blockchain(&blockchain);
59    HttpResponse::Ok()
60        .content_type("application/json") // Ensure the content type is JSON
61        .body(output)
62    // HttpResponse::Ok().body(output)
63    // HttpResponse::Ok().json(&*blockchain)
64}
65
66///
67/// Get the wallet address.
68///
69async fn get_wallet_addresses(data: web::Data<AppState>) -> Json<Vec<String>> {
70    Json(utils::get_wallet_addresses(&data.wallets_collection).await)
71}
72
73///
74/// Get pending transactions.
75///
76async fn get_pending_transactions(data: web::Data<AppState>) -> Json<Vec<String>> {
77    Json(utils::get_pending_transactions(&data.wallets_collection).await)
78}
79
80///
81/// Get previous hash.
82///
83async fn get_previous_hash(data: web::Data<AppState>) -> String {
84    utils::get_previous_hash(&data.blocks_collection, &data.blockchain_collection).await
85}
86
87///
88/// Display wallet details.
89///
90async fn view_wallet(
91    data: web::Data<AppState>,
92    query: web::Query<std::collections::HashMap<String, String>>,
93) -> impl Responder {
94    let wallets_collection = data.wallets_collection.clone();
95    let mut wallet_map: HashMap<String, Wallet> = Result::expect(
96        mongo_utils::load_wallets(wallets_collection.clone()).await,
97        "Could not get wallets data",
98    );
99
100    let address = query.get("address").unwrap_or(&String::new()).to_string();
101
102    let wallet = wallet_map
103        .remove(&address)
104        .expect("Sender wallet not found");
105
106    let output = utils::view_wallet(&wallet, &wallet.address);
107
108    HttpResponse::Ok()
109        .content_type("application/json") // Ensure the content type is JSON
110        .body(output)
111}
112
113///
114/// Send crypto one wallet to another.
115///
116async fn send_crypto(
117    data: web::Data<AppState>,
118    req: web::Json<SendCryptoRequest>,
119    // wallet_id: web::Path<u8>,
120) -> impl Responder {
121    let blocks_collection = data.blocks_collection.clone();
122    let wallets_collection = data.wallets_collection.clone();
123    let blockchain_collection = data.blockchain_collection.clone();
124    //let mut blockchain = data.blockchain.lock().unwrap();
125
126    // Load blockchain from database
127    let mut blockchain = mongo_utils::load_blockchain(
128        data.blocks_collection.clone(),
129        data.blockchain_collection.clone(),
130    )
131    .await
132    .unwrap();
133
134    let mut wallet_map: HashMap<String, Wallet> = Result::expect(
135        mongo_utils::load_wallets(wallets_collection.clone()).await,
136        "Could not get wallets data",
137    );
138
139    let mut wallet1 = wallet_map
140        .remove(&req.address_s)
141        .expect("Sender wallet not found");
142    let mut wallet2 = wallet_map
143        .remove(&req.address_r)
144        .expect("Receiver wallet not found");
145
146    let txn_result = utils::send_crypto(
147        req.amount,
148        req.fee,
149        &mut blockchain,
150        &mut wallet1,
151        &mut wallet2,
152        blocks_collection,
153        wallets_collection,
154        blockchain_collection,
155    )
156    .await;
157
158    // HttpResponse::Ok().json(&*blockchain)
159    let response = json!({
160        "message": "Send Crypto...",
161        "response": txn_result.unwrap()
162    });
163    HttpResponse::Ok().json(response)
164    // HttpResponse::Ok().body(txn_result.unwrap())
165}
166
167///
168/// Mine a pending transaction.
169///
170async fn mine_trxn(
171    // data: web::Data<AppState>,
172    query: web::Query<std::collections::HashMap<String, String>>,
173) -> impl Responder {
174    let transaction_str = query.get("tx_id").unwrap_or(&String::new()).to_string();
175    println!("string is {transaction_str}");
176
177    let tx_id = transaction_str
178        .split(',')
179        .nth(0)
180        .and_then(|s| s.split(':').nth(1))
181        .unwrap_or("tx_id not found")
182        .replace('"', "")
183        .trim()
184        .to_string();
185
186    println!("tx_id = {}", tx_id);
187
188    let mut response = String::from("");
189    if tx_id.clone() == String::from("tx_id not found") {
190        response = String::from("No transaction to process!");
191    }
192    // } else {
193    //     let blocks_collection = data.blocks_collection.clone();
194    //     let blockchain_collection = data.blockchain_collection.clone();
195
196    //     utils::mine_crypto(blocks_collection, blockchain_collection).await;
197    // }
198
199    HttpResponse::Ok().body(response)
200}
201
202///
203/// Save mined transaction to database.
204///
205async fn commit_mine_trxn(
206    data: web::Data<AppState>,
207    query: web::Query<std::collections::HashMap<String, String>>,
208) -> impl Responder {
209    let transaction_str = query.get("tx_id").unwrap_or(&String::new()).to_string();
210    println!("string is {transaction_str}");
211
212    let tx_id = transaction_str
213        .split(',')
214        .nth(0)
215        .and_then(|s| s.split(':').nth(1))
216        .unwrap_or("tx_id not found")
217        .replace('"', "")
218        .trim()
219        .to_string();
220
221    let blocks_collection = data.blocks_collection.clone();
222    let blockchain_collection = data.blockchain_collection.clone();
223    let wallets_collection = data.wallets_collection.clone();
224
225    let response = utils::commit_mine_crypto(
226        tx_id.clone(),
227        blocks_collection,
228        blockchain_collection,
229        wallets_collection,
230    )
231    .await;
232
233    // HttpResponse::Ok().json(response)
234    HttpResponse::Ok()
235        .content_type("application/json") // Ensure the content type is JSON
236        .body(response)
237}
238
239///
240/// Reset sample data to genesis block.
241///
242async fn reset_sample_data(data: web::Data<AppState>) -> impl Responder {
243    let result = utils::reset_sample_data(
244        &data.blocks_collection,
245        &data.wallets_collection,
246        &data.blockchain_collection,
247    )
248    .await;
249
250    let new_blockchain = mongo_utils::load_blockchain(
251        data.blocks_collection.clone(),
252        data.blockchain_collection.clone(),
253    )
254    .await
255    .unwrap();
256
257    // Update the in-memory state
258    let mut blockchain = data.blockchain.lock().unwrap();
259    *blockchain = new_blockchain;
260
261    println!("{:?}", result);
262    HttpResponse::Ok().body("Sample data has been reset and loaded")
263}
264
265///
266/// Start web socket for mining transaction.
267///
268async fn websocket_handler(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
269    let mining_actor = wsactor::MiningProgressWebSocket::new("".to_string(), "".to_string(), 0);
270    ws::start(mining_actor, &req, stream)
271}
272
273///
274/// Path definition to static file location.
275///
276static STATIC_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/static");
277
278///
279/// Index page for the server.
280///
281async fn index() -> impl Responder {
282    HttpResponse::Ok()
283        .content_type("text/html")
284        .body(include_str!("../static/pages/index.html"))
285}
286
287///
288/// Function to server static files.
289///
290async fn serve_static_file(path: web::Path<(String,)>) -> impl Responder {
291    let path = &path.into_inner().0;
292    if let Some(file) = STATIC_DIR.get_file(path) {
293        let mime_type = mime_guess::from_path(path).first_or_octet_stream();
294        HttpResponse::Ok()
295            .content_type(mime_type)
296            .body(file.contents())
297    } else {
298        HttpResponse::NotFound().body("File not found")
299    }
300}
301
302///
303/// Function main for the Web blockchain application.
304///
305#[actix_web::main]
306async fn main() -> std::io::Result<()> {
307    // Initialize the logger
308    env_logger::init();
309    println!("Starting server...");
310
311    // Initialize MongoDB client
312    let client = Client::with_uri_str("mongodb://localhost:27017")
313        .await
314        .unwrap();
315    let db = client.database("blockchain_db");
316
317    let blocks_collection = db.collection("blocks");
318    let wallets_collection = db.collection("wallets");
319    let blockchain_collection = db.collection("blockchain");
320
321    // Load blockchain from database
322    let blockchain =
323        mongo_utils::load_blockchain(blocks_collection.clone(), blockchain_collection.clone())
324            .await
325            .unwrap();
326
327    // Initialize web app data
328    let app_data = web::Data::new(AppState {
329        blockchain: Mutex::new(blockchain),
330        blocks_collection,
331        wallets_collection,
332        blockchain_collection,
333    });
334
335    // Setup, start and bind Rust server to 8088
336    HttpServer::new(move || {
337        App::new()
338            .wrap(Logger::default())
339            .app_data(app_data.clone())
340            .route("/blockchain", web::get().to(list_blockchain))
341            .route("/wallets", web::get().to(get_wallet_addresses))
342            .route("/phash", web::get().to(get_previous_hash))
343            .route("/wallet", web::get().to(view_wallet))
344            .route("/send_crypto/{id}", web::post().to(send_crypto))
345            .route("pendingTxnxs", web::get().to(get_pending_transactions))
346            .route("/mining", web::get().to(mine_trxn))
347            .route("/commit_mining", web::get().to(commit_mine_trxn))
348            .route("/ws", web::get().to(websocket_handler))
349            .route("/reset_sample_data", web::post().to(reset_sample_data))
350            .route("/", web::get().to(index))
351            .route("/static/{filename:.*}", web::get().to(serve_static_file))
352    })
353    .bind("0.0.0.0:8088")?
354    .run()
355    .await
356}