use axum::{ extract::{Path, State}, http::StatusCode, response::IntoResponse, Json, }; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::binance::client::BinanceClient; use crate::storage::config::BotConfig; use crate::AppState; #[derive(Serialize)] pub struct SymbolInfo { pub symbol: String, pub min_notional: f64, } pub async fn list_symbols(State(state): State) -> impl IntoResponse { let client = BinanceClient::new(state.api_key, state.api_secret, state.testnet); match client.get_usdt_symbols_with_min().await { Ok(mut symbols) => { symbols.sort_by(|a, b| a.0.cmp(&b.0)); let result: Vec = symbols .into_iter() .map(|(symbol, min_notional)| SymbolInfo { symbol, min_notional }) .collect(); Json(result).into_response() } Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), } } #[derive(Deserialize)] pub struct CreateBotRequest { pub name: String, pub symbol: String, pub timeframe: crate::binance::models::Timeframe, pub usdt_amount: f64, pub profit_percent: f64, pub testnet: Option, } #[derive(Serialize)] pub struct BotResponse { #[serde(flatten)] pub config: BotConfig, pub running: bool, } pub async fn list_bots(State(state): State) -> impl IntoResponse { let db = state.db.lock().await; let bots = match db.get_bots() { Ok(b) => b, Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), }; drop(db); let manager = state.manager.lock().await; let response: Vec = bots .into_iter() .map(|b| { let running = manager.is_running(&b.id); BotResponse { config: b, running } }) .collect(); Json(response).into_response() } pub async fn create_bot( State(state): State, Json(req): Json, ) -> impl IntoResponse { let config = BotConfig { id: Uuid::new_v4().to_string(), name: req.name, symbol: req.symbol.to_uppercase(), timeframe: req.timeframe, usdt_amount: req.usdt_amount, profit_percent: req.profit_percent, testnet: req.testnet.unwrap_or(false), active: false, }; let db = state.db.lock().await; if let Err(e) = db.insert_bot(&config) { return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(); } drop(db); Json(BotResponse { config, running: false }).into_response() } pub async fn delete_bot( State(state): State, Path(id): Path, ) -> impl IntoResponse { let mut manager = state.manager.lock().await; manager.stop(&id).await; drop(manager); let db = state.db.lock().await; if let Err(e) = db.delete_bot(&id) { return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(); } StatusCode::NO_CONTENT.into_response() } pub async fn start_bot( State(state): State, Path(id): Path, ) -> impl IntoResponse { let db = state.db.lock().await; let bots = match db.get_bots() { Ok(b) => b, Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), }; let config = match bots.into_iter().find(|b| b.id == id) { Some(c) => c, None => return StatusCode::NOT_FOUND.into_response(), }; if let Err(e) = db.set_bot_active(&id, true) { return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(); } drop(db); let mut manager = state.manager.lock().await; manager.start(config); StatusCode::OK.into_response() } pub async fn stop_bot( State(state): State, Path(id): Path, ) -> impl IntoResponse { let db = state.db.lock().await; if let Err(e) = db.set_bot_active(&id, false) { return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(); } drop(db); let mut manager = state.manager.lock().await; manager.stop(&id).await; StatusCode::OK.into_response() }