Fix db and fix endpoints

This commit is contained in:
2025-08-05 22:30:18 +07:00
parent 2dcbcc3ef2
commit 36529ae403
36 changed files with 5073 additions and 245 deletions

View File

@@ -27,4 +27,5 @@ public interface IAgentSummaryRepository
SortableFields sortBy, SortableFields sortBy,
string sortOrder, string sortOrder,
IEnumerable<string>? agentNames = null); IEnumerable<string>? agentNames = null);
Task<IEnumerable<AgentSummary>> GetAllAgentWithRunningBots();
} }

View File

@@ -17,6 +17,7 @@ public interface ITradingRepository
Task InsertScenarioAsync(Scenario scenario); Task InsertScenarioAsync(Scenario scenario);
Task InsertIndicatorAsync(IndicatorBase indicator); Task InsertIndicatorAsync(IndicatorBase indicator);
Task<IEnumerable<Scenario>> GetScenariosAsync(); Task<IEnumerable<Scenario>> GetScenariosAsync();
Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user);
Task<IEnumerable<IndicatorBase>> GetStrategiesAsync(); Task<IEnumerable<IndicatorBase>> GetStrategiesAsync();
Task<IEnumerable<IndicatorBase>> GetIndicatorsAsync(); Task<IEnumerable<IndicatorBase>> GetIndicatorsAsync();
Task DeleteScenarioAsync(string name); Task DeleteScenarioAsync(string name);

View File

@@ -7,6 +7,5 @@ public interface IUserRepository
Task<User> GetUserByAgentNameAsync(string agentName); Task<User> GetUserByAgentNameAsync(string agentName);
Task<User> GetUserByNameAsync(string name); Task<User> GetUserByNameAsync(string name);
Task<IEnumerable<User>> GetAllUsersAsync(); Task<IEnumerable<User>> GetAllUsersAsync();
Task InsertUserAsync(User user); Task SaveOrUpdateUserAsync(User user);
Task UpdateUser(User user);
} }

View File

@@ -13,4 +13,5 @@ public interface IAgentService
Task SaveOrUpdateAgentSummary(AgentSummary agentSummary); Task SaveOrUpdateAgentSummary(AgentSummary agentSummary);
Task<IEnumerable<AgentSummary>> GetAllAgentSummaries(); Task<IEnumerable<AgentSummary>> GetAllAgentSummaries();
Task<IEnumerable<string>> GetAllOnlineAgents();
} }

View File

@@ -23,6 +23,7 @@ public interface ITradingService
Task InsertScenarioAsync(Scenario scenario); Task InsertScenarioAsync(Scenario scenario);
Task InsertIndicatorAsync(IndicatorBase indicatorBase); Task InsertIndicatorAsync(IndicatorBase indicatorBase);
Task<IEnumerable<Scenario>> GetScenariosAsync(); Task<IEnumerable<Scenario>> GetScenariosAsync();
Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user);
Task<IEnumerable<IndicatorBase>> GetIndicatorsAsync(); Task<IEnumerable<IndicatorBase>> GetIndicatorsAsync();
Task DeleteScenarioAsync(string name); Task DeleteScenarioAsync(string name);
Task DeleteIndicatorAsync(string name); Task DeleteIndicatorAsync(string name);

View File

@@ -35,52 +35,52 @@ public class AccountService : IAccountService
_userRepository = userRepository; _userRepository = userRepository;
} }
public async Task<Account> CreateAccount(User user, Account request) public async Task<Account> CreateAccount(User user, Account account)
{ {
var account = await _accountRepository.GetAccountByNameAsync(request.Name); var a = await _accountRepository.GetAccountByNameAsync(account.Name);
if (account != null) if (a != null)
{ {
throw new Exception($"Account {request.Name} alreary exist"); throw new Exception($"Account {account.Name} alreary exist");
} }
else else
{ {
request.User = user; account.User = user;
if (request.Exchange == TradingExchanges.Evm if (account.Exchange == TradingExchanges.Evm
&& request.Type == AccountType.Trader) && account.Type == AccountType.Trader)
{ {
var keys = _evmManager.GenerateAddress(); var keys = _evmManager.GenerateAddress();
request.Key = keys.Key; account.Key = keys.Key;
request.Secret = keys.Secret; account.Secret = keys.Secret;
} }
else if (request.Exchange == TradingExchanges.Evm else if (account.Exchange == TradingExchanges.Evm
&& request.Type == AccountType.Privy) && account.Type == AccountType.Privy)
{ {
if (string.IsNullOrEmpty(request.Key)) if (string.IsNullOrEmpty(account.Key))
{ {
// No key provided, create new privy embedded wallet. // No key provided, create new privy embedded wallet.
// TODO : Fix it to create privy wallet // TODO : Fix it to create privy wallet
var privyClient = await _evmManager.CreatePrivyWallet(); var privyClient = await _evmManager.CreatePrivyWallet();
request.Key = privyClient.Address; account.Key = privyClient.Address;
request.Secret = privyClient.Id; account.Secret = privyClient.Id;
} }
else else
{ {
request.Key = request.Key; // Address account.Key = account.Key; // Address
request.Secret = request.Secret; // Privy wallet id account.Secret = account.Secret; // Privy wallet id
} }
} }
else else
{ {
request.Key = request.Key; account.Key = account.Key;
request.Secret = request.Secret; account.Secret = account.Secret;
} }
await _accountRepository.InsertAccountAsync(request); await _accountRepository.InsertAccountAsync(account);
} }
return request; return account;
} }
public bool DeleteAccount(User user, string name) public bool DeleteAccount(User user, string name)

View File

@@ -114,4 +114,10 @@ public class AgentService : IAgentService
{ {
return await _agentSummaryRepository.GetAllAsync(); return await _agentSummaryRepository.GetAllAsync();
} }
public async Task<IEnumerable<string>> GetAllOnlineAgents()
{
var agentSummaries = await _agentSummaryRepository.GetAllAgentWithRunningBots();
return agentSummaries.Select(a => a.AgentName);
}
} }

View File

@@ -1,4 +1,4 @@
using Managing.Application.Abstractions; using Managing.Application.Abstractions.Services;
using Managing.Application.ManageBot.Commands; using Managing.Application.ManageBot.Commands;
using MediatR; using MediatR;
@@ -9,18 +9,17 @@ namespace Managing.Application.ManageBot
/// </summary> /// </summary>
public class GetOnlineAgentNamesCommandHandler : IRequestHandler<GetOnlineAgentNamesCommand, IEnumerable<string>> public class GetOnlineAgentNamesCommandHandler : IRequestHandler<GetOnlineAgentNamesCommand, IEnumerable<string>>
{ {
private readonly IBotService _botService; private readonly IAgentService _agentService;
public GetOnlineAgentNamesCommandHandler(IBotService botService) public GetOnlineAgentNamesCommandHandler(IAgentService agentService)
{ {
_botService = botService; _agentService = agentService;
} }
public async Task<IEnumerable<string>> Handle(GetOnlineAgentNamesCommand request, public async Task<IEnumerable<string>> Handle(GetOnlineAgentNamesCommand request,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var activeBots = await _botService.GetActiveBotsNamesAsync(); return await _agentService.GetAllOnlineAgents();
return activeBots;
} }
} }
} }

View File

@@ -116,8 +116,7 @@ namespace Managing.Application.Scenarios
public async Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user) public async Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user)
{ {
var scenarios = await _tradingService.GetScenariosAsync(); return await _tradingService.GetScenariosByUserAsync(user);
return scenarios.Where(s => s.User?.Name == user.Name);
} }
public async Task<Scenario> CreateScenarioForUser(User user, string name, List<string> strategies, public async Task<Scenario> CreateScenarioForUser(User user, string name, List<string> strategies,

View File

@@ -88,6 +88,11 @@ public class TradingService : ITradingService
return await _tradingRepository.GetScenariosAsync(); return await _tradingRepository.GetScenariosAsync();
} }
public async Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user)
{
return await _tradingRepository.GetScenariosByUserAsync(user);
}
public async Task<IEnumerable<IndicatorBase>> GetIndicatorsAsync() public async Task<IEnumerable<IndicatorBase>> GetIndicatorsAsync()
{ {
return await _tradingRepository.GetStrategiesAsync(); return await _tradingRepository.GetStrategiesAsync();

View File

@@ -95,15 +95,16 @@ public class UserService : IUserService
} }
else else
{ {
// First login // First login - create user first
user = new User user = new User
{ {
Name = name Name = name
}; };
await _userRepository.InsertUserAsync(user); // Save the user first
await _userRepository.SaveOrUpdateUserAsync(user);
// Create embedded account to authenticate user // Create embedded account to authenticate user after user is saved
var account = await _accountService.CreateAccount(user, new Account var account = await _accountService.CreateAccount(user, new Account
{ {
Name = $"{name}-embedded", Name = $"{name}-embedded",
@@ -116,9 +117,6 @@ public class UserService : IUserService
{ {
account account
}; };
// Update user with the new account
await _userRepository.UpdateUser(user);
} }
return user; return user;
@@ -165,8 +163,12 @@ public class UserService : IUserService
else else
{ {
user = await GetUserByName(user.Name); user = await GetUserByName(user.Name);
if (!string.IsNullOrEmpty(user.AgentName) && user.AgentName.Equals(agentName))
return user;
// Update the agent name on the provided user object
user.AgentName = agentName; user.AgentName = agentName;
await _userRepository.UpdateUser(user); await _userRepository.SaveOrUpdateUserAsync(user);
// Initialize the AgentGrain for this user // Initialize the AgentGrain for this user
try try
@@ -204,8 +206,12 @@ public class UserService : IUserService
} }
user = await GetUserByName(user.Name); user = await GetUserByName(user.Name);
if (!string.IsNullOrEmpty(user.AvatarUrl) && user.AvatarUrl.Equals(avatarUrl))
return user;
// Update the avatar URL on the provided user object
user.AvatarUrl = avatarUrl; user.AvatarUrl = avatarUrl;
await _userRepository.UpdateUser(user); await _userRepository.SaveOrUpdateUserAsync(user);
return user; return user;
} }
@@ -222,8 +228,12 @@ public class UserService : IUserService
} }
user = await GetUserByName(user.Name); user = await GetUserByName(user.Name);
if (!string.IsNullOrEmpty(user.TelegramChannel) && user.TelegramChannel.Equals(telegramChannel))
return user;
// Update the telegram channel on the provided user object
user.TelegramChannel = telegramChannel; user.TelegramChannel = telegramChannel;
await _userRepository.UpdateUser(user); await _userRepository.SaveOrUpdateUserAsync(user);
return user; return user;
} }
@@ -247,6 +257,4 @@ public class UserService : IUserService
{ {
return await _userRepository.GetAllUsersAsync(); return await _userRepository.GetAllUsersAsync();
} }
} }

