feat(Backend): update API gateway and websocket hubs
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using FinlyticBackend.Util;
|
||||
using FinlyticCore.Dtos;
|
||||
using FinlyticCore.Models.Trades;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace FinlyticBackend.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("api/v1/user/trades")]
|
||||
public class UserTradesController : ControllerBase
|
||||
{
|
||||
private readonly WebMqttClient _mqttClient;
|
||||
private readonly ILogger<UserTradesController> _logger;
|
||||
|
||||
public UserTradesController(WebMqttClient mqttClient, ILogger<UserTradesController> logger)
|
||||
{
|
||||
_mqttClient = mqttClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Liest die eindeutige UserId aus den Claims des authentifizierten Bearer Tokens.
|
||||
/// </summary>
|
||||
private string GetUserIdFromClaims()
|
||||
{
|
||||
var claimUserId = User.FindFirstValue(ClaimTypes.NameIdentifier)
|
||||
?? User.FindFirstValue("sub")
|
||||
?? User.FindFirstValue("nameid");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(claimUserId))
|
||||
{
|
||||
return claimUserId;
|
||||
}
|
||||
|
||||
_logger.LogWarning("[UserTradesController] Claim NameIdentifier not found for authenticated request. Falling back to default_user.");
|
||||
return "default_user";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a list of trades for the current authenticated user (including global proposals).
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetUserTrades([FromQuery] string? isin = null, [FromQuery] string? status = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_mqttClient.IsConnected)
|
||||
{
|
||||
return StatusCode(503, new { error = "MQTT Broker disconnected" });
|
||||
}
|
||||
|
||||
var userId = GetUserIdFromClaims();
|
||||
|
||||
// FIX: UserId explizit an GetTradesRequest übergeben!
|
||||
var request = new GetTradesRequest(isin, status, userId);
|
||||
|
||||
var trades = await _mqttClient.SendRpcRequestAsync<List<TradeProposalDto>, GetTradesRequest>(
|
||||
"trades_Get",
|
||||
request,
|
||||
TimeSpan.FromSeconds(5));
|
||||
|
||||
return Ok(trades ?? new List<TradeProposalDto>());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to retrieve user trades via MQTT RPC.");
|
||||
return StatusCode(500, new { error = "Internal server error while fetching trades" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public endpoint to retrieve active global proposals for guest users.
|
||||
/// </summary>
|
||||
[HttpGet("/api/v1/trades/public")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> GetPublicProposals([FromQuery] string? isin = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_mqttClient.IsConnected)
|
||||
{
|
||||
return StatusCode(503, new { error = "MQTT Broker disconnected" });
|
||||
}
|
||||
|
||||
// Status = Proposed für anonyme Öffentliche Anfragen
|
||||
var request = new GetTradesRequest(isin, "Proposed", UserId: null);
|
||||
var proposals = await _mqttClient.SendRpcRequestAsync<List<TradeProposalDto>, GetTradesRequest>(
|
||||
"trades_Get",
|
||||
request,
|
||||
TimeSpan.FromSeconds(5));
|
||||
|
||||
return Ok(proposals ?? new List<TradeProposalDto>());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to retrieve public trade proposals via MQTT RPC.");
|
||||
return Ok(new List<TradeProposalDto>());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accepts a proposed trade and assigns it to the current user's portfolio.
|
||||
/// </summary>
|
||||
[HttpPost("accept")]
|
||||
public async Task<IActionResult> AcceptTrade([FromBody] TradeAcceptanceDto request)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_mqttClient.IsConnected)
|
||||
{
|
||||
return StatusCode(503, new { error = "MQTT Broker disconnected" });
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.AnalysisId) && string.IsNullOrWhiteSpace(request.TradeId))
|
||||
{
|
||||
return BadRequest(new { error = "AnalysisId or TradeId is required" });
|
||||
}
|
||||
|
||||
// FIX: UserId felsenfest aus den authentifizierten Claims überschreiben
|
||||
request.UserId = GetUserIdFromClaims();
|
||||
|
||||
var result = await _mqttClient.SendRpcRequestAsync<TradeProposalDto, TradeAcceptanceDto>(
|
||||
"trades_Accept",
|
||||
request,
|
||||
TimeSpan.FromSeconds(5));
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
// Fallback Fire-and-Forget
|
||||
await _mqttClient.PublishAsync($"finlytic/trades/accept/{request.Isin}", request);
|
||||
return Ok(new { status = "Accepted", analysisId = request.AnalysisId, tradeId = request.TradeId, userId = request.UserId });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to accept trade proposal via MQTT RPC.");
|
||||
return StatusCode(500, new { error = "Internal server error" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes an active trade.
|
||||
/// </summary>
|
||||
[HttpPost("{id}/close")]
|
||||
public async Task<IActionResult> CloseTrade(string id, [FromBody] CloseTradeRequest? request)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_mqttClient.IsConnected)
|
||||
{
|
||||
return StatusCode(503, new { error = "MQTT Broker disconnected" });
|
||||
}
|
||||
|
||||
var closeReq = request ?? new CloseTradeRequest { UserExitPrice = 100.0m, CloseReason = "UserManualClose" };
|
||||
var result = await _mqttClient.SendRpcRequestAsync<TradeProposalDto, CloseTradeRequest>(
|
||||
$"trades_Close/{id}",
|
||||
closeReq,
|
||||
TimeSpan.FromSeconds(5));
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return Ok(new { status = "Closed", tradeId = id });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to close trade {Id} via MQTT RPC.", id);
|
||||
return StatusCode(500, new { error = "Internal server error while closing trade" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rejects a proposed trade.
|
||||
/// </summary>
|
||||
[HttpPost("{id}/reject")]
|
||||
public async Task<IActionResult> RejectTrade(string id, [FromBody] CloseTradeRequest? request)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_mqttClient.IsConnected)
|
||||
{
|
||||
return StatusCode(503, new { error = "MQTT Broker disconnected" });
|
||||
}
|
||||
|
||||
var closeReq = request ?? new CloseTradeRequest { CloseReason = "UserRejected" };
|
||||
var result = await _mqttClient.SendRpcRequestAsync<TradeProposalDto, CloseTradeRequest>(
|
||||
$"trades_Reject/{id}",
|
||||
closeReq,
|
||||
TimeSpan.FromSeconds(5));
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return Ok(new { status = "Rejected", tradeId = id });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to reject trade {Id} via MQTT RPC.", id);
|
||||
return StatusCode(500, new { error = "Internal server error while rejecting trade" });
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user