Files
Finlytic/FinlyticBackend/Controllers/UserTradesController.cs
T

215 lines
7.2 KiB
C#

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" });
}
}
}