View File

@@ -6,21 +6,15 @@ namespace Managing.Domain.Users;
[GenerateSerializer] [GenerateSerializer]
public class User public class User
{ {
[Id(0)] [Id(0)] public int Id { get; set; }
public int Id { get; set; }
[Id(1)] [Id(1)] public string Name { get; set; }
public string Name { get; set; }
[Id(2)] [Id(2)] public List<Account> Accounts { get; set; }
public List<Account> Accounts { get; set; }
[Id(3)] [Id(3)] public string AgentName { get; set; } = string.Empty;
public string AgentName { get; set; }
[Id(4)] [Id(4)] public string AvatarUrl { get; set; } = string.Empty;
public string AvatarUrl { get; set; }
[Id(5)] [Id(5)] public string TelegramChannel { get; set; } = string.Empty;
public string TelegramChannel { get; set; }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class UniqueUsername : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_Users_Name",
table: "Users",
column: "Name",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Users_Name",
table: "Users");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class AccountUpdate : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "UserId",
table: "Accounts",
type: "integer",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "integer",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "Key",
table: "Accounts",
type: "character varying(500)",
maxLength: 500,
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "character varying(500)",
oldMaxLength: 500,
oldNullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "UserId",
table: "Accounts",
type: "integer",
nullable: true,
oldClrType: typeof(int),
oldType: "integer");
migrationBuilder.AlterColumn<string>(
name: "Key",
table: "Accounts",
type: "character varying(500)",
maxLength: 500,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(500)",
oldMaxLength: 500);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,425 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Managing.Infrastructure.Databases.Migrations
{
/// <inheritdoc />
public partial class UserKeys : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Signals_Identifier_Date_UserName",
table: "Signals");
migrationBuilder.DropIndex(
name: "IX_Signals_UserName",
table: "Signals");
migrationBuilder.DropIndex(
name: "IX_Signals_UserName_Date",
table: "Signals");
migrationBuilder.DropIndex(
name: "IX_Scenarios_UserName",
table: "Scenarios");
migrationBuilder.DropIndex(
name: "IX_Scenarios_UserName_Name",
table: "Scenarios");
migrationBuilder.DropIndex(
name: "IX_Positions_UserName",
table: "Positions");
migrationBuilder.DropIndex(
name: "IX_Positions_UserName_Identifier",
table: "Positions");
migrationBuilder.DropIndex(
name: "IX_Indicators_UserName",
table: "Indicators");
migrationBuilder.DropIndex(
name: "IX_Indicators_UserName_Name",
table: "Indicators");
migrationBuilder.DropIndex(
name: "IX_BundleBacktestRequests_UserName",
table: "BundleBacktestRequests");
migrationBuilder.DropIndex(
name: "IX_BundleBacktestRequests_UserName_CreatedAt",
table: "BundleBacktestRequests");
migrationBuilder.DropIndex(
name: "IX_Backtests_UserName",
table: "Backtests");
migrationBuilder.DropIndex(
name: "IX_Backtests_UserName_Score",
table: "Backtests");
migrationBuilder.DropColumn(
name: "UserName",
table: "Signals");
migrationBuilder.DropColumn(
name: "UserName",
table: "Scenarios");
migrationBuilder.DropColumn(
name: "UserName",
table: "Positions");
migrationBuilder.DropColumn(
name: "UserName",
table: "Indicators");
migrationBuilder.DropColumn(
name: "UserName",
table: "BundleBacktestRequests");
migrationBuilder.DropColumn(
name: "UserName",
table: "Backtests");
migrationBuilder.AddColumn<int>(
name: "UserId",
table: "Signals",
type: "integer",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "UserId",
table: "Scenarios",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "UserId",
table: "Positions",
type: "integer",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "UserId",
table: "Indicators",
type: "integer",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "UserId",
table: "Backtests",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateIndex(
name: "IX_Signals_Identifier_Date_UserId",
table: "Signals",
columns: new[] { "Identifier", "Date", "UserId" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Signals_UserId",
table: "Signals",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_Signals_UserId_Date",
table: "Signals",
columns: new[] { "UserId", "Date" });
migrationBuilder.CreateIndex(
name: "IX_Scenarios_UserId",
table: "Scenarios",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_Scenarios_UserId_Name",
table: "Scenarios",
columns: new[] { "UserId", "Name" });
migrationBuilder.CreateIndex(
name: "IX_Positions_UserId",
table: "Positions",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_Positions_UserId_Identifier",
table: "Positions",
columns: new[] { "UserId", "Identifier" });
migrationBuilder.CreateIndex(
name: "IX_Indicators_UserId",
table: "Indicators",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_Indicators_UserId_Name",
table: "Indicators",
columns: new[] { "UserId", "Name" });
migrationBuilder.CreateIndex(
name: "IX_BundleBacktestRequests_UserId_CreatedAt",
table: "BundleBacktestRequests",
columns: new[] { "UserId", "CreatedAt" });
migrationBuilder.CreateIndex(
name: "IX_Backtests_UserId",
table: "Backtests",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_Backtests_UserId_Score",
table: "Backtests",
columns: new[] { "UserId", "Score" });
migrationBuilder.AddForeignKey(
name: "FK_Backtests_Users_UserId",
table: "Backtests",
column: "UserId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Indicators_Users_UserId",
table: "Indicators",
column: "UserId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Positions_Users_UserId",
table: "Positions",
column: "UserId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Scenarios_Users_UserId",
table: "Scenarios",
column: "UserId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_Signals_Users_UserId",
table: "Signals",
column: "UserId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Backtests_Users_UserId",
table: "Backtests");
migrationBuilder.DropForeignKey(
name: "FK_Indicators_Users_UserId",
table: "Indicators");
migrationBuilder.DropForeignKey(
name: "FK_Positions_Users_UserId",
table: "Positions");
migrationBuilder.DropForeignKey(
name: "FK_Scenarios_Users_UserId",
table: "Scenarios");
migrationBuilder.DropForeignKey(
name: "FK_Signals_Users_UserId",
table: "Signals");
migrationBuilder.DropIndex(
name: "IX_Signals_Identifier_Date_UserId",
table: "Signals");
migrationBuilder.DropIndex(
name: "IX_Signals_UserId",
table: "Signals");
migrationBuilder.DropIndex(
name: "IX_Signals_UserId_Date",
table: "Signals");
migrationBuilder.DropIndex(
name: "IX_Scenarios_UserId",
table: "Scenarios");
migrationBuilder.DropIndex(
name: "IX_Scenarios_UserId_Name",
table: "Scenarios");
migrationBuilder.DropIndex(
name: "IX_Positions_UserId",
table: "Positions");
migrationBuilder.DropIndex(
name: "IX_Positions_UserId_Identifier",
table: "Positions");
migrationBuilder.DropIndex(
name: "IX_Indicators_UserId",
table: "Indicators");
migrationBuilder.DropIndex(
name: "IX_Indicators_UserId_Name",
table: "Indicators");
migrationBuilder.DropIndex(
name: "IX_BundleBacktestRequests_UserId_CreatedAt",
table: "BundleBacktestRequests");
migrationBuilder.DropIndex(
name: "IX_Backtests_UserId",
table: "Backtests");
migrationBuilder.DropIndex(
name: "IX_Backtests_UserId_Score",
table: "Backtests");
migrationBuilder.DropColumn(
name: "UserId",
table: "Signals");
migrationBuilder.DropColumn(
name: "UserId",
table: "Scenarios");
migrationBuilder.DropColumn(
name: "UserId",
table: "Positions");
migrationBuilder.DropColumn(
name: "UserId",
table: "Indicators");
migrationBuilder.DropColumn(
name: "UserId",
table: "Backtests");
migrationBuilder.AddColumn<string>(
name: "UserName",
table: "Signals",
type: "character varying(255)",
maxLength: 255,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "UserName",
table: "Scenarios",
type: "character varying(255)",
maxLength: 255,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "UserName",
table: "Positions",
type: "character varying(255)",
maxLength: 255,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "UserName",
table: "Indicators",
type: "character varying(255)",
maxLength: 255,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "UserName",
table: "BundleBacktestRequests",
type: "character varying(255)",
maxLength: 255,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "UserName",
table: "Backtests",
type: "character varying(255)",
maxLength: 255,
nullable: false,
defaultValue: "");
migrationBuilder.CreateIndex(
name: "IX_Signals_Identifier_Date_UserName",
table: "Signals",
columns: new[] { "Identifier", "Date", "UserName" },
unique: true);
migrationBuilder.CreateIndex(
name: "IX_Signals_UserName",
table: "Signals",
column: "UserName");
migrationBuilder.CreateIndex(
name: "IX_Signals_UserName_Date",
table: "Signals",
columns: new[] { "UserName", "Date" });
migrationBuilder.CreateIndex(
name: "IX_Scenarios_UserName",
table: "Scenarios",
column: "UserName");
migrationBuilder.CreateIndex(
name: "IX_Scenarios_UserName_Name",
table: "Scenarios",
columns: new[] { "UserName", "Name" });
migrationBuilder.CreateIndex(
name: "IX_Positions_UserName",
table: "Positions",
column: "UserName");
migrationBuilder.CreateIndex(
name: "IX_Positions_UserName_Identifier",
table: "Positions",
columns: new[] { "UserName", "Identifier" });
migrationBuilder.CreateIndex(
name: "IX_Indicators_UserName",
table: "Indicators",
column: "UserName");
migrationBuilder.CreateIndex(
name: "IX_Indicators_UserName_Name",
table: "Indicators",
columns: new[] { "UserName", "Name" });
migrationBuilder.CreateIndex(
name: "IX_BundleBacktestRequests_UserName",
table: "BundleBacktestRequests",
column: "UserName");
migrationBuilder.CreateIndex(
name: "IX_BundleBacktestRequests_UserName_CreatedAt",
table: "BundleBacktestRequests",
columns: new[] { "UserName", "CreatedAt" });
migrationBuilder.CreateIndex(
name: "IX_Backtests_UserName",
table: "Backtests",
column: "UserName");
migrationBuilder.CreateIndex(
name: "IX_Backtests_UserName_Score",
table: "Backtests",
columns: new[] { "UserName", "Score" });
}
}
}

