1use 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#[derive(Serialize, Deserialize)]
29struct SendCryptoRequest {
30 address_s: String,
31 address_r: String,
32 amount: f64,
33 fee: f64,
34}
35
36struct AppState {
40 blockchain: Mutex<Blockchain>,
41 blocks_collection: Collection<Document>,
42 wallets_collection: Collection<Document>,
43 blockchain_collection: Collection<Document>,
44}
45
46async fn list_blockchain(data: web::Data<AppState>) -> impl Responder {
50 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") .body(output)
62 }
65
66async fn get_wallet_addresses(data: web::Data<AppState>) -> Json<Vec<String>> {
70 Json(utils::get_wallet_addresses(&data.wallets_collection).await)
71}
72
73async fn get_pending_transactions(data: web::Data<AppState>) -> Json<Vec<String>> {
77 Json(utils::get_pending_transactions(&data.wallets_collection).await)
78}
79
80async fn get_previous_hash(data: web::Data<AppState>) -> String {
84 utils::get_previous_hash(&data.blocks_collection, &data.blockchain_collection).await
85}
86
87async 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") .body(output)
111}
112
113async fn send_crypto(
117 data: web::Data<AppState>,
118 req: web::Json<SendCryptoRequest>,
119 ) -> 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 = 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 let response = json!({
160 "message": "Send Crypto...",
161 "response": txn_result.unwrap()
162 });
163 HttpResponse::Ok().json(response)
164 }
166
167async fn mine_trxn(
171 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 HttpResponse::Ok().body(response)
200}
201
202async 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()
235 .content_type("application/json") .body(response)
237}
238
239async 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 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
265async 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
273static STATIC_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/static");
277
278async fn index() -> impl Responder {
282 HttpResponse::Ok()
283 .content_type("text/html")
284 .body(include_str!("../static/pages/index.html"))
285}
286
287async 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#[actix_web::main]
306async fn main() -> std::io::Result<()> {
307 env_logger::init();
309 println!("Starting server...");
310
311 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 let blockchain =
323 mongo_utils::load_blockchain(blocks_collection.clone(), blockchain_collection.clone())
324 .await
325 .unwrap();
326
327 let app_data = web::Data::new(AppState {
329 blockchain: Mutex::new(blockchain),
330 blocks_collection,
331 wallets_collection,
332 blockchain_collection,
333 });
334
335 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}