Created database for user interests.
This commit is contained in:
parent
41d5d22b01
commit
54777c0a2d
@ -10,6 +10,7 @@ builder.Services.AddSwaggerGen();
|
||||
|
||||
builder.Services.AddSingleton<IHistoricalMarketDataCache, PostgresHistoricalMarketDataCache>();
|
||||
builder.Services.AddSingleton<MarketDataService>();
|
||||
builder.Services.AddSingleton<IUserInterestStore, PostgresUserInterestStore>();
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
|
||||
66
Controller/UserInterestController.cs
Normal file
66
Controller/UserInterestController.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using Backend.DatabaseHandler;
|
||||
using Backend.UserInterests;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Backend.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/user-interest")]
|
||||
public sealed class UserInterestController : ControllerBase
|
||||
{
|
||||
private readonly IUserInterestStore _store;
|
||||
|
||||
public UserInterestController(IUserInterestStore store)
|
||||
{
|
||||
_store = store;
|
||||
}
|
||||
|
||||
[HttpGet("fetch")]
|
||||
public async Task<ActionResult<UserInterestFetchResponse>> FetchAll(
|
||||
[FromQuery] string user,
|
||||
[FromQuery] string type)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<string> items = await _store.FetchAllAsync(user, type);
|
||||
return Ok(new UserInterestFetchResponse
|
||||
{
|
||||
Items = items
|
||||
});
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("add")]
|
||||
public async Task<ActionResult<BoolResponse>> Add(
|
||||
[FromBody] UserInterestModifyRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
bool success = await _store.AddAsync(request.User, request.Type, request.Value);
|
||||
return Ok(new BoolResponse { Success = success });
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("remove")]
|
||||
public async Task<ActionResult<BoolResponse>> Remove(
|
||||
[FromBody] UserInterestModifyRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
bool success = await _store.RemoveAsync(request.User, request.Type, request.Value);
|
||||
return Ok(new BoolResponse { Success = success });
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
DatabaseHandler/IUserInterestCache.cs
Normal file
8
DatabaseHandler/IUserInterestCache.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Backend.DatabaseHandler;
|
||||
|
||||
public interface IUserInterestStore
|
||||
{
|
||||
Task<List<string>> FetchAllAsync(string user, string type);
|
||||
Task<bool> AddAsync(string user, string type, string value);
|
||||
Task<bool> RemoveAsync(string user, string type, string value);
|
||||
}
|
||||
141
DatabaseHandler/PostgresUserInterestStore.cs
Normal file
141
DatabaseHandler/PostgresUserInterestStore.cs
Normal file
@ -0,0 +1,141 @@
|
||||
using Npgsql;
|
||||
|
||||
namespace Backend.DatabaseHandler;
|
||||
|
||||
public sealed class PostgresUserInterestStore : IUserInterestStore
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
|
||||
public PostgresUserInterestStore(IConfiguration configuration)
|
||||
{
|
||||
_connectionString = configuration.GetConnectionString("MarketDataDb")
|
||||
?? throw new InvalidOperationException("Missing connection string: MarketDataDb");
|
||||
}
|
||||
|
||||
public async Task<List<string>> FetchAllAsync(string user, string type)
|
||||
{
|
||||
ValidateUser(user);
|
||||
|
||||
string normalizedType = NormalizeType(type);
|
||||
string sql = normalizedType == "symbol"
|
||||
? """
|
||||
SELECT symbol
|
||||
FROM user_interest_symbols
|
||||
WHERE username = @user
|
||||
ORDER BY symbol ASC;
|
||||
"""
|
||||
: """
|
||||
SELECT topic
|
||||
FROM user_interest_topics
|
||||
WHERE username = @user
|
||||
ORDER BY topic ASC;
|
||||
""";
|
||||
|
||||
var results = new List<string>();
|
||||
|
||||
await using var conn = new NpgsqlConnection(_connectionString);
|
||||
await conn.OpenAsync();
|
||||
|
||||
await using var cmd = new NpgsqlCommand(sql, conn);
|
||||
cmd.Parameters.AddWithValue("user", user.Trim());
|
||||
|
||||
await using var reader = await cmd.ExecuteReaderAsync();
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
results.Add(reader.GetString(0));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public async Task<bool> AddAsync(string user, string type, string value)
|
||||
{
|
||||
ValidateUser(user);
|
||||
ValidateValue(value);
|
||||
|
||||
string normalizedType = NormalizeType(type);
|
||||
string normalizedValue = normalizedType == "symbol"
|
||||
? value.Trim().ToUpperInvariant()
|
||||
: value.Trim();
|
||||
|
||||
string sql = normalizedType == "symbol"
|
||||
? """
|
||||
INSERT INTO user_interest_symbols (username, symbol)
|
||||
VALUES (@user, @value)
|
||||
ON CONFLICT (username, symbol) DO NOTHING;
|
||||
"""
|
||||
: """
|
||||
INSERT INTO user_interest_topics (username, topic)
|
||||
VALUES (@user, @value)
|
||||
ON CONFLICT (username, topic) DO NOTHING;
|
||||
""";
|
||||
|
||||
await using var conn = new NpgsqlConnection(_connectionString);
|
||||
await conn.OpenAsync();
|
||||
|
||||
await using var cmd = new NpgsqlCommand(sql, conn);
|
||||
cmd.Parameters.AddWithValue("user", user.Trim());
|
||||
cmd.Parameters.AddWithValue("value", normalizedValue);
|
||||
|
||||
int rows = await cmd.ExecuteNonQueryAsync();
|
||||
return rows > 0;
|
||||
}
|
||||
|
||||
public async Task<bool> RemoveAsync(string user, string type, string value)
|
||||
{
|
||||
ValidateUser(user);
|
||||
ValidateValue(value);
|
||||
|
||||
string normalizedType = NormalizeType(type);
|
||||
string normalizedValue = normalizedType == "symbol"
|
||||
? value.Trim().ToUpperInvariant()
|
||||
: value.Trim();
|
||||
|
||||
string sql = normalizedType == "symbol"
|
||||
? """
|
||||
DELETE FROM user_interest_symbols
|
||||
WHERE username = @user AND symbol = @value;
|
||||
"""
|
||||
: """
|
||||
DELETE FROM user_interest_topics
|
||||
WHERE username = @user AND topic = @value;
|
||||
""";
|
||||
|
||||
await using var conn = new NpgsqlConnection(_connectionString);
|
||||
await conn.OpenAsync();
|
||||
|
||||
await using var cmd = new NpgsqlCommand(sql, conn);
|
||||
cmd.Parameters.AddWithValue("user", user.Trim());
|
||||
cmd.Parameters.AddWithValue("value", normalizedValue);
|
||||
|
||||
int rows = await cmd.ExecuteNonQueryAsync();
|
||||
return rows > 0;
|
||||
}
|
||||
|
||||
private static string NormalizeType(string type)
|
||||
{
|
||||
string normalized = type.Trim().ToLowerInvariant();
|
||||
return normalized switch
|
||||
{
|
||||
"symbol" => "symbol",
|
||||
"topic" => "topic",
|
||||
_ => throw new ArgumentException("Type must be either 'symbol' or 'topic'.")
|
||||
};
|
||||
}
|
||||
|
||||
private static void ValidateUser(string user)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(user))
|
||||
{
|
||||
throw new ArgumentException("User cannot be empty.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateValue(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
throw new ArgumentException("Value cannot be empty.");
|
||||
}
|
||||
}
|
||||
}
|
||||
24
UserInterests/UserInterests.cs
Normal file
24
UserInterests/UserInterests.cs
Normal file
@ -0,0 +1,24 @@
|
||||
namespace Backend.UserInterests;
|
||||
|
||||
public sealed class UserInterestFetchRequest
|
||||
{
|
||||
public string User { get; set; } = string.Empty;
|
||||
public string Type { get; set; } = string.Empty; // "symbol" or "topic"
|
||||
}
|
||||
|
||||
public sealed class UserInterestModifyRequest
|
||||
{
|
||||
public string User { get; set; } = string.Empty;
|
||||
public string Type { get; set; } = string.Empty; // "symbol" or "topic"
|
||||
public string Value { get; set; } = string.Empty; // symbol or topic
|
||||
}
|
||||
|
||||
public sealed class UserInterestFetchResponse
|
||||
{
|
||||
public List<string> Items { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class BoolResponse
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user