View File

@@ -35,6 +35,7 @@ namespace Managing.Infrastructure.Databases.Migrations
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("Key") b.Property<string>("Key")
.IsRequired()
.HasMaxLength(500) .HasMaxLength(500)
.HasColumnType("character varying(500)"); .HasColumnType("character varying(500)");
@@ -51,7 +52,7 @@ namespace Managing.Infrastructure.Databases.Migrations
.IsRequired() .IsRequired()
.HasColumnType("text"); .HasColumnType("text");
b.Property<int?>("UserId") b.Property<int>("UserId")
.HasColumnType("integer"); .HasColumnType("integer");
b.HasKey("Id"); b.HasKey("Id");
@@ -192,10 +193,8 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<DateTime>("UpdatedAt") b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<string>("UserName") b.Property<int>("UserId")
.IsRequired() .HasColumnType("integer");
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.Property<int>("WinRate") b.Property<int>("WinRate")
.HasColumnType("integer"); .HasColumnType("integer");
@@ -209,11 +208,11 @@ namespace Managing.Infrastructure.Databases.Migrations
b.HasIndex("Score"); b.HasIndex("Score");
b.HasIndex("UserName"); b.HasIndex("UserId");
b.HasIndex("RequestId", "Score"); b.HasIndex("RequestId", "Score");
b.HasIndex("UserName", "Score"); b.HasIndex("UserId", "Score");
b.ToTable("Backtests"); b.ToTable("Backtests");
}); });
@@ -347,11 +346,6 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<int?>("UserId") b.Property<int?>("UserId")
.HasColumnType("integer"); .HasColumnType("integer");
b.Property<string>("UserName")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("character varying(255)");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("RequestId") b.HasIndex("RequestId")
@@ -361,9 +355,7 @@ namespace Managing.Infrastructure.Databases.Migrations
b.HasIndex("UserId"); b.HasIndex("UserId");
b.HasIndex("UserName"); b.HasIndex("UserId", "CreatedAt");
b.HasIndex("UserName", "CreatedAt");
b.ToTable("BundleBacktestRequests"); b.ToTable("BundleBacktestRequests");
}); });
@@ -593,15 +585,14 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<DateTime>("UpdatedAt") b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<string>("UserName") b.Property<int?>("UserId")
.HasMaxLength(255) .HasColumnType("integer");
.HasColumnType("character varying(255)");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("UserName"); b.HasIndex("UserId");
b.HasIndex("UserName", "Name"); b.HasIndex("UserId", "Name");
b.ToTable("Indicators"); b.ToTable("Indicators");
}); });
@@ -715,9 +706,8 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<DateTime>("UpdatedAt") b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<string>("UserName") b.Property<int?>("UserId")
.HasMaxLength(255) .HasColumnType("integer");
.HasColumnType("character varying(255)");
b.HasKey("Identifier"); b.HasKey("Identifier");
@@ -736,9 +726,9 @@ namespace Managing.Infrastructure.Databases.Migrations
b.HasIndex("TakeProfit2TradeId"); b.HasIndex("TakeProfit2TradeId");
b.HasIndex("UserName"); b.HasIndex("UserId");
b.HasIndex("UserName", "Identifier"); b.HasIndex("UserId", "Identifier");
b.ToTable("Positions"); b.ToTable("Positions");
}); });
@@ -765,15 +755,14 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<DateTime>("UpdatedAt") b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<string>("UserName") b.Property<int>("UserId")
.HasMaxLength(255) .HasColumnType("integer");
.HasColumnType("character varying(255)");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("UserName"); b.HasIndex("UserId");
b.HasIndex("UserName", "Name"); b.HasIndex("UserId", "Name");
b.ToTable("Scenarios"); b.ToTable("Scenarios");
}); });
@@ -863,9 +852,8 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Property<DateTime>("UpdatedAt") b.Property<DateTime>("UpdatedAt")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");
b.Property<string>("UserName") b.Property<int?>("UserId")
.HasMaxLength(255) .HasColumnType("integer");
.HasColumnType("character varying(255)");
b.HasKey("Id"); b.HasKey("Id");
@@ -877,11 +865,11 @@ namespace Managing.Infrastructure.Databases.Migrations
b.HasIndex("Ticker"); b.HasIndex("Ticker");
b.HasIndex("UserName"); b.HasIndex("UserId");
b.HasIndex("UserName", "Date"); b.HasIndex("UserId", "Date");
b.HasIndex("Identifier", "Date", "UserName") b.HasIndex("Identifier", "Date", "UserId")
.IsUnique(); .IsUnique();
b.ToTable("Signals"); b.ToTable("Signals");
@@ -1213,6 +1201,9 @@ namespace Managing.Infrastructure.Databases.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("Name")
.IsUnique();
b.ToTable("Users"); b.ToTable("Users");
}); });
@@ -1256,7 +1247,8 @@ namespace Managing.Infrastructure.Databases.Migrations
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
.WithMany() .WithMany()
.HasForeignKey("UserId") .HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull)
.IsRequired();
b.Navigation("User"); b.Navigation("User");
}); });
@@ -1272,6 +1264,17 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Navigation("User"); b.Navigation("User");
}); });
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b =>
{
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b => modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b =>
{ {
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
@@ -1303,6 +1306,16 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Navigation("User"); b.Navigation("User");
}); });
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b =>
{
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("User");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b =>
{ {
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
@@ -1335,6 +1348,11 @@ namespace Managing.Infrastructure.Databases.Migrations
.HasForeignKey("TakeProfit2TradeId") .HasForeignKey("TakeProfit2TradeId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("OpenTrade"); b.Navigation("OpenTrade");
b.Navigation("StopLossTrade"); b.Navigation("StopLossTrade");
@@ -1342,6 +1360,19 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Navigation("TakeProfit1Trade"); b.Navigation("TakeProfit1Trade");
b.Navigation("TakeProfit2Trade"); b.Navigation("TakeProfit2Trade");
b.Navigation("User");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b =>
{
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.IsRequired();
b.Navigation("User");
}); });
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b =>
@@ -1363,6 +1394,16 @@ namespace Managing.Infrastructure.Databases.Migrations
b.Navigation("Scenario"); b.Navigation("Scenario");
}); });
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b =>
{
b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.SetNull);
b.Navigation("User");
});
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b =>
{ {
b.Navigation("ScenarioIndicators"); b.Navigation("ScenarioIndicators");

View File

@@ -229,4 +229,14 @@ public class AgentSummaryRepository : IAgentSummaryRepository
User = PostgreSqlMappers.Map(entity.User) User = PostgreSqlMappers.Map(entity.User)
}; };
} }
public async Task<IEnumerable<AgentSummary>> GetAllAgentWithRunningBots()
{
var agentSummaries = await _context.AgentSummaries
.Include(a => a.User)
.Where(a => _context.Bots.Any(b => b.UserId == a.UserId && b.Status == BotStatus.Up))
.ToListAsync();
return agentSummaries.Select(MapToDomain);
}
} }

