using Managing.Api.Models.Requests; using Managing.Api.Models.Responses; using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services; using Managing.Application.Hubs; using Managing.Application.ManageBot.Commands; using Managing.Common; using Managing.Domain.Bots; using Managing.Domain.Users; using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Managing.Domain.Trades; using Microsoft.Extensions.DependencyInjection; using static Managing.Common.Enums; using ApplicationTradingBot = Managing.Application.Bots.TradingBot; using ApiTradingBot = Managing.Api.Models.Responses.TradingBot; namespace Managing.Api.Controllers; /// /// Controller for bot operations such as starting, stopping, deleting, and managing bots. /// Requires authorization for access and produces JSON responses. /// [ApiController] [Authorize] [Route("[controller]")] [Produces("application/json")] public class BotController : BaseController { private readonly IMediator _mediator; private readonly ILogger _logger; private readonly IHubContext _hubContext; private readonly IBacktester _backtester; private readonly IBotService _botService; private readonly IAccountService _accountService; private readonly IMoneyManagementService _moneyManagementService; /// /// Initializes a new instance of the class. /// /// Logger for logging information. /// Mediator for handling commands and requests. /// SignalR hub context for real-time communication. /// Backtester for running backtests on bots. public BotController(ILogger logger, IMediator mediator, IHubContext hubContext, IBacktester backtester, IBotService botService, IUserService userService, IAccountService accountService, IMoneyManagementService moneyManagementService) : base(userService) { _logger = logger; _mediator = mediator; _hubContext = hubContext; _backtester = backtester; _botService = botService; _accountService = accountService; _moneyManagementService = moneyManagementService; } /// /// Checks if the current authenticated user owns the account associated with the specified bot or account name /// /// The name of the bot to check /// Optional account name to check when creating a new bot /// True if the user owns the account, False otherwise private async Task UserOwnsBotAccount(string botName, string accountName = null) { try { var user = await GetUser(); if (user == null) return false; // For new bot creation, check if the user owns the account provided in the request if (!string.IsNullOrEmpty(accountName)) { var accountService = HttpContext.RequestServices.GetRequiredService(); var account = await accountService.GetAccount(accountName, true, false); // Compare the user names return account != null && account.User != null && account.User.Name == user.Name; } // For existing bots, check if the user owns the bot's account var activeBots = _botService.GetActiveBots(); var bot = activeBots.FirstOrDefault(b => b.Name == botName); if (bot == null) return true; // Bot doesn't exist yet, so no ownership conflict var botAccountService = HttpContext.RequestServices.GetRequiredService(); var botAccount = await botAccountService.GetAccount(bot.AccountName, true, false); // Compare the user names return botAccount != null && botAccount.User != null && botAccount.User.Name == user.Name; } catch (Exception ex) { _logger.LogError(ex, "Error checking if user owns bot account"); return false; } } /// /// Starts a bot with the specified parameters. /// /// The request containing bot start parameters. /// A string indicating the result of the start operation. [HttpPost] [Route("Start")] public async Task> Start(StartBotRequest request) { try { // Check if user owns the account if (!await UserOwnsBotAccount(request.BotName, request.AccountName)) { return Forbid("You don't have permission to start this bot"); } // Trigger error if money management is not provided if (string.IsNullOrEmpty(request.MoneyManagementName)) { return BadRequest("Money management name is required"); } var user = await GetUser(); var moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName); if (moneyManagement == null) { return BadRequest("Money management not found"); } var result = await _mediator.Send(new StartBotCommand(request.BotType, request.BotName, request.Ticker, request.Scenario, request.Timeframe, request.AccountName, request.MoneyManagementName, user, request.IsForWatchOnly)); await NotifyBotSubscriberAsync(); return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Error starting bot"); return StatusCode(500, $"Error starting bot: {ex.Message}"); } } /// /// Stops a bot specified by type and name. /// /// The type of the bot to stop. /// The name of the bot to stop. /// A string indicating the result of the stop operation. [HttpGet] [Route("Stop")] public async Task> Stop(BotType botType, string botName) { try { // Check if user owns the account if (!await UserOwnsBotAccount(botName)) { return Forbid("You don't have permission to stop this bot"); } var result = await _mediator.Send(new StopBotCommand(botType, botName)); _logger.LogInformation($"{botType} type called {botName} is now {result}"); await NotifyBotSubscriberAsync(); return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Error stopping bot"); return StatusCode(500, $"Error stopping bot: {ex.Message}"); } } /// /// Deletes a bot specified by name. /// /// The name of the bot to delete. /// A boolean indicating the result of the delete operation. [HttpDelete] [Route("Delete")] public async Task> Delete(string botName) { try { // Check if user owns the account if (!await UserOwnsBotAccount(botName)) { return Forbid("You don't have permission to delete this bot"); } var result = await _botService.DeleteBot(botName); await NotifyBotSubscriberAsync(); return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Error deleting bot"); return StatusCode(500, $"Error deleting bot: {ex.Message}"); } } /// /// Stops all active bots. /// /// A string summarizing the results of the stop operations for all bots. [HttpPost("stop-all")] public async Task StopAll() { // This method should be restricted to only stop bots owned by the current user var user = await GetUser(); if (user == null) return "No authenticated user found"; try { var bots = await GetBotList(); // Filter to only include bots owned by the current user var userBots = new List(); foreach (var bot in bots) { var account = await _accountService.GetAccount(bot.AccountName, true, false); // Compare the user names if (account != null && account.User != null && account.User.Name == user.Name) { userBots.Add(bot); } } foreach (var bot in userBots) { await _mediator.Send(new StopBotCommand(bot.BotType, bot.Name)); await _hubContext.Clients.All.SendAsync("SendNotification", $"Bot {bot.Name} paused by {user.Name}.", "Info"); } await NotifyBotSubscriberAsync(); return "All your bots have been stopped successfully!"; } catch (Exception ex) { _logger.LogError(ex, "Error stopping all bots"); return $"Error stopping bots: {ex.Message}"; } } /// /// Restarts a bot specified by type and name. /// /// The type of the bot to restart. /// The name of the bot to restart. /// A string indicating the result of the restart operation. [HttpGet] [Route("Restart")] public async Task> Restart(BotType botType, string botName) { try { // Check if user owns the account if (!await UserOwnsBotAccount(botName)) { return Forbid("You don't have permission to restart this bot"); } var result = await _mediator.Send(new RestartBotCommand(botType, botName)); _logger.LogInformation($"{botType} type called {botName} is now {result}"); await NotifyBotSubscriberAsync(); return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Error restarting bot"); return StatusCode(500, $"Error restarting bot: {ex.Message}"); } } /// /// Restarts all active bots. /// /// A string summarizing the results of the restart operations for all bots. [HttpPost("restart-all")] public async Task RestartAll() { var user = await GetUser(); if (user == null) return "No authenticated user found"; try { var bots = await GetBotList(); // Filter to only include bots owned by the current user var userBots = new List(); var accountService = HttpContext.RequestServices.GetRequiredService(); foreach (var bot in bots) { var account = await accountService.GetAccount(bot.AccountName, true, false); // Compare the user names if (account != null && account.User != null && account.User.Name == user.Name) { userBots.Add(bot); } } foreach (var bot in userBots) { // We can't directly restart a bot with just BotType and Name // Instead, stop the bot and then retrieve the backup to start it again await _mediator.Send(new StopBotCommand(bot.BotType, bot.Name)); // Get the saved bot backup var backup = _botService.GetBotBackup(bot.Name); if (backup != null) { _botService.StartBotFromBackup(backup); await _hubContext.Clients.All.SendAsync("SendNotification", $"Bot {bot.Name} restarted by {user.Name}.", "Info"); } } await NotifyBotSubscriberAsync(); return "All your bots have been restarted successfully!"; } catch (Exception e) { _logger.LogError(e, "Failed to restart all bots"); return $"Error restarting bots: {e.Message}"; } } /// /// Toggles the watching status of a bot specified by name. /// /// The name of the bot to toggle watching status. /// A string indicating the new watching status of the bot. [HttpGet] [Route("ToggleIsForWatching")] public async Task> ToggleIsForWatching(string botName) { try { // Check if user owns the account if (!await UserOwnsBotAccount(botName)) { return Forbid("You don't have permission to modify this bot"); } var result = await _mediator.Send(new ToggleIsForWatchingCommand(botName)); _logger.LogInformation($"{botName} bot is now {result}"); await NotifyBotSubscriberAsync(); return Ok(result); } catch (Exception ex) { _logger.LogError(ex, "Error toggling bot watching status"); return StatusCode(500, $"Error toggling bot watching status: {ex.Message}"); } } /// /// Retrieves a list of active bots. /// /// A list of active trading bots. [HttpGet] public async Task> GetActiveBots() { return await GetBotList(); } /// /// Retrieves a list of active bots by sending a command to the mediator. /// /// A list of trading bots. private async Task> GetBotList() { var result = await _mediator.Send(new GetActiveBotsCommand()); var list = new List(); foreach (var item in result) { list.Add(new ApiTradingBot { Status = item.GetStatus(), Name = item.Name, Signals = item.Signals.ToList(), Positions = item.Positions, Candles = item.Candles.DistinctBy(c => c.Date).ToList(), WinRate = item.GetWinRate(), ProfitAndLoss = item.GetProfitAndLoss(), Timeframe = item.Timeframe, Ticker = item.Ticker, Scenario = item.ScenarioName, IsForWatchingOnly = item.IsForWatchingOnly, BotType = item.BotType, AccountName = item.AccountName, MoneyManagement = item.MoneyManagement }); } return list; } /// /// Notifies subscribers about the current list of bots via SignalR. /// private async Task NotifyBotSubscriberAsync() { var botsList = await GetBotList(); await _hubContext.Clients.All.SendAsync("BotsSubscription", botsList); } /// /// Manually opens a position for a specified bot with the given parameters. /// /// The request containing position parameters. /// A response indicating the result of the operation. [HttpPost] [Route("OpenPosition")] public async Task> OpenPositionManually([FromBody] OpenPositionManuallyRequest request) { try { // Check if user owns the account if (!await UserOwnsBotAccount(request.BotName)) { return Forbid("You don't have permission to open positions for this bot"); } var activeBots = _botService.GetActiveBots(); var bot = activeBots.FirstOrDefault(b => b.Name == request.BotName) as ApplicationTradingBot; if (bot == null) { return NotFound($"Bot {request.BotName} not found or is not a trading bot"); } if (bot.GetStatus() != BotStatus.Up.ToString()) { return BadRequest($"Bot {request.BotName} is not running"); } var position = await bot.OpenPositionManually( request.Direction ); await NotifyBotSubscriberAsync(); return Ok(position); } catch (Exception ex) { _logger.LogError(ex, "Error opening position manually"); return StatusCode(500, $"Error opening position: {ex.Message}"); } } }