144 lines
3.5 KiB
C#
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 });
|
|
}
|
|
}
|
|
}
|
|
}
|