SoftTraderBackend/Controller/NewsSentimentController.cs
2026-03-20 10:42:48 -04:00

144 lines
3.5 KiB
C#

using Backend.Interface;
using Backend.ServiceHander;
using Microsoft.AspNetCore.Mvc;
namespace Backend.Controller
{
[ApiController]
[Route("api/news-sentiment")]
public sealed class NewsSentimentController : ControllerBase
{
private static DateTime _lastRefresh = DateTime.MinValue;
private static readonly Lock _refreshLock = new();
private readonly NewsSentimentService _service;
private readonly IUserInterestStore _interests;
private readonly INewsSentimentStore _store;
public NewsSentimentController(
NewsSentimentService service,
IUserInterestStore interests,
INewsSentimentStore store)
{
_service = service;
_interests = interests;
_store = store;
}
[HttpGet]
public async Task<IActionResult> Analyze(
[FromQuery] string keyword,
CancellationToken cancellationToken)
{
try
{
var results = await _service.AnalyzeAsync(keyword, cancellationToken);
return Ok(results.Select(r => new
{
source = r.Source,
keyword = r.Keyword,
publishedAt = r.PublishedAt,
title = r.Title,
score = r.Score,
sentimentLabel = r.SentimentLabel,
publisher = r.Publisher,
url = r.Url
}));
}
catch (ArgumentException ex)
{
return BadRequest(ex.Message);
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.ToString() });
}
}
[HttpGet("feed")]
public async Task<IActionResult> Feed(
[FromQuery] string user,
[FromQuery] string? keyword = null,
[FromQuery(Name = "request-amount")] int requestAmount = 20,
CancellationToken cancellationToken = default)
{
try
{
if (string.IsNullOrWhiteSpace(user))
return BadRequest("User cannot be empty.");
if (requestAmount < 1)
requestAmount = 1;
List<string> keywords;
if (!string.IsNullOrWhiteSpace(keyword))
{
keywords = [keyword.Trim()];
}
else
{
var symbols = await _interests.FetchAllAsync(user, "symbol");
var topics = await _interests.FetchAllAsync(user, "topic");
keywords = symbols.Concat(topics).Distinct().ToList();
}
if (keywords.Count == 0)
return Ok(Array.Empty<object>());
var results = await _store.GetFeedAsync(keywords, requestAmount, cancellationToken);
return Ok(results.Select(r => new
{
title = r.Title,
publisher = r.Publisher,
score = r.Score,
publishedAt = r.PublishedAt.UtcDateTime.ToString("yyyy-MM-ddTHH:mm:ssZ"),
url = r.Url
}));
}
catch (ArgumentException ex)
{
return BadRequest(ex.Message);
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.ToString() });
}
}
[HttpGet("refresh")]
public async Task<ActionResult<BoolResponse>> Refresh(
[FromQuery] string user,
CancellationToken cancellationToken = default)
{
try
{
if (string.IsNullOrWhiteSpace(user))
return BadRequest("User cannot be empty.");
// 5 second refresh cooldown - abuse protection
lock (_refreshLock)
{
if ((DateTime.UtcNow - _lastRefresh).TotalSeconds < 5)
return Ok(new BoolResponse { Success = false });
_lastRefresh = DateTime.UtcNow;
}
var symbols = await _interests.FetchAllAsync(user, "symbol");
var topics = await _interests.FetchAllAsync(user, "topic");
var keywords = symbols.Concat(topics).Distinct().ToList();
foreach (var keyword in keywords)
{
await _service.AnalyzeAsync(keyword, cancellationToken);
}
return Ok(new BoolResponse { Success = true });
}
catch (Exception)
{
return Ok(new BoolResponse { Success = false });
}
}
}
}