View File

@@ -1,16 +1,19 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities; namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("Accounts")]
public class AccountEntity public class AccountEntity
{ {
public int Id { get; set; } [Key] public int Id { get; set; }
public string Name { get; set; } [Required] public string Name { get; set; }
public TradingExchanges Exchange { get; set; } [Required] public TradingExchanges Exchange { get; set; }
public AccountType Type { get; set; } [Required] public AccountType Type { get; set; }
public string? Key { get; set; } [Required] public string? Key { get; set; }
public string? Secret { get; set; } public string? Secret { get; set; }
public int? UserId { get; set; } [Required] public int UserId { get; set; }
// Navigation properties // Navigation properties
public UserEntity? User { get; set; } public UserEntity? User { get; set; }

View File

@@ -56,8 +56,10 @@ public class BacktestEntity
public string MoneyManagementJson { get; set; } = string.Empty; public string MoneyManagementJson { get; set; } = string.Empty;
[Required] [Required]
[MaxLength(255)] public int UserId { get; set; }
public string UserName { get; set; } = string.Empty;
// Navigation property
public UserEntity? User { get; set; }
[Column(TypeName = "jsonb")] [Column(TypeName = "jsonb")]
public string? StatisticsJson { get; set; } public string? StatisticsJson { get; set; }

View File

@@ -15,7 +15,7 @@ public class BotEntity
public int UserId { get; set; } public int UserId { get; set; }
[Required] [ForeignKey("UserId")] public required UserEntity User { get; set; } [ForeignKey("UserId")] public UserEntity User { get; set; }
public BotStatus Status { get; set; } public BotStatus Status { get; set; }
public DateTime CreateDate { get; set; } public DateTime CreateDate { get; set; }

View File

@@ -15,10 +15,6 @@ public class BundleBacktestRequestEntity
[MaxLength(255)] [MaxLength(255)]
public string RequestId { get; set; } = string.Empty; public string RequestId { get; set; } = string.Empty;
[Required]
[MaxLength(255)]
public string UserName { get; set; } = string.Empty;
// Foreign key to User entity // Foreign key to User entity
public int? UserId { get; set; } public int? UserId { get; set; }

View File

@@ -1,7 +1,9 @@
using System.ComponentModel.DataAnnotations.Schema;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities; namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("GeneticRequests")]
public class GeneticRequestEntity public class GeneticRequestEntity
{ {
public int Id { get; set; } public int Id { get; set; }

View File

@@ -28,12 +28,14 @@ public class IndicatorEntity
public int? SmoothPeriods { get; set; } public int? SmoothPeriods { get; set; }
public int? CyclePeriods { get; set; } public int? CyclePeriods { get; set; }
[MaxLength(255)] public int? UserId { get; set; }
public string? UserName { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
// Navigation properties
public UserEntity? User { get; set; }
// Navigation property for the many-to-many relationship with scenarios // Navigation property for the many-to-many relationship with scenarios
public virtual ICollection<ScenarioIndicatorEntity> ScenarioIndicators { get; set; } = new List<ScenarioIndicatorEntity>(); public virtual ICollection<ScenarioIndicatorEntity> ScenarioIndicators { get; set; } = new List<ScenarioIndicatorEntity>();
} }

View File

@@ -22,7 +22,7 @@ public class PositionEntity
[MaxLength(255)] public string AccountName { get; set; } [MaxLength(255)] public string AccountName { get; set; }
[MaxLength(255)] public string? UserName { get; set; } public int? UserId { get; set; }
// Foreign keys to trades // Foreign keys to trades
public int? OpenTradeId { get; set; } public int? OpenTradeId { get; set; }
@@ -37,6 +37,8 @@ public class PositionEntity
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
// Navigation properties // Navigation properties
public UserEntity? User { get; set; }
[ForeignKey("OpenTradeId")] public virtual TradeEntity? OpenTrade { get; set; } [ForeignKey("OpenTradeId")] public virtual TradeEntity? OpenTrade { get; set; }
[ForeignKey("StopLossTradeId")] public virtual TradeEntity? StopLossTrade { get; set; } [ForeignKey("StopLossTradeId")] public virtual TradeEntity? StopLossTrade { get; set; }

View File

@@ -15,12 +15,15 @@ public class ScenarioEntity
public int LoopbackPeriod { get; set; } public int LoopbackPeriod { get; set; }
[MaxLength(255)] [Required]
public string? UserName { get; set; } public int UserId { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
// Navigation properties
public UserEntity? User { get; set; }
// Navigation property for the many-to-many relationship with indicators // Navigation property for the many-to-many relationship with indicators
public virtual ICollection<ScenarioIndicatorEntity> ScenarioIndicators { get; set; } = new List<ScenarioIndicatorEntity>(); public virtual ICollection<ScenarioIndicatorEntity> ScenarioIndicators { get; set; } = new List<ScenarioIndicatorEntity>();
} }

View File

@@ -26,8 +26,10 @@ public class SignalEntity
[MaxLength(255)] [MaxLength(255)]
public string IndicatorName { get; set; } public string IndicatorName { get; set; }
[MaxLength(255)] public int? UserId { get; set; }
public string? UserName { get; set; }
// Navigation property
public UserEntity? User { get; set; }
// Candle data stored as JSON // Candle data stored as JSON
[Column(TypeName = "text")] [Column(TypeName = "text")]

View File

@@ -1,9 +1,11 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace Managing.Infrastructure.Databases.PostgreSql.Entities; namespace Managing.Infrastructure.Databases.PostgreSql.Entities;
[Table("Users")] [Table("Users")]
[Index(nameof(Name), IsUnique = true)]
public class UserEntity public class UserEntity
{ {
[Key] public int Id { get; set; } [Key] public int Id { get; set; }

View File

@@ -130,7 +130,7 @@ public class ManagingDbContext : DbContext
entity.HasKey(e => e.Id); entity.HasKey(e => e.Id);
entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255); entity.Property(e => e.Identifier).IsRequired().HasMaxLength(255);
entity.Property(e => e.RequestId).IsRequired().HasMaxLength(255); entity.Property(e => e.RequestId).IsRequired().HasMaxLength(255);
entity.Property(e => e.UserName).IsRequired().HasMaxLength(255); entity.Property(e => e.UserId).IsRequired();
entity.Property(e => e.FinalPnl).HasColumnType("decimal(18,8)"); entity.Property(e => e.FinalPnl).HasColumnType("decimal(18,8)");
entity.Property(e => e.GrowthPercentage).HasColumnType("decimal(18,8)"); entity.Property(e => e.GrowthPercentage).HasColumnType("decimal(18,8)");
entity.Property(e => e.HodlPercentage).HasColumnType("decimal(18,8)"); entity.Property(e => e.HodlPercentage).HasColumnType("decimal(18,8)");
@@ -143,14 +143,20 @@ public class ManagingDbContext : DbContext
entity.Property(e => e.ScoreMessage).HasMaxLength(1000); entity.Property(e => e.ScoreMessage).HasMaxLength(1000);
entity.Property(e => e.Metadata).HasColumnType("text"); entity.Property(e => e.Metadata).HasColumnType("text");
// Configure relationship with User
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.SetNull);
// Create indexes for common queries // Create indexes for common queries
entity.HasIndex(e => e.Identifier).IsUnique(); entity.HasIndex(e => e.Identifier).IsUnique();
entity.HasIndex(e => e.RequestId); entity.HasIndex(e => e.RequestId);
entity.HasIndex(e => e.UserName); entity.HasIndex(e => e.UserId);
entity.HasIndex(e => e.Score); entity.HasIndex(e => e.Score);
// Composite indexes for efficient pagination and filtering // Composite indexes for efficient pagination and filtering
entity.HasIndex(e => new { e.UserName, e.Score }); entity.HasIndex(e => new { e.UserId, e.Score });
entity.HasIndex(e => new { e.RequestId, e.Score }); entity.HasIndex(e => new { e.RequestId, e.Score });
}); });
@@ -159,7 +165,7 @@ public class ManagingDbContext : DbContext
{ {
entity.HasKey(e => e.Id); entity.HasKey(e => e.Id);
entity.Property(e => e.RequestId).IsRequired().HasMaxLength(255); entity.Property(e => e.RequestId).IsRequired().HasMaxLength(255);
entity.Property(e => e.UserName).IsRequired().HasMaxLength(255); entity.Property(e => e.UserId);
entity.Property(e => e.Name).IsRequired().HasMaxLength(255); entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
entity.Property(e => e.Status) entity.Property(e => e.Status)
.IsRequired() .IsRequired()
@@ -178,11 +184,11 @@ public class ManagingDbContext : DbContext
// Create indexes for common queries // Create indexes for common queries
entity.HasIndex(e => e.RequestId).IsUnique(); entity.HasIndex(e => e.RequestId).IsUnique();
entity.HasIndex(e => e.UserName); entity.HasIndex(e => e.UserId);
entity.HasIndex(e => e.Status); entity.HasIndex(e => e.Status);
// Composite index for user queries ordered by creation date // Composite index for user queries ordered by creation date
entity.HasIndex(e => new { e.UserName, e.CreatedAt }); entity.HasIndex(e => new { e.UserId, e.CreatedAt });
}); });
// Configure Scenario entity // Configure Scenario entity
@@ -190,13 +196,19 @@ public class ManagingDbContext : DbContext
{ {
entity.HasKey(e => e.Id); entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(255); entity.Property(e => e.Name).IsRequired().HasMaxLength(255);
entity.Property(e => e.UserName).HasMaxLength(255); entity.Property(e => e.UserId).IsRequired();
// Configure relationship with User
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.SetNull);
// Create indexes // Create indexes
entity.HasIndex(e => e.UserName); entity.HasIndex(e => e.UserId);
// Composite index for user scenarios // Composite index for user scenarios
entity.HasIndex(e => new { e.UserName, e.Name }); entity.HasIndex(e => new { e.UserId, e.Name });
}); });
// Configure Indicator entity // Configure Indicator entity
@@ -207,13 +219,19 @@ public class ManagingDbContext : DbContext
entity.Property(e => e.Type).IsRequired().HasConversion<string>(); entity.Property(e => e.Type).IsRequired().HasConversion<string>();
entity.Property(e => e.Timeframe).IsRequired().HasConversion<string>(); entity.Property(e => e.Timeframe).IsRequired().HasConversion<string>();
entity.Property(e => e.SignalType).IsRequired().HasConversion<string>(); entity.Property(e => e.SignalType).IsRequired().HasConversion<string>();
entity.Property(e => e.UserName).HasMaxLength(255); entity.Property(e => e.UserId);
// Configure relationship with User
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.SetNull);
// Create indexes // Create indexes
entity.HasIndex(e => e.UserName); entity.HasIndex(e => e.UserId);
// Composite index for user indicators // Composite index for user indicators
entity.HasIndex(e => new { e.UserName, e.Name }); entity.HasIndex(e => new { e.UserId, e.Name });
}); });
// Configure ScenarioIndicator junction table // Configure ScenarioIndicator junction table
@@ -249,19 +267,25 @@ public class ManagingDbContext : DbContext
entity.Property(e => e.Type).IsRequired().HasConversion<string>(); entity.Property(e => e.Type).IsRequired().HasConversion<string>();
entity.Property(e => e.SignalType).IsRequired().HasConversion<string>(); entity.Property(e => e.SignalType).IsRequired().HasConversion<string>();
entity.Property(e => e.IndicatorName).IsRequired().HasMaxLength(255); entity.Property(e => e.IndicatorName).IsRequired().HasMaxLength(255);
entity.Property(e => e.UserName).HasMaxLength(255); entity.Property(e => e.UserId);
entity.Property(e => e.CandleJson).HasColumnType("text"); entity.Property(e => e.CandleJson).HasColumnType("text");
// Configure relationship with User
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.SetNull);
// Create indexes // Create indexes
entity.HasIndex(e => e.Identifier); entity.HasIndex(e => e.Identifier);
entity.HasIndex(e => e.UserName); entity.HasIndex(e => e.UserId);
entity.HasIndex(e => e.Date); entity.HasIndex(e => e.Date);
entity.HasIndex(e => e.Ticker); entity.HasIndex(e => e.Ticker);
entity.HasIndex(e => e.Status); entity.HasIndex(e => e.Status);
// Composite indexes for common queries // Composite indexes for common queries
entity.HasIndex(e => new { e.UserName, e.Date }); entity.HasIndex(e => new { e.UserId, e.Date });
entity.HasIndex(e => new { e.Identifier, e.Date, e.UserName }).IsUnique(); entity.HasIndex(e => new { e.Identifier, e.Date, e.UserId }).IsUnique();
}); });
// Configure Position entity // Configure Position entity
@@ -275,9 +299,15 @@ public class ManagingDbContext : DbContext
entity.Property(e => e.Initiator).IsRequired().HasConversion<string>(); entity.Property(e => e.Initiator).IsRequired().HasConversion<string>();
entity.Property(e => e.SignalIdentifier).IsRequired().HasMaxLength(255); entity.Property(e => e.SignalIdentifier).IsRequired().HasMaxLength(255);
entity.Property(e => e.AccountName).IsRequired().HasMaxLength(255); entity.Property(e => e.AccountName).IsRequired().HasMaxLength(255);
entity.Property(e => e.UserName).HasMaxLength(255); entity.Property(e => e.UserId);
entity.Property(e => e.MoneyManagementJson).HasColumnType("text"); entity.Property(e => e.MoneyManagementJson).HasColumnType("text");
// Configure relationship with User
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.SetNull);
// Configure relationships with trades // Configure relationships with trades
entity.HasOne(e => e.OpenTrade) entity.HasOne(e => e.OpenTrade)
.WithMany() .WithMany()
@@ -301,12 +331,12 @@ public class ManagingDbContext : DbContext
// Create indexes // Create indexes
entity.HasIndex(e => e.Identifier).IsUnique(); entity.HasIndex(e => e.Identifier).IsUnique();
entity.HasIndex(e => e.UserName); entity.HasIndex(e => e.UserId);
entity.HasIndex(e => e.Status); entity.HasIndex(e => e.Status);
entity.HasIndex(e => e.Date); entity.HasIndex(e => e.Date);
// Composite indexes // Composite indexes
entity.HasIndex(e => new { e.UserName, e.Identifier }); entity.HasIndex(e => new { e.UserId, e.Identifier });
}); });
// Configure Trade entity // Configure Trade entity

