feat(backend): add admin evaluation history, engine, simulation, bot API controllers and global exception middleware
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
@@ -7,11 +8,13 @@ using System.Threading.Tasks;
|
||||
using FinlyticBackend.Database;
|
||||
using FinlyticBackend.Hubs;
|
||||
using FinlyticBackend.Services;
|
||||
using FinlyticBackend.Settings;
|
||||
using FinlyticCore.Dtos.Bot;
|
||||
using FinlyticCore.Dtos.Logging;
|
||||
using FinlyticCore.Dtos.News;
|
||||
using FinlyticCore.Dtos.Trading;
|
||||
using FinlyticCore.Models;
|
||||
using FinlyticCore.Models.Auth;
|
||||
using FinlyticCore.Models.Trades;
|
||||
using FinlyticCore.Services;
|
||||
using FinlyticCore.Util;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -23,85 +26,98 @@ using Microsoft.Extensions.Logging;
|
||||
namespace FinlyticBackend.Util;
|
||||
|
||||
/// <summary>
|
||||
/// Central Managed MQTT Bridge & RPC Gateway for FinlyticBackend.
|
||||
/// Subscribes to general broadcast MQTT topics and forwards them to SignalR clients and FCM push services.
|
||||
/// </summary>
|
||||
public class BackendMqttBridge : ManagedMqttClient, IHostedService
|
||||
{
|
||||
public static readonly ConcurrentDictionary<string, JsonElement> FundamentalsCache = new(StringComparer.OrdinalIgnoreCase);
|
||||
public static readonly ConcurrentDictionary<string, JsonElement> TechnicalsCache = new(StringComparer.OrdinalIgnoreCase);
|
||||
public static readonly ConcurrentDictionary<string, ConcurrentQueue<LogMessageDto>> ServiceLogsRingBuffer = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly IHubContext<TradeRealtimeHub, ITradeClient> _hubContext;
|
||||
private readonly IHubContext<TradeHub> _tradeHubContext;
|
||||
private readonly IHubContext<TradeStreamHub, ITradeStreamClient> _tradeStreamHubContext;
|
||||
private readonly IHubContext<NewsHub> _newsHubContext;
|
||||
private readonly IHubContext<LogStreamHub> _logHubContext;
|
||||
private readonly IFirebaseNotificationService _firebaseService;
|
||||
private readonly ILogger<BackendMqttBridge> _logger;
|
||||
private readonly IFinlyticLogger<BackendMqttBridge> _finlyticLogger;
|
||||
|
||||
public BackendMqttBridge(
|
||||
IConfiguration configuration,
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IHubContext<TradeRealtimeHub, ITradeClient> hubContext,
|
||||
IHubContext<TradeHub> tradeHubContext,
|
||||
IHubContext<TradeStreamHub, ITradeStreamClient> tradeStreamHubContext,
|
||||
IHubContext<NewsHub> newsHubContext,
|
||||
IHubContext<LogStreamHub> logHubContext,
|
||||
IFirebaseNotificationService firebaseService,
|
||||
ILogger<BackendMqttBridge> logger) : base(logger)
|
||||
ILogger<BackendMqttBridge> logger,
|
||||
IFinlyticLogger<BackendMqttBridge> finlyticLogger) : base(logger)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_scopeFactory = scopeFactory;
|
||||
_hubContext = hubContext;
|
||||
_tradeHubContext = tradeHubContext;
|
||||
_tradeStreamHubContext = tradeStreamHubContext;
|
||||
_newsHubContext = newsHubContext;
|
||||
_logHubContext = logHubContext;
|
||||
_firebaseService = firebaseService;
|
||||
_logger = logger;
|
||||
_finlyticLogger = finlyticLogger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var config = new MqttConfiguration
|
||||
{
|
||||
Host = _configuration["MQTT:Host"] ?? _configuration["MQTT__Host"] ?? "localhost",
|
||||
Port = Convert.ToInt32(_configuration["MQTT:Port"] ?? _configuration["MQTT__Port"] ?? "1883"),
|
||||
ClientId = $"{(_configuration["MQTT:ClientId"] ?? _configuration["MQTT__ClientId"] ?? "finlytic_backend_bridge")}_{Guid.NewGuid()}"
|
||||
};
|
||||
var config = MqttConfiguration.FromConfiguration(_configuration, "finlytic_backend_gateway");
|
||||
|
||||
_logger.LogInformation("Starting Backend MQTT Bridge. Host: {Host}, ClientId: {ClientId}", config.Host, config.ClientId);
|
||||
_logger.LogInformation("Starting Backend MQTT Gateway Bridge. Host: {Host}, ClientId: {ClientId}", config.Host, config.ClientId);
|
||||
await ConnectAsync(config);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Stopping Backend MQTT Bridge.");
|
||||
_logger.LogInformation("Stopping Backend MQTT Gateway Bridge.");
|
||||
await DisconnectAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task OnConnectedAsync()
|
||||
{
|
||||
_logger.LogInformation("Backend MQTT Bridge connected. Subscribing to broadcast topics...");
|
||||
|
||||
await SubscribeAsync("finlytic/trades/proposed/#");
|
||||
await SubscribeAsync("finlytic/trades/updates/#");
|
||||
|
||||
// News topics
|
||||
await SubscribeAsync("services/news/completed");
|
||||
await SubscribeAsync("finlytic/news/#");
|
||||
await SubscribeAsync("finlytic/sentiment/#");
|
||||
await _finlyticLogger.LogInfoAsync(BackendSettingKeys.MqttChannel,
|
||||
"[BackendMqttBridge] Backend MQTT Gateway Bridge connected. Subscribing to broadcast topics...");
|
||||
|
||||
// Fundamentals & Technicals
|
||||
await SubscribeAsync("finlytic/fundamentals/#");
|
||||
await SubscribeAsync("finlytic/assets/fundamentals/#");
|
||||
await SubscribeAsync("finlytic/technicalanalysis/#");
|
||||
await SubscribeAsync("finlytic/ta/#");
|
||||
// RPC response stream
|
||||
await SubscribeAsync(MqttTopics.ResponseWildcard);
|
||||
|
||||
// Engine & Bot streams
|
||||
await SubscribeAsync(MqttTopics.EngineWildcard);
|
||||
await SubscribeAsync(MqttTopics.BotWildcard);
|
||||
|
||||
// News & Sentiment streams
|
||||
await SubscribeAsync(MqttTopics.NewsCompleted);
|
||||
await SubscribeAsync(MqttTopics.NewsStreamWildcard);
|
||||
await SubscribeAsync(MqttTopics.SentimentWildcard);
|
||||
|
||||
// Real-time Logs
|
||||
await SubscribeAsync("finlytic/logs/#");
|
||||
await SubscribeAsync(MqttTopics.LogsWildcard);
|
||||
|
||||
// Universe RPC Endpoint for FinlyticTechnicals
|
||||
await SubscribeRpcAsync<object, List<string>>(
|
||||
MqttTopics.RequestFilter(MqttTopics.Channels.BackendGetAggregatedFavorites),
|
||||
HandleGetAggregatedFavoritesRpcAsync);
|
||||
|
||||
// FinlyticBackend previously never broadcast its OWN structured logs at all (it only relayed other
|
||||
// services' logs received on MqttTopics.LogsWildcard, subscribed above) - it never used
|
||||
// IFinlyticLogger<T>, so FinlyticLogBroadcaster.Broadcast was never invoked for anything happening
|
||||
// inside FinlyticBackend itself. This hook publishes FinlyticBackend's own logs onto the exact same
|
||||
// finlytic/logs/FinlyticBackend topic every other service publishes to, which the wildcard
|
||||
// subscription above then picks straight back up and relays into LogStreamHub like any other
|
||||
// service's messages - no separate "local echo" mechanism needed.
|
||||
FinlyticCore.Services.FinlyticLogBroadcaster.OnLogPublished = async (logDto) =>
|
||||
{
|
||||
if (IsConnected && string.Equals(logDto.ServiceName, "FinlyticBackend", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await PublishAsync(MqttTopics.Logs("FinlyticBackend"), logDto);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -111,39 +127,33 @@ public class BackendMqttBridge : ManagedMqttClient, IHostedService
|
||||
|
||||
try
|
||||
{
|
||||
if (topic.StartsWith("finlytic/logs/", StringComparison.OrdinalIgnoreCase))
|
||||
if (topic.StartsWith(MqttTopics.LogsPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await HandleLogMessageAsync(payloadStr);
|
||||
}
|
||||
else if (topic.StartsWith("finlytic/trades/proposed/", StringComparison.OrdinalIgnoreCase) ||
|
||||
topic.StartsWith("finlytic/trades/update", StringComparison.OrdinalIgnoreCase))
|
||||
else if (topic.StartsWith(MqttTopics.EngineProposalsPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await HandleTradeProposalAsync(payloadStr);
|
||||
await HandleEngineProposalAsync(payloadStr);
|
||||
}
|
||||
else if (topic.StartsWith("finlytic/trades/updates/", StringComparison.OrdinalIgnoreCase))
|
||||
else if (topic.StartsWith(MqttTopics.EngineTradesPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await HandleTradeUpdateAsync(payloadStr);
|
||||
await HandleEngineTradeUpdateAsync(payloadStr);
|
||||
}
|
||||
else if (topic.Equals("services/news/completed", StringComparison.OrdinalIgnoreCase) ||
|
||||
topic.StartsWith("finlytic/news/", StringComparison.OrdinalIgnoreCase) ||
|
||||
topic.StartsWith("finlytic/sentiment/", StringComparison.OrdinalIgnoreCase))
|
||||
else if (topic.StartsWith(MqttTopics.BotTradesPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await HandleBotTradeUpdateAsync(payloadStr);
|
||||
}
|
||||
else if (topic.Equals(MqttTopics.NewsCompleted, StringComparison.OrdinalIgnoreCase) ||
|
||||
topic.StartsWith(MqttTopics.NewsPrefix, StringComparison.OrdinalIgnoreCase) ||
|
||||
topic.StartsWith(MqttTopics.SentimentPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await HandleNewsArticleAsync(payloadStr);
|
||||
}
|
||||
else if (topic.StartsWith("finlytic/fundamentals/", StringComparison.OrdinalIgnoreCase) ||
|
||||
topic.StartsWith("finlytic/assets/fundamentals/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
HandleFundamentalsCache(topic, payloadStr);
|
||||
}
|
||||
else if (topic.StartsWith("finlytic/technicalanalysis/", StringComparison.OrdinalIgnoreCase) ||
|
||||
topic.StartsWith("finlytic/ta/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
HandleTechnicalsCache(topic, payloadStr);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing message in Backend MQTT Bridge on topic {Topic}", topic);
|
||||
await _finlyticLogger.LogErrorAsync(BackendSettingKeys.BackendChannel, ex,
|
||||
"[BackendMqttBridge] Error processing message on topic {Topic}", topic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,49 +169,94 @@ public class BackendMqttBridge : ManagedMqttClient, IHostedService
|
||||
// Keep buffer capped at 250 entries
|
||||
while (queue.Count > 250 && queue.TryDequeue(out _)) { }
|
||||
|
||||
// Broadcast to SignalR clients
|
||||
await _logHubContext.Clients.Group(serviceKey).SendAsync("ReceiveLogMessage", logDto);
|
||||
// Broadcast once to all connected SignalR admin logs clients
|
||||
await _logHubContext.Clients.All.SendAsync("ReceiveLogMessage", logDto);
|
||||
}
|
||||
|
||||
private async Task HandleTradeProposalAsync(string payloadStr)
|
||||
private async Task HandleEngineProposalAsync(string payloadStr)
|
||||
{
|
||||
var proposal = JsonSerializer.Deserialize<TradeProposalDto>(payloadStr);
|
||||
if (proposal == null) return;
|
||||
|
||||
await _hubContext.Clients.All.OnTradeProposed(proposal);
|
||||
await _tradeHubContext.Clients.All.SendAsync("ReceiveTradeUpdate", proposal);
|
||||
_logger.LogInformation("Broadcasted Trade Proposal {AnalysisId} via SignalR.", proposal.AnalysisId);
|
||||
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<BackendDbContext>();
|
||||
var fcmTokens = await dbContext.UserDeviceTokens.AsNoTracking().Select(t => t.FcmToken).ToListAsync();
|
||||
|
||||
if (fcmTokens.Count > 0)
|
||||
await _tradeStreamHubContext.Clients.All.ReceiveTradeProposal(proposal);
|
||||
if (!string.IsNullOrWhiteSpace(proposal.UnderlyingIsin))
|
||||
{
|
||||
await _firebaseService.SendTradeProposalNotificationAsync(proposal, fcmTokens);
|
||||
await _tradeStreamHubContext.Clients.Group(proposal.UnderlyingIsin.ToUpperInvariant()).ReceiveTradeProposal(proposal);
|
||||
}
|
||||
|
||||
await _finlyticLogger.LogInfoAsync(BackendSettingKeys.BackendChannel,
|
||||
"[BackendMqttBridge] Broadcasted FinlyticEngine Trade Proposal {ProposalId} for {Isin} via SignalR TradeStreamHub.", proposal.ProposalId, proposal.UnderlyingIsin);
|
||||
|
||||
// FCM Push Notification for High Score Proposals (Score >= 75)
|
||||
if (proposal.CompositeScore >= 75)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<BackendDbContext>();
|
||||
var fcmTokens = await dbContext.UserDeviceTokens.AsNoTracking().Select(t => t.FcmToken).ToListAsync();
|
||||
|
||||
if (fcmTokens.Count > 0)
|
||||
{
|
||||
await _firebaseService.SendTradeProposalNotificationAsync(proposal, fcmTokens);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _finlyticLogger.LogWarningAsync(BackendSettingKeys.PushNotificationChannel, ex,
|
||||
"[BackendMqttBridge] Failed to send FCM push notification for proposal {ProposalId}.", proposal.ProposalId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleTradeUpdateAsync(string payloadStr)
|
||||
private async Task HandleEngineTradeUpdateAsync(string payloadStr)
|
||||
{
|
||||
var update = JsonSerializer.Deserialize<TradeHourlyUpdateDto>(payloadStr);
|
||||
if (update == null) return;
|
||||
var trade = JsonSerializer.Deserialize<ActiveTradeDto>(payloadStr);
|
||||
if (trade == null) return;
|
||||
|
||||
await _hubContext.Clients.All.OnTradeUpdated(update);
|
||||
await _tradeHubContext.Clients.All.SendAsync("ReceiveTradeUpdate", update);
|
||||
|
||||
if (string.Equals(update.Recommendation, "Close", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(update.Recommendation, "AdjustSL", StringComparison.OrdinalIgnoreCase))
|
||||
await _tradeStreamHubContext.Clients.All.ReceiveTradeUpdate(trade);
|
||||
if (!string.IsNullOrWhiteSpace(trade.UnderlyingIsin))
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<BackendDbContext>();
|
||||
var fcmTokens = await dbContext.UserDeviceTokens.AsNoTracking().Select(t => t.FcmToken).ToListAsync();
|
||||
await _tradeStreamHubContext.Clients.Group(trade.UnderlyingIsin.ToUpperInvariant()).ReceiveTradeUpdate(trade);
|
||||
}
|
||||
|
||||
if (fcmTokens.Count > 0)
|
||||
await _finlyticLogger.LogInfoAsync(BackendSettingKeys.BackendChannel,
|
||||
"[BackendMqttBridge] Broadcasted FinlyticEngine Trade Update {TradeId} (Status: {Status}) via SignalR TradeStreamHub.", trade.TradeId, trade.Status);
|
||||
|
||||
// Push notification on important state transitions (e.g. stopped out or TP2 hit)
|
||||
if (trade.Status == TradeStatus.StoppedOut || trade.Status == TradeStatus.Tp2Hit || trade.Status == TradeStatus.Closed)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _firebaseService.SendTradeUpdateNotificationAsync(update, fcmTokens);
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<BackendDbContext>();
|
||||
var fcmTokens = await dbContext.UserDeviceTokens.AsNoTracking().Select(t => t.FcmToken).ToListAsync();
|
||||
|
||||
if (fcmTokens.Count > 0)
|
||||
{
|
||||
await _firebaseService.SendTradeUpdateNotificationAsync(trade, fcmTokens);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _finlyticLogger.LogWarningAsync(BackendSettingKeys.PushNotificationChannel, ex,
|
||||
"[BackendMqttBridge] Failed to send FCM push notification for trade update {TradeId}.", trade.TradeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleBotTradeUpdateAsync(string payloadStr)
|
||||
{
|
||||
var botTrade = JsonSerializer.Deserialize<BotTradeOrderDto>(payloadStr, DefaultJsonOptions);
|
||||
if (botTrade != null)
|
||||
{
|
||||
await _tradeStreamHubContext.Clients.All.ReceiveBotPositionUpdate(botTrade);
|
||||
if (!string.IsNullOrWhiteSpace(botTrade.Isin))
|
||||
{
|
||||
await _tradeStreamHubContext.Clients.Group(botTrade.Isin.ToUpperInvariant()).ReceiveBotPositionUpdate(botTrade);
|
||||
}
|
||||
await _finlyticLogger.LogInfoAsync(BackendSettingKeys.BackendChannel,
|
||||
"[BackendMqttBridge] Broadcasted Bot trade update for {Isin} ({Symbol}, Status: {Status}) via SignalR TradeStreamHub.", botTrade.Isin, botTrade.Symbol, botTrade.Status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,39 +266,24 @@ public class BackendMqttBridge : ManagedMqttClient, IHostedService
|
||||
if (article == null) return;
|
||||
|
||||
await _newsHubContext.Clients.All.SendAsync("ReceiveNewArticle", article);
|
||||
_logger.LogInformation("Broadcasted live news item '{Title}' over SignalR NewsHub.", article.Title);
|
||||
await _finlyticLogger.LogInfoAsync(BackendSettingKeys.BackendChannel,
|
||||
"[BackendMqttBridge] Broadcasted live news item '{Title}' over SignalR NewsHub.", article.Title);
|
||||
}
|
||||
|
||||
private void HandleFundamentalsCache(string topic, string payloadStr)
|
||||
private async Task<List<string>> HandleGetAggregatedFavoritesRpcAsync(object? _, string correlationId)
|
||||
{
|
||||
using var jsonDoc = JsonDocument.Parse(payloadStr);
|
||||
var root = jsonDoc.RootElement.Clone();
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<BackendDbContext>();
|
||||
|
||||
string? isin = root.TryGetProperty("isin", out var isinProp) ? isinProp.GetString() : null;
|
||||
string? ticker = root.TryGetProperty("primaryTicker", out var tickerProp)
|
||||
? tickerProp.GetString()
|
||||
: (root.TryGetProperty("ticker", out var tProp) ? tProp.GetString() : null);
|
||||
var isins = await dbContext.UserFavoriteAssets
|
||||
.AsNoTracking()
|
||||
.Select(f => f.Isin)
|
||||
.Where(isin => !string.IsNullOrWhiteSpace(isin))
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
|
||||
if (!string.IsNullOrEmpty(isin)) FundamentalsCache[isin] = root;
|
||||
if (!string.IsNullOrEmpty(ticker)) FundamentalsCache[ticker] = root;
|
||||
|
||||
string topicKey = topic.Split('/').LastOrDefault() ?? string.Empty;
|
||||
if (!string.IsNullOrEmpty(topicKey)) FundamentalsCache[topicKey] = root;
|
||||
|
||||
_logger.LogInformation("Cached fundamentals payload from MQTT topic {Topic}.", topic);
|
||||
}
|
||||
|
||||
private void HandleTechnicalsCache(string topic, string payloadStr)
|
||||
{
|
||||
using var jsonDoc = JsonDocument.Parse(payloadStr);
|
||||
var root = jsonDoc.RootElement.Clone();
|
||||
|
||||
string? symbol = root.TryGetProperty("symbol", out var sProp) ? sProp.GetString() : null;
|
||||
if (!string.IsNullOrEmpty(symbol)) TechnicalsCache[symbol] = root;
|
||||
|
||||
string topicKey = topic.Split('/').LastOrDefault() ?? string.Empty;
|
||||
if (!string.IsNullOrEmpty(topicKey)) TechnicalsCache[topicKey] = root;
|
||||
|
||||
_logger.LogInformation("Cached technical analysis payload from MQTT topic {Topic}.", topic);
|
||||
await _finlyticLogger.LogInfoAsync(BackendSettingKeys.MqttChannel,
|
||||
"[BackendMqttBridge] Responded to backend_GetAggregatedFavorites with {Count} unique ISINs. [CorrelationId: {CorrelationId}]", isins.Count, correlationId);
|
||||
return isins;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +1,44 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FinlyticCore.Models;
|
||||
using FinlyticCore.Util;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FinlyticBackend.Util;
|
||||
|
||||
/// <summary>
|
||||
/// Managed MQTT client for web gateway endpoints enabling RPC communication with background microservices.
|
||||
/// Backward-compatible bridge forwarding RPC requests to the primary singleton <see cref="BackendMqttBridge"/>.
|
||||
/// Prevents secondary MQTT connection instantiations in the gateway process.
|
||||
/// </summary>
|
||||
public class WebMqttClient : ManagedMqttClient, IHostedService
|
||||
public class WebMqttClient : IHostedService
|
||||
{
|
||||
private readonly ILogger<WebMqttClient> _logger;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly BackendMqttBridge _bridge;
|
||||
|
||||
public WebMqttClient(ILogger<WebMqttClient> logger, IConfiguration configuration) : base(logger)
|
||||
public bool IsConnected => _bridge.IsConnected;
|
||||
|
||||
public WebMqttClient(BackendMqttBridge bridge)
|
||||
{
|
||||
_logger = logger;
|
||||
_configuration = configuration;
|
||||
_bridge = bridge;
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var config = new MqttConfiguration
|
||||
{
|
||||
Host = _configuration["MQTT:Host"] ?? _configuration["MQTT__Host"] ?? "localhost",
|
||||
Port = Convert.ToInt32(_configuration["MQTT:Port"] ?? _configuration["MQTT__Port"] ?? "1883"),
|
||||
ClientId = $"{(_configuration["MQTT:ClientId"] ?? _configuration["MQTT__ClientId"] ?? "finlytic_backend_rpc")}_{Guid.NewGuid()}"
|
||||
};
|
||||
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
_logger.LogInformation("Starting Web MQTT RPC Client. Host: {Host}, ClientId: {ClientId}", config.Host, config.ClientId);
|
||||
await ConnectAsync(config);
|
||||
public Task<TResponse?> SendRpcRequestAsync<TResponse, TRequest>(string channel, TRequest request, TimeSpan timeout)
|
||||
where TResponse : class
|
||||
where TRequest : class
|
||||
{
|
||||
return _bridge.SendRpcRequestAsync<TResponse, TRequest>(channel, request, timeout);
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
public Task<TResponse?> SendRpcRequestAsync<TResponse, TRequest>(string channel, TRequest request)
|
||||
where TResponse : class
|
||||
where TRequest : class
|
||||
{
|
||||
_logger.LogInformation("Stopping Web MQTT RPC Client.");
|
||||
await DisconnectAsync();
|
||||
return _bridge.SendRpcRequestAsync<TResponse, TRequest>(channel, request);
|
||||
}
|
||||
|
||||
protected override async Task OnConnectedAsync()
|
||||
public Task PublishAsync<T>(string topic, T payload)
|
||||
{
|
||||
_logger.LogInformation("Web MQTT RPC client connected. Subscribing to RPC response channels...");
|
||||
await SubscribeAsync("services/response/news_Get/#");
|
||||
await SubscribeAsync("services/response/news_GetDaily/#");
|
||||
await SubscribeAsync("services/response/news_GetById/#");
|
||||
await SubscribeAsync("services/response/sentiment_GetArticle/#");
|
||||
await SubscribeAsync("services/response/sentiment_GetIsin/#");
|
||||
await SubscribeAsync("services/response/sentiment_Analyze/#");
|
||||
await SubscribeAsync("services/response/fundamentals_Get/#");
|
||||
await SubscribeAsync("services/response/events_GetAll/#");
|
||||
await SubscribeAsync("services/response/events_GetByMonth/#");
|
||||
await SubscribeAsync("services/response/ta_GetAnalysis/#");
|
||||
await SubscribeAsync("services/response/tr_GetLivePrice/#");
|
||||
await SubscribeAsync("services/response/assets_Get/#");
|
||||
await SubscribeAsync("services/response/assets_Search/#");
|
||||
await SubscribeAsync("services/response/assets_GetDiscovery/#");
|
||||
await SubscribeAsync("services/response/assets_GetDerivatives/#");
|
||||
await SubscribeAsync("services/response/trades_Get/#");
|
||||
await SubscribeAsync("services/response/trades_Close/#");
|
||||
await SubscribeAsync("services/response/trades_Reject/#");
|
||||
await SubscribeAsync("services/response/trades_Accept/#");
|
||||
await SubscribeAsync("services/response/analyzer_TriggerManual/#");
|
||||
await SubscribeAsync("services/response/health_Ping/#");
|
||||
|
||||
// Settings RPC response channels for all microservices
|
||||
await SubscribeAsync("services/response/fundamentals_settings_GetAll/#");
|
||||
await SubscribeAsync("services/response/fundamentals_settings_Update/#");
|
||||
await SubscribeAsync("services/response/news_settings_GetAll/#");
|
||||
await SubscribeAsync("services/response/news_settings_Update/#");
|
||||
await SubscribeAsync("services/response/ta_settings_GetAll/#");
|
||||
await SubscribeAsync("services/response/ta_settings_Update/#");
|
||||
await SubscribeAsync("services/response/sentiment_settings_GetAll/#");
|
||||
await SubscribeAsync("services/response/sentiment_settings_Update/#");
|
||||
await SubscribeAsync("services/response/analyzer_settings_GetAll/#");
|
||||
await SubscribeAsync("services/response/analyzer_settings_Update/#");
|
||||
await SubscribeAsync("services/response/trades_settings_GetAll/#");
|
||||
await SubscribeAsync("services/response/trades_settings_Update/#");
|
||||
await SubscribeAsync("services/response/assets_settings_GetAll/#");
|
||||
await SubscribeAsync("services/response/assets_settings_Update/#");
|
||||
}
|
||||
|
||||
protected override Task OnMessageReceivedAsync(string topic, string payload)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
return _bridge.PublishAsync(topic, payload);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user