From fb49190346f2458f1e353ead06fc5f8715e3f2f5 Mon Sep 17 00:00:00 2001 From: cryptooda Date: Sat, 3 Jan 2026 03:09:44 +0700 Subject: [PATCH] Add agent summary update functionality and improve user controller - Introduced a new endpoint in UserController to update the agent summary, ensuring balance data is refreshed after transactions. - Implemented ForceUpdateSummaryImmediate method in IAgentGrain to allow immediate updates without cooldown checks. - Enhanced StartBotCommandHandler to force update the agent summary before starting the bot, ensuring accurate balance data. - Updated TypeScript API client to include the new update-agent-summary method for frontend integration. --- .../Controllers/UserController.cs | 23 ++++++++++- .../Abstractions/Grains/IAgentGrain.cs | 7 ++++ .../Bots/Grains/AgentGrain.cs | 17 ++++++++ .../Commands/UpdateAgentSummaryCommand.cs | 16 ++++++++ .../ManageBot/StartBotCommandHandler.cs | 23 ++++++++++- .../UpdateAgentSummaryCommandHandler.cs | 40 +++++++++++++++++++ .../src/generated/ManagingApi.ts | 40 +++++++++++++++++++ 7 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 src/Managing.Application/ManageBot/Commands/UpdateAgentSummaryCommand.cs create mode 100644 src/Managing.Application/ManageBot/UpdateAgentSummaryCommandHandler.cs diff --git a/src/Managing.Api/Controllers/UserController.cs b/src/Managing.Api/Controllers/UserController.cs index c137ca7a..c8b51cdb 100644 --- a/src/Managing.Api/Controllers/UserController.cs +++ b/src/Managing.Api/Controllers/UserController.cs @@ -1,7 +1,10 @@ using Managing.Api.Authorization; using Managing.Api.Models.Requests; +using Managing.Application.Abstractions.Models; using Managing.Application.Abstractions.Services; +using Managing.Application.ManageBot.Commands; using Managing.Domain.Users; +using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -18,6 +21,7 @@ public class UserController : BaseController private IConfiguration _config; private readonly IJwtUtils _jwtUtils; private readonly IWebhookService _webhookService; + private readonly IMediator _mediator; /// /// Initializes a new instance of the class. @@ -26,13 +30,15 @@ public class UserController : BaseController /// Service for user-related operations. /// Utility for JWT token operations. /// Service for webhook operations. + /// Mediator for handling commands. public UserController(IConfiguration config, IUserService userService, IJwtUtils jwtUtils, - IWebhookService webhookService) + IWebhookService webhookService, IMediator mediator) : base(userService) { _config = config; _jwtUtils = jwtUtils; _webhookService = webhookService; + _mediator = mediator; } /// @@ -158,7 +164,7 @@ public class UserController : BaseController var user = await GetUser(); // Map API request to DTO // Note: IsGmxEnabled and DefaultExchange are not updatable via settings endpoint - var settingsDto = new Managing.Application.Abstractions.Models.UserSettingsDto + var settingsDto = new UserSettingsDto { LowEthAmountAlert = settings.LowEthAmountAlert, EnableAutoswap = settings.EnableAutoswap, @@ -174,4 +180,17 @@ public class UserController : BaseController var updatedUser = await _userService.UpdateUserSettings(user, settingsDto); return Ok(updatedUser); } + + /// + /// Updates the agent summary by refreshing balance data and recalculating metrics. + /// Should be called after a topup/deposit to ensure the balance is up to date. + /// + /// Success response. + [HttpPost("update-agent-summary")] + public async Task UpdateAgentSummary() + { + var user = await GetUser(); + await _mediator.Send(new UpdateAgentSummaryCommand(user)); + return Ok(new { message = "Agent summary updated successfully" }); + } } \ No newline at end of file diff --git a/src/Managing.Application/Abstractions/Grains/IAgentGrain.cs b/src/Managing.Application/Abstractions/Grains/IAgentGrain.cs index 928fa7ea..b2d7f50e 100644 --- a/src/Managing.Application/Abstractions/Grains/IAgentGrain.cs +++ b/src/Managing.Application/Abstractions/Grains/IAgentGrain.cs @@ -68,6 +68,13 @@ namespace Managing.Application.Abstractions.Grains [OneWay] Task ForceUpdateSummary(); + /// + /// Forces an immediate update of the agent summary without cooldown check (for critical updates like after topup) + /// Invalidates cached balance data to ensure fresh balance fetch + /// + [OneWay] + Task ForceUpdateSummaryImmediate(); + /// /// Updates the agent summary by recalculating from position data (used for initialization or manual refresh) /// diff --git a/src/Managing.Application/Bots/Grains/AgentGrain.cs b/src/Managing.Application/Bots/Grains/AgentGrain.cs index 83869f51..4b2a302a 100644 --- a/src/Managing.Application/Bots/Grains/AgentGrain.cs +++ b/src/Managing.Application/Bots/Grains/AgentGrain.cs @@ -176,6 +176,23 @@ public class AgentGrain : Grain, IAgentGrain await UpdateSummary(); } + /// + /// Forces an immediate update of the agent summary without cooldown check (for critical updates like after topup) + /// Invalidates cached balance data to ensure fresh balance fetch + /// + public async Task ForceUpdateSummaryImmediate() + { + // Invalidate cached balance data to force fresh fetch + _state.State.CachedBalanceData = null; + await _state.WriteStateAsync(); + + _logger.LogInformation("Force updating agent summary immediately for user {UserId} (cache invalidated)", + this.GetPrimaryKeyLong()); + + // Update summary immediately without cooldown check + await UpdateSummary(); + } + /// /// Updates the agent summary by recalculating from position data (used for initialization or manual refresh) /// diff --git a/src/Managing.Application/ManageBot/Commands/UpdateAgentSummaryCommand.cs b/src/Managing.Application/ManageBot/Commands/UpdateAgentSummaryCommand.cs new file mode 100644 index 00000000..855c169b --- /dev/null +++ b/src/Managing.Application/ManageBot/Commands/UpdateAgentSummaryCommand.cs @@ -0,0 +1,16 @@ +using Managing.Domain.Users; +using MediatR; + +namespace Managing.Application.ManageBot.Commands +{ + public class UpdateAgentSummaryCommand : IRequest + { + public User User { get; } + + public UpdateAgentSummaryCommand(User user) + { + User = user; + } + } +} + diff --git a/src/Managing.Application/ManageBot/StartBotCommandHandler.cs b/src/Managing.Application/ManageBot/StartBotCommandHandler.cs index e9365aa6..3a926555 100644 --- a/src/Managing.Application/ManageBot/StartBotCommandHandler.cs +++ b/src/Managing.Application/ManageBot/StartBotCommandHandler.cs @@ -5,6 +5,7 @@ using Managing.Application.ManageBot.Commands; using Managing.Common; using Managing.Domain.Accounts; using MediatR; +using Microsoft.Extensions.Logging; using static Managing.Common.Enums; namespace Managing.Application.ManageBot @@ -16,15 +17,22 @@ namespace Managing.Application.ManageBot private readonly IBotService _botService; private readonly ITradingService _tradingService; private readonly IFlagsmithService _flagsmithService; + private readonly ILogger _logger; public StartBotCommandHandler( - IAccountService accountService, IGrainFactory grainFactory, IBotService botService, ITradingService tradingService, IFlagsmithService flagsmithService) + IAccountService accountService, + IGrainFactory grainFactory, + IBotService botService, + ITradingService tradingService, + IFlagsmithService flagsmithService, + ILogger logger) { _accountService = accountService; _grainFactory = grainFactory; _botService = botService; _tradingService = tradingService; _flagsmithService = flagsmithService; + _logger = logger; } public async Task Handle(StartBotCommand request, CancellationToken cancellationToken) @@ -137,6 +145,19 @@ namespace Managing.Application.ManageBot $"Balance : {usdcBalance?.Value:F2 ?? 0} Available: {availableAllocation:F2} USDC."); } + // Force update agent summary to ensure we have the latest balance before starting bot + try + { + var agentGrain = _grainFactory.GetGrain(request.User.Id); + await agentGrain.ForceUpdateSummaryImmediate(); + } + catch (Exception ex) + { + // Log but don't fail - balance check already happened + // This is just to ensure summary is up to date before starting + _logger.LogWarning(ex, "Failed to update agent summary before starting bot for user {UserId}", request.User.Id); + } + try { var botGrain = _grainFactory.GetGrain(Guid.NewGuid()); diff --git a/src/Managing.Application/ManageBot/UpdateAgentSummaryCommandHandler.cs b/src/Managing.Application/ManageBot/UpdateAgentSummaryCommandHandler.cs new file mode 100644 index 00000000..9451db8a --- /dev/null +++ b/src/Managing.Application/ManageBot/UpdateAgentSummaryCommandHandler.cs @@ -0,0 +1,40 @@ +using Managing.Application.Abstractions.Grains; +using Managing.Application.ManageBot.Commands; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Managing.Application.ManageBot +{ + public class UpdateAgentSummaryCommandHandler : IRequestHandler + { + private readonly IGrainFactory _grainFactory; + private readonly ILogger _logger; + + public UpdateAgentSummaryCommandHandler( + IGrainFactory grainFactory, + ILogger logger) + { + _grainFactory = grainFactory; + _logger = logger; + } + + public async Task Handle(UpdateAgentSummaryCommand request, CancellationToken cancellationToken) + { + try + { + var agentGrain = _grainFactory.GetGrain(request.User.Id); + await agentGrain.ForceUpdateSummaryImmediate(); + + _logger.LogInformation("Agent summary updated for user {UserId} after topup", request.User.Id); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating agent summary for user {UserId}", request.User.Id); + throw; + } + + return Unit.Value; + } + } +} + diff --git a/src/Managing.WebApp/src/generated/ManagingApi.ts b/src/Managing.WebApp/src/generated/ManagingApi.ts index c6705d6b..58170eb1 100644 --- a/src/Managing.WebApp/src/generated/ManagingApi.ts +++ b/src/Managing.WebApp/src/generated/ManagingApi.ts @@ -4461,6 +4461,46 @@ export class UserClient extends AuthorizedApiBase { } return Promise.resolve(null as any); } + + user_UpdateAgentSummary(): Promise { + let url_ = this.baseUrl + "/User/update-agent-summary"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.transformOptions(options_).then(transformedOptions_ => { + return this.http.fetch(url_, transformedOptions_); + }).then((_response: Response) => { + return this.processUser_UpdateAgentSummary(_response); + }); + } + + protected processUser_UpdateAgentSummary(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } } export class WhitelistClient extends AuthorizedApiBase {