View File

@@ -131,28 +131,12 @@ public class PostgreSqlAccountRepository : IAccountRepository
{ {
var accountEntity = PostgreSqlMappers.Map(account); var accountEntity = PostgreSqlMappers.Map(account);
// Handle User relationship - check if user exists or create new one // Handle User relationship - user should always have an ID at this point
if (account.User != null) if (account.User == null)
{ {
var existingUser = await _context.Users throw new Exception($"Cannot create account {account.Name} without a user");
.AsTracking() // Explicitly enable tracking for this operation
.FirstOrDefaultAsync(u => u.Name == account.User.Name)
.ConfigureAwait(false);
if (existingUser != null)
{
accountEntity.UserId = existingUser.Id;
accountEntity.User = null; // Prevent EF from trying to insert duplicate user
}
else
{
// Let EF handle the new user creation
accountEntity.UserId = null;
}
} }
// Balances are not stored in PostgreSQL, they remain in domain logic only
_context.Accounts.Add(accountEntity); _context.Accounts.Add(accountEntity);
await _context.SaveChangesAsync().ConfigureAwait(false); await _context.SaveChangesAsync().ConfigureAwait(false);
} }

View File

@@ -72,7 +72,8 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
{ {
var entities = _context.Backtests var entities = _context.Backtests
.AsNoTracking() .AsNoTracking()
.Where(b => b.UserName == user.Name) .Include(b => b.User)
.Where(b => b.UserId == user.Id)
.ToList(); .ToList();
return entities.Select(PostgreSqlMappers.Map); return entities.Select(PostgreSqlMappers.Map);
@@ -82,7 +83,8 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
{ {
var entities = await _context.Backtests var entities = await _context.Backtests
.AsNoTracking() .AsNoTracking()
.Where(b => b.UserName == user.Name) .Include(b => b.User)
.Where(b => b.UserId == user.Id)
.ToListAsync() .ToListAsync()
.ConfigureAwait(false); .ConfigureAwait(false);
@@ -259,7 +261,8 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
{ {
var entity = _context.Backtests var entity = _context.Backtests
.AsNoTracking() .AsNoTracking()
.FirstOrDefault(b => b.Identifier == id && b.UserName == user.Name); .Include(b => b.User)
.FirstOrDefault(b => b.Identifier == id && b.UserId == user.Id);
return entity != null ? PostgreSqlMappers.Map(entity) : null; return entity != null ? PostgreSqlMappers.Map(entity) : null;
} }
@@ -268,7 +271,8 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
{ {
var entity = await _context.Backtests var entity = await _context.Backtests
.AsNoTracking() .AsNoTracking()
.FirstOrDefaultAsync(b => b.Identifier == id && b.UserName == user.Name) .Include(b => b.User)
.FirstOrDefaultAsync(b => b.Identifier == id && b.UserId == user.Id)
.ConfigureAwait(false); .ConfigureAwait(false);
return entity != null ? PostgreSqlMappers.Map(entity) : null; return entity != null ? PostgreSqlMappers.Map(entity) : null;
@@ -278,7 +282,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
{ {
var entity = _context.Backtests var entity = _context.Backtests
.AsTracking() .AsTracking()
.FirstOrDefault(b => b.Identifier == id && b.UserName == user.Name); .FirstOrDefault(b => b.Identifier == id && b.UserId == user.Id);
if (entity != null) if (entity != null)
{ {
@@ -291,7 +295,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
{ {
var entity = await _context.Backtests var entity = await _context.Backtests
.AsTracking() .AsTracking()
.FirstOrDefaultAsync(b => b.Identifier == id && b.UserName == user.Name) .FirstOrDefaultAsync(b => b.Identifier == id && b.UserId == user.Id)
.ConfigureAwait(false); .ConfigureAwait(false);
if (entity != null) if (entity != null)
@@ -305,7 +309,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
{ {
var entities = _context.Backtests var entities = _context.Backtests
.AsTracking() .AsTracking()
.Where(b => b.UserName == user.Name && ids.Contains(b.Identifier)) .Where(b => b.UserId == user.Id && ids.Contains(b.Identifier))
.ToList(); .ToList();
if (entities.Any()) if (entities.Any())
@@ -319,7 +323,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
{ {
var entities = await _context.Backtests var entities = await _context.Backtests
.AsTracking() .AsTracking()
.Where(b => b.UserName == user.Name && ids.Contains(b.Identifier)) .Where(b => b.UserId == user.Id && ids.Contains(b.Identifier))
.ToListAsync() .ToListAsync()
.ConfigureAwait(false); .ConfigureAwait(false);
@@ -334,7 +338,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
{ {
var entities = _context.Backtests var entities = _context.Backtests
.AsTracking() .AsTracking()
.Where(b => b.UserName == user.Name) .Where(b => b.UserId == user.Id)
.ToList(); .ToList();
if (entities.Any()) if (entities.Any())
@@ -380,7 +384,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
var baseQuery = _context.Backtests var baseQuery = _context.Backtests
.AsNoTracking() .AsNoTracking()
.Where(b => b.UserName == user.Name); .Where(b => b.UserId == user.Id);
var afterQueryMs = stopwatch.ElapsedMilliseconds; var afterQueryMs = stopwatch.ElapsedMilliseconds;
var totalCount = baseQuery.Count(); var totalCount = baseQuery.Count();
@@ -452,7 +456,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
var baseQuery = _context.Backtests var baseQuery = _context.Backtests
.AsNoTracking() .AsNoTracking()
.Where(b => b.UserName == user.Name); .Where(b => b.UserId == user.Id);
var afterQueryMs = stopwatch.ElapsedMilliseconds; var afterQueryMs = stopwatch.ElapsedMilliseconds;
var totalCount = await baseQuery.CountAsync().ConfigureAwait(false); var totalCount = await baseQuery.CountAsync().ConfigureAwait(false);
@@ -556,7 +560,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
var entities = _context.BundleBacktestRequests var entities = _context.BundleBacktestRequests
.AsNoTracking() .AsNoTracking()
.Include(b => b.User) .Include(b => b.User)
.Where(b => b.UserName == user.Name) .Where(b => b.UserId == user.Id)
.OrderByDescending(b => b.CreatedAt) .OrderByDescending(b => b.CreatedAt)
.ToList(); .ToList();
@@ -568,7 +572,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
var entities = await _context.BundleBacktestRequests var entities = await _context.BundleBacktestRequests
.AsNoTracking() .AsNoTracking()
.Include(b => b.User) .Include(b => b.User)
.Where(b => b.UserName == user.Name) .Where(b => b.UserId == user.Id)
.OrderByDescending(b => b.CreatedAt) .OrderByDescending(b => b.CreatedAt)
.ToListAsync() .ToListAsync()
.ConfigureAwait(false); .ConfigureAwait(false);
@@ -581,7 +585,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
var entity = _context.BundleBacktestRequests var entity = _context.BundleBacktestRequests
.AsNoTracking() .AsNoTracking()
.Include(b => b.User) .Include(b => b.User)
.FirstOrDefault(b => b.RequestId == id && b.UserName == user.Name); .FirstOrDefault(b => b.RequestId == id && b.UserId == user.Id);
return entity != null ? PostgreSqlMappers.Map(entity) : null; return entity != null ? PostgreSqlMappers.Map(entity) : null;
} }
@@ -591,7 +595,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
var entity = await _context.BundleBacktestRequests var entity = await _context.BundleBacktestRequests
.AsNoTracking() .AsNoTracking()
.Include(b => b.User) .Include(b => b.User)
.FirstOrDefaultAsync(b => b.RequestId == id && b.UserName == user.Name) .FirstOrDefaultAsync(b => b.RequestId == id && b.UserId == user.Id)
.ConfigureAwait(false); .ConfigureAwait(false);
return entity != null ? PostgreSqlMappers.Map(entity) : null; return entity != null ? PostgreSqlMappers.Map(entity) : null;
@@ -682,7 +686,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
{ {
var entity = _context.BundleBacktestRequests var entity = _context.BundleBacktestRequests
.AsTracking() .AsTracking()
.FirstOrDefault(b => b.RequestId == id && b.UserName == user.Name); .FirstOrDefault(b => b.RequestId == id && b.UserId == user.Id);
if (entity != null) if (entity != null)
{ {
@@ -695,7 +699,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
{ {
var entity = await _context.BundleBacktestRequests var entity = await _context.BundleBacktestRequests
.AsTracking() .AsTracking()
.FirstOrDefaultAsync(b => b.RequestId == id && b.UserName == user.Name) .FirstOrDefaultAsync(b => b.RequestId == id && b.UserId == user.Id)
.ConfigureAwait(false); .ConfigureAwait(false);
if (entity != null) if (entity != null)

View File

@@ -49,7 +49,7 @@ public static class PostgreSqlMappers
Type = account.Type, Type = account.Type,
Key = account.Key, Key = account.Key,
Secret = account.Secret, Secret = account.Secret,
User = account.User != null ? Map(account.User) : null UserId = account.User.Id,
}; };
} }
@@ -88,8 +88,7 @@ public static class PostgreSqlMappers
StopLoss = moneyManagement.StopLoss, StopLoss = moneyManagement.StopLoss,
TakeProfit = moneyManagement.TakeProfit, TakeProfit = moneyManagement.TakeProfit,
Leverage = moneyManagement.Leverage, Leverage = moneyManagement.Leverage,
UserName = moneyManagement.User?.Name, UserId = moneyManagement.User?.Id ?? 0
User = moneyManagement.User != null ? Map(moneyManagement.User) : null
}; };
} }
@@ -204,7 +203,7 @@ public static class PostgreSqlMappers
var entity = new GeneticRequestEntity var entity = new GeneticRequestEntity
{ {
RequestId = geneticRequest.RequestId, RequestId = geneticRequest.RequestId,
User = geneticRequest.User != null ? Map(geneticRequest.User) : null, UserId = geneticRequest.User?.Id ?? 0,
CreatedAt = geneticRequest.CreatedAt, CreatedAt = geneticRequest.CreatedAt,
CompletedAt = geneticRequest.CompletedAt, CompletedAt = geneticRequest.CompletedAt,
UpdatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow,
@@ -268,7 +267,8 @@ public static class PostgreSqlMappers
var config = JsonConvert.DeserializeObject<TradingBotConfig>(entity.ConfigJson); var config = JsonConvert.DeserializeObject<TradingBotConfig>(entity.ConfigJson);
var positionsList = JsonConvert.DeserializeObject<List<Position>>(entity.PositionsJson) ?? new List<Position>(); var positionsList = JsonConvert.DeserializeObject<List<Position>>(entity.PositionsJson) ?? new List<Position>();
var positions = positionsList.ToDictionary(p => p.Identifier, p => p); var positions = positionsList.ToDictionary(p => p.Identifier, p => p);
var signalsList = JsonConvert.DeserializeObject<List<LightSignal>>(entity.SignalsJson) ?? new List<LightSignal>(); var signalsList = JsonConvert.DeserializeObject<List<LightSignal>>(entity.SignalsJson) ??
new List<LightSignal>();
var signals = signalsList.ToDictionary(s => s.Identifier, s => s); var signals = signalsList.ToDictionary(s => s.Identifier, s => s);
var statistics = !string.IsNullOrEmpty(entity.StatisticsJson) var statistics = !string.IsNullOrEmpty(entity.StatisticsJson)
? JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson) ? JsonConvert.DeserializeObject<PerformanceMetrics>(entity.StatisticsJson)
@@ -283,7 +283,7 @@ public static class PostgreSqlMappers
HodlPercentage = entity.HodlPercentage, HodlPercentage = entity.HodlPercentage,
StartDate = entity.StartDate, StartDate = entity.StartDate,
EndDate = entity.EndDate, EndDate = entity.EndDate,
User = new User { Name = entity.UserName }, User = entity.User != null ? Map(entity.User) : null,
Statistics = statistics, Statistics = statistics,
Fees = entity.Fees, Fees = entity.Fees,
Score = entity.Score, Score = entity.Score,
@@ -313,7 +313,7 @@ public static class PostgreSqlMappers
StartDate = backtest.StartDate, StartDate = backtest.StartDate,
EndDate = backtest.EndDate, EndDate = backtest.EndDate,
MoneyManagementJson = JsonConvert.SerializeObject(backtest.Config?.MoneyManagement), MoneyManagementJson = JsonConvert.SerializeObject(backtest.Config?.MoneyManagement),
UserName = backtest.User?.Name ?? string.Empty, UserId = backtest.User?.Id ?? 0,
StatisticsJson = backtest.Statistics != null ? JsonConvert.SerializeObject(backtest.Statistics) : null, StatisticsJson = backtest.Statistics != null ? JsonConvert.SerializeObject(backtest.Statistics) : null,
Fees = backtest.Fees, Fees = backtest.Fees,
Score = backtest.Score, Score = backtest.Score,
@@ -339,7 +339,7 @@ public static class PostgreSqlMappers
var bundleRequest = new BundleBacktestRequest(entity.RequestId) var bundleRequest = new BundleBacktestRequest(entity.RequestId)
{ {
User = entity.User != null ? Map(entity.User) : new User { Name = entity.UserName }, User = entity.User != null ? Map(entity.User) : null,
CreatedAt = entity.CreatedAt, CreatedAt = entity.CreatedAt,
CompletedAt = entity.CompletedAt, CompletedAt = entity.CompletedAt,
Status = entity.Status, Status = entity.Status,
@@ -378,8 +378,7 @@ public static class PostgreSqlMappers
var entity = new BundleBacktestRequestEntity var entity = new BundleBacktestRequestEntity
{ {
RequestId = bundleRequest.RequestId, RequestId = bundleRequest.RequestId,
UserName = bundleRequest.User?.Name ?? string.Empty, UserId = bundleRequest.User?.Id ?? 0,
UserId = null, // Will be set by the repository when saving
CreatedAt = bundleRequest.CreatedAt, CreatedAt = bundleRequest.CreatedAt,
CompletedAt = bundleRequest.CompletedAt, CompletedAt = bundleRequest.CompletedAt,
Status = bundleRequest.Status, Status = bundleRequest.Status,
@@ -431,7 +430,7 @@ public static class PostgreSqlMappers
return new Scenario(entity.Name, entity.LoopbackPeriod) return new Scenario(entity.Name, entity.LoopbackPeriod)
{ {
User = entity.UserName != null ? new User { Name = entity.UserName } : null, User = entity.User != null ? Map(entity.User) : null,
Indicators = new List<IndicatorBase>() // Will be populated separately when needed Indicators = new List<IndicatorBase>() // Will be populated separately when needed
}; };
} }
@@ -444,7 +443,7 @@ public static class PostgreSqlMappers
{ {
Name = scenario.Name, Name = scenario.Name,
LoopbackPeriod = scenario.LoopbackPeriod ?? 1, LoopbackPeriod = scenario.LoopbackPeriod ?? 1,
UserName = scenario.User?.Name UserId = scenario.User?.Id ?? 0
}; };
} }
@@ -465,7 +464,7 @@ public static class PostgreSqlMappers
SmoothPeriods = entity.SmoothPeriods, SmoothPeriods = entity.SmoothPeriods,
StochPeriods = entity.StochPeriods, StochPeriods = entity.StochPeriods,
CyclePeriods = entity.CyclePeriods, CyclePeriods = entity.CyclePeriods,
User = entity.UserName != null ? new User { Name = entity.UserName } : null User = entity.User != null ? Map(entity.User) : null
}; };
} }
@@ -488,7 +487,7 @@ public static class PostgreSqlMappers
SmoothPeriods = indicatorBase.SmoothPeriods, SmoothPeriods = indicatorBase.SmoothPeriods,
StochPeriods = indicatorBase.StochPeriods, StochPeriods = indicatorBase.StochPeriods,
CyclePeriods = indicatorBase.CyclePeriods, CyclePeriods = indicatorBase.CyclePeriods,
UserName = indicatorBase.User?.Name UserId = indicatorBase.User?.Id ?? 0
}; };
} }
@@ -511,7 +510,7 @@ public static class PostgreSqlMappers
entity.Type, entity.Type,
entity.SignalType, entity.SignalType,
entity.IndicatorName, entity.IndicatorName,
entity.UserName != null ? new User { Name = entity.UserName } : null) entity.User != null ? Map(entity.User) : null)
{ {
Status = entity.Status Status = entity.Status
}; };
@@ -533,7 +532,7 @@ public static class PostgreSqlMappers
Type = signal.IndicatorType, Type = signal.IndicatorType,
SignalType = signal.SignalType, SignalType = signal.SignalType,
IndicatorName = signal.IndicatorName, IndicatorName = signal.IndicatorName,
UserName = signal.User?.Name, UserId = signal.User?.Id ?? 0,
CandleJson = signal.Candle != null ? JsonConvert.SerializeObject(signal.Candle) : null CandleJson = signal.Candle != null ? JsonConvert.SerializeObject(signal.Candle) : null
}; };
} }
@@ -559,7 +558,7 @@ public static class PostgreSqlMappers
moneyManagement, moneyManagement,
entity.Initiator, entity.Initiator,
entity.Date, entity.Date,
entity.UserName != null ? new User { Name = entity.UserName } : null) entity.User != null ? Map(entity.User) : null)
{ {
Status = entity.Status, Status = entity.Status,
SignalIdentifier = entity.SignalIdentifier SignalIdentifier = entity.SignalIdentifier
@@ -596,7 +595,7 @@ public static class PostgreSqlMappers
Initiator = position.Initiator, Initiator = position.Initiator,
SignalIdentifier = position.SignalIdentifier, SignalIdentifier = position.SignalIdentifier,
AccountName = position.AccountName, AccountName = position.AccountName,
UserName = position.User?.Name, UserId = position.User?.Id ?? 0,
MoneyManagementJson = position.MoneyManagement != null MoneyManagementJson = position.MoneyManagement != null
? JsonConvert.SerializeObject(position.MoneyManagement) ? JsonConvert.SerializeObject(position.MoneyManagement)
: null : null
@@ -703,7 +702,6 @@ public static class PostgreSqlMappers
{ {
Identifier = bot.Identifier, Identifier = bot.Identifier,
UserId = bot.User.Id, UserId = bot.User.Id,
User = bot.User != null ? Map(bot.User) : null,
Status = bot.Status, Status = bot.Status,
CreateDate = bot.CreateDate, CreateDate = bot.CreateDate,
Name = bot.Name, Name = bot.Name,

View File

@@ -40,6 +40,7 @@ public class PostgreSqlTradingRepository : ITradingRepository
{ {
var scenario = await _context.Scenarios var scenario = await _context.Scenarios
.AsNoTracking() .AsNoTracking()
.Include(s => s.User)
.Include(s => s.ScenarioIndicators) .Include(s => s.ScenarioIndicators)
.ThenInclude(si => si.Indicator) .ThenInclude(si => si.Indicator)
.FirstOrDefaultAsync(s => s.Name == name) .FirstOrDefaultAsync(s => s.Name == name)
@@ -80,14 +81,36 @@ public class PostgreSqlTradingRepository : ITradingRepository
}); });
} }
public async Task<IEnumerable<Scenario>> GetScenariosByUserAsync(User user)
{
var userId = user?.Id ?? 0;
var scenarios = await _context.Scenarios
.AsNoTracking()
.Include(s => s.User)
.Include(s => s.ScenarioIndicators)
.ThenInclude(si => si.Indicator)
.Where(s => s.UserId == userId)
.ToListAsync()
.ConfigureAwait(false);
return scenarios.Select(scenario =>
{
var mappedScenario = PostgreSqlMappers.Map(scenario);
mappedScenario.Indicators = scenario.ScenarioIndicators
.Select(si => PostgreSqlMappers.Map(si.Indicator))
.ToList();
return mappedScenario;
});
}
public async Task InsertScenarioAsync(Scenario scenario) public async Task InsertScenarioAsync(Scenario scenario)
{ {
var userId = scenario.User?.Id ?? 0;
// Check if scenario already exists for the same user // Check if scenario already exists for the same user
var existingScenario = await _context.Scenarios var existingScenario = await _context.Scenarios
.AsNoTracking() .AsNoTracking()
.FirstOrDefaultAsync(s => s.Name == scenario.Name && .FirstOrDefaultAsync(s => s.Name == scenario.Name && s.UserId == userId);
((scenario.User == null && s.UserName == null) ||
(scenario.User != null && s.UserName == scenario.User.Name)));
if (existingScenario != null) if (existingScenario != null)
{ {
@@ -104,11 +127,10 @@ public class PostgreSqlTradingRepository : ITradingRepository
{ {
foreach (var indicator in scenario.Indicators) foreach (var indicator in scenario.Indicators)
{ {
var indicatorUserId = indicator.User?.Id ?? 0;
var indicatorEntity = await _context.Indicators var indicatorEntity = await _context.Indicators
.AsNoTracking() .AsNoTracking()
.FirstOrDefaultAsync(i => i.Name == indicator.Name && .FirstOrDefaultAsync(i => i.Name == indicator.Name && i.UserId == indicatorUserId);
((indicator.User == null && i.UserName == null) ||
(indicator.User != null && i.UserName == indicator.User.Name)));
if (indicatorEntity != null) if (indicatorEntity != null)
{ {
@@ -134,7 +156,7 @@ public class PostgreSqlTradingRepository : ITradingRepository
if (entity != null) if (entity != null)
{ {
entity.LoopbackPeriod = scenario.LoopbackPeriod ?? 1; entity.LoopbackPeriod = scenario.LoopbackPeriod ?? 1;
entity.UserName = scenario.User?.Name; entity.UserId = scenario.User?.Id ?? 0;
entity.UpdatedAt = DateTime.UtcNow; entity.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
@@ -196,12 +218,11 @@ public class PostgreSqlTradingRepository : ITradingRepository
public async Task InsertIndicatorAsync(IndicatorBase indicatorBase) public async Task InsertIndicatorAsync(IndicatorBase indicatorBase)
{ {
var indicatorUserId = indicatorBase.User?.Id ?? 0;
// Check if indicator already exists for the same user // Check if indicator already exists for the same user
var existingIndicator = await _context.Indicators var existingIndicator = await _context.Indicators
.AsNoTracking() .AsNoTracking()
.FirstOrDefaultAsync(i => i.Name == indicatorBase.Name && .FirstOrDefaultAsync(i => i.Name == indicatorBase.Name && i.UserId == indicatorUserId);
((indicatorBase.User == null && i.UserName == null) ||
(indicatorBase.User != null && i.UserName == indicatorBase.User.Name)));
if (existingIndicator != null) if (existingIndicator != null)
{ {
@@ -233,7 +254,7 @@ public class PostgreSqlTradingRepository : ITradingRepository
entity.SmoothPeriods = indicatorBase.SmoothPeriods; entity.SmoothPeriods = indicatorBase.SmoothPeriods;
entity.StochPeriods = indicatorBase.StochPeriods; entity.StochPeriods = indicatorBase.StochPeriods;
entity.CyclePeriods = indicatorBase.CyclePeriods; entity.CyclePeriods = indicatorBase.CyclePeriods;
entity.UserName = indicatorBase.User?.Name; entity.UserId = indicatorBase.User?.Id ?? 0;
entity.UpdatedAt = DateTime.UtcNow; entity.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
@@ -249,6 +270,7 @@ public class PostgreSqlTradingRepository : ITradingRepository
{ {
var position = await _context.Positions var position = await _context.Positions
.AsNoTracking() .AsNoTracking()
.Include(p => p.User)
.Include(p => p.OpenTrade) .Include(p => p.OpenTrade)
.Include(p => p.StopLossTrade) .Include(p => p.StopLossTrade)
.Include(p => p.TakeProfit1Trade) .Include(p => p.TakeProfit1Trade)
@@ -268,6 +290,7 @@ public class PostgreSqlTradingRepository : ITradingRepository
{ {
var positions = await _context.Positions var positions = await _context.Positions
.AsNoTracking() .AsNoTracking()
.Include(p => p.User)
.Include(p => p.OpenTrade) .Include(p => p.OpenTrade)
.Include(p => p.StopLossTrade) .Include(p => p.StopLossTrade)
.Include(p => p.TakeProfit1Trade) .Include(p => p.TakeProfit1Trade)
@@ -288,6 +311,7 @@ public class PostgreSqlTradingRepository : ITradingRepository
{ {
var positions = await _context.Positions var positions = await _context.Positions
.AsNoTracking() .AsNoTracking()
.Include(p => p.User)
.Include(p => p.OpenTrade) .Include(p => p.OpenTrade)
.Include(p => p.StopLossTrade) .Include(p => p.StopLossTrade)
.Include(p => p.TakeProfit1Trade) .Include(p => p.TakeProfit1Trade)
@@ -301,12 +325,11 @@ public class PostgreSqlTradingRepository : ITradingRepository
public async Task InsertPositionAsync(Position position) public async Task InsertPositionAsync(Position position)
{ {
var positionUserId = position.User?.Id ?? 0;
// Check if position already exists for the same user // Check if position already exists for the same user
var existingPosition = await _context.Positions var existingPosition = await _context.Positions
.AsNoTracking() .AsNoTracking()
.FirstOrDefaultAsync(p => p.Identifier == position.Identifier && .FirstOrDefaultAsync(p => p.Identifier == position.Identifier && p.UserId == positionUserId);
((position.User == null && p.UserName == null) ||
(position.User != null && p.UserName == position.User.Name)));
if (existingPosition != null) if (existingPosition != null)
{ {
@@ -385,10 +408,11 @@ public class PostgreSqlTradingRepository : ITradingRepository
public async Task<IEnumerable<Signal>> GetSignalsByUserAsync(User user) public async Task<IEnumerable<Signal>> GetSignalsByUserAsync(User user)
{ {
var userId = user?.Id ?? 0;
var signals = await _context.Signals var signals = await _context.Signals
.AsNoTracking() .AsNoTracking()
.Where(s => (user == null && s.UserName == null) || .Include(s => s.User)
(user != null && s.UserName == user.Name)) .Where(s => s.UserId == userId)
.ToListAsync() .ToListAsync()
.ConfigureAwait(false); .ConfigureAwait(false);
@@ -402,11 +426,11 @@ public class PostgreSqlTradingRepository : ITradingRepository
public async Task<Signal> GetSignalByIdentifierAsync(string identifier, User user = null) public async Task<Signal> GetSignalByIdentifierAsync(string identifier, User user = null)
{ {
var userId = user?.Id ?? 0;
var signal = await _context.Signals var signal = await _context.Signals
.AsNoTracking() .AsNoTracking()
.FirstOrDefaultAsync(s => s.Identifier == identifier && .Include(s => s.User)
((user == null && s.UserName == null) || .FirstOrDefaultAsync(s => s.Identifier == identifier && s.UserId == userId)
(user != null && s.UserName == user.Name)))
.ConfigureAwait(false); .ConfigureAwait(false);
return PostgreSqlMappers.Map(signal); return PostgreSqlMappers.Map(signal);
@@ -414,13 +438,13 @@ public class PostgreSqlTradingRepository : ITradingRepository
public async Task InsertSignalAsync(Signal signal) public async Task InsertSignalAsync(Signal signal)
{ {
var signalUserId = signal.User?.Id ?? 0;
// Check if signal already exists with the same identifier, date, and user // Check if signal already exists with the same identifier, date, and user
var existingSignal = _context.Signals var existingSignal = _context.Signals
.AsNoTracking() .AsNoTracking()
.FirstOrDefault(s => s.Identifier == signal.Identifier && .FirstOrDefault(s => s.Identifier == signal.Identifier &&
s.Date == signal.Date && s.Date == signal.Date &&
((s.UserName == null && signal.User == null) || s.UserId == signalUserId);
(s.UserName != null && signal.User != null && s.UserName == signal.User.Name)));
if (existingSignal != null) if (existingSignal != null)
{ {
@@ -435,21 +459,21 @@ public class PostgreSqlTradingRepository : ITradingRepository
public async Task<IndicatorBase> GetStrategyByNameUserAsync(string name, User user) public async Task<IndicatorBase> GetStrategyByNameUserAsync(string name, User user)
{ {
var userId = user?.Id ?? 0;
var indicator = await _context.Indicators var indicator = await _context.Indicators
.AsNoTracking() .AsNoTracking()
.FirstOrDefaultAsync(i => i.Name == name && .Include(i => i.User)
((user == null && i.UserName == null) || .FirstOrDefaultAsync(i => i.Name == name && i.UserId == userId);
(user != null && i.UserName == user.Name)));
return PostgreSqlMappers.Map(indicator); return PostgreSqlMappers.Map(indicator);
} }
public async Task<Scenario> GetScenarioByNameUserAsync(string scenarioName, User user) public async Task<Scenario> GetScenarioByNameUserAsync(string scenarioName, User user)
{ {
var userId = user?.Id ?? 0;
var scenario = await _context.Scenarios var scenario = await _context.Scenarios
.AsNoTracking() .AsNoTracking()
.FirstOrDefaultAsync(s => s.Name == scenarioName && .Include(s => s.User)
((user == null && s.UserName == null) || .FirstOrDefaultAsync(s => s.Name == scenarioName && s.UserId == userId);
(user != null && s.UserName == user.Name)));
return PostgreSqlMappers.Map(scenario); return PostgreSqlMappers.Map(scenario);
} }

View File

@@ -78,14 +78,6 @@ public class PostgreSqlUserRepository : IUserRepository
} }
} }
public async Task InsertUserAsync(User user)
{
var userEntity = PostgreSqlMappers.Map(user);
_context.Users.Add(userEntity);
await _context.SaveChangesAsync().ConfigureAwait(false);
}
public async Task<IEnumerable<User>> GetAllUsersAsync() public async Task<IEnumerable<User>> GetAllUsersAsync()
{ {
try try
@@ -107,31 +99,45 @@ public class PostgreSqlUserRepository : IUserRepository
} }
} }
public async Task UpdateUser(User user) public async Task SaveOrUpdateUserAsync(User user)
{ {
try try
{ {
var userEntity = await _context.Users var existingUser = await _context.Users
.AsTracking() // Explicitly enable tracking for update operations .AsTracking()
.FirstOrDefaultAsync(u => u.Name == user.Name) .FirstOrDefaultAsync(u => u.Name == user.Name)
.ConfigureAwait(false); .ConfigureAwait(false);
if (userEntity == null) if (existingUser != null)
{ {
throw new InvalidOperationException($"User with name '{user.Name}' not found"); // Update existing user
existingUser.AgentName = user.AgentName;
existingUser.AvatarUrl = user.AvatarUrl;
existingUser.TelegramChannel = user.TelegramChannel;
_context.Users.Update(existingUser);
// Update the user object with the existing user's ID
user.Id = existingUser.Id;
}
else
{
// Insert new user
var userEntity = PostgreSqlMappers.Map(user);
_context.Users.Add(userEntity);
// Update the user object with the database-generated ID after save
await _context.SaveChangesAsync().ConfigureAwait(false);
user.Id = userEntity.Id;
return; // Exit early since we already saved
} }
userEntity.AgentName = user.AgentName;
userEntity.AvatarUrl = user.AvatarUrl;
userEntity.TelegramChannel = user.TelegramChannel;
_context.Users.Update(userEntity);
await _context.SaveChangesAsync().ConfigureAwait(false); await _context.SaveChangesAsync().ConfigureAwait(false);
} }
catch (Exception e) catch (Exception e)
{ {
Console.WriteLine(e); Console.WriteLine(e);
throw new Exception("Cannot update user"); throw new Exception("Cannot save or update user");
} }
} }
} }