diff --git a/src/Managing.Application.Abstractions/Repositories/IAgentSummaryRepository.cs b/src/Managing.Application.Abstractions/Repositories/IAgentSummaryRepository.cs index 9a13df0..b2f095e 100644 --- a/src/Managing.Application.Abstractions/Repositories/IAgentSummaryRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/IAgentSummaryRepository.cs @@ -27,4 +27,5 @@ public interface IAgentSummaryRepository SortableFields sortBy, string sortOrder, IEnumerable? agentNames = null); + Task> GetAllAgentWithRunningBots(); } \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Repositories/ITradingRepository.cs b/src/Managing.Application.Abstractions/Repositories/ITradingRepository.cs index c15a72c..7b03dd6 100644 --- a/src/Managing.Application.Abstractions/Repositories/ITradingRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/ITradingRepository.cs @@ -17,6 +17,7 @@ public interface ITradingRepository Task InsertScenarioAsync(Scenario scenario); Task InsertIndicatorAsync(IndicatorBase indicator); Task> GetScenariosAsync(); + Task> GetScenariosByUserAsync(User user); Task> GetStrategiesAsync(); Task> GetIndicatorsAsync(); Task DeleteScenarioAsync(string name); diff --git a/src/Managing.Application.Abstractions/Repositories/IUserRepository.cs b/src/Managing.Application.Abstractions/Repositories/IUserRepository.cs index 2317325..a1a8815 100644 --- a/src/Managing.Application.Abstractions/Repositories/IUserRepository.cs +++ b/src/Managing.Application.Abstractions/Repositories/IUserRepository.cs @@ -7,6 +7,5 @@ public interface IUserRepository Task GetUserByAgentNameAsync(string agentName); Task GetUserByNameAsync(string name); Task> GetAllUsersAsync(); - Task InsertUserAsync(User user); - Task UpdateUser(User user); + Task SaveOrUpdateUserAsync(User user); } \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Services/IAgentService.cs b/src/Managing.Application.Abstractions/Services/IAgentService.cs index 552e481..18d6cfe 100644 --- a/src/Managing.Application.Abstractions/Services/IAgentService.cs +++ b/src/Managing.Application.Abstractions/Services/IAgentService.cs @@ -13,4 +13,5 @@ public interface IAgentService Task SaveOrUpdateAgentSummary(AgentSummary agentSummary); Task> GetAllAgentSummaries(); + Task> GetAllOnlineAgents(); } \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Services/ITradingService.cs b/src/Managing.Application.Abstractions/Services/ITradingService.cs index 2202446..2328f5f 100644 --- a/src/Managing.Application.Abstractions/Services/ITradingService.cs +++ b/src/Managing.Application.Abstractions/Services/ITradingService.cs @@ -23,6 +23,7 @@ public interface ITradingService Task InsertScenarioAsync(Scenario scenario); Task InsertIndicatorAsync(IndicatorBase indicatorBase); Task> GetScenariosAsync(); + Task> GetScenariosByUserAsync(User user); Task> GetIndicatorsAsync(); Task DeleteScenarioAsync(string name); Task DeleteIndicatorAsync(string name); diff --git a/src/Managing.Application/Accounts/AccountService.cs b/src/Managing.Application/Accounts/AccountService.cs index a0107aa..55b01a5 100644 --- a/src/Managing.Application/Accounts/AccountService.cs +++ b/src/Managing.Application/Accounts/AccountService.cs @@ -35,52 +35,52 @@ public class AccountService : IAccountService _userRepository = userRepository; } - public async Task CreateAccount(User user, Account request) + public async Task 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 { - request.User = user; + account.User = user; - if (request.Exchange == TradingExchanges.Evm - && request.Type == AccountType.Trader) + if (account.Exchange == TradingExchanges.Evm + && account.Type == AccountType.Trader) { var keys = _evmManager.GenerateAddress(); - request.Key = keys.Key; - request.Secret = keys.Secret; + account.Key = keys.Key; + account.Secret = keys.Secret; } - else if (request.Exchange == TradingExchanges.Evm - && request.Type == AccountType.Privy) + else if (account.Exchange == TradingExchanges.Evm + && account.Type == AccountType.Privy) { - if (string.IsNullOrEmpty(request.Key)) + if (string.IsNullOrEmpty(account.Key)) { // No key provided, create new privy embedded wallet. // TODO : Fix it to create privy wallet var privyClient = await _evmManager.CreatePrivyWallet(); - request.Key = privyClient.Address; - request.Secret = privyClient.Id; + account.Key = privyClient.Address; + account.Secret = privyClient.Id; } else { - request.Key = request.Key; // Address - request.Secret = request.Secret; // Privy wallet id + account.Key = account.Key; // Address + account.Secret = account.Secret; // Privy wallet id } } else { - request.Key = request.Key; - request.Secret = request.Secret; + account.Key = account.Key; + account.Secret = account.Secret; } - await _accountRepository.InsertAccountAsync(request); + await _accountRepository.InsertAccountAsync(account); } - return request; + return account; } public bool DeleteAccount(User user, string name) diff --git a/src/Managing.Application/Agents/AgentService.cs b/src/Managing.Application/Agents/AgentService.cs index 73fdf01..bcc5d31 100644 --- a/src/Managing.Application/Agents/AgentService.cs +++ b/src/Managing.Application/Agents/AgentService.cs @@ -114,4 +114,10 @@ public class AgentService : IAgentService { return await _agentSummaryRepository.GetAllAsync(); } + + public async Task> GetAllOnlineAgents() + { + var agentSummaries = await _agentSummaryRepository.GetAllAgentWithRunningBots(); + return agentSummaries.Select(a => a.AgentName); + } } \ No newline at end of file diff --git a/src/Managing.Application/ManageBot/GetOnlineAgentNamesCommandHandler.cs b/src/Managing.Application/ManageBot/GetOnlineAgentNamesCommandHandler.cs index 4664e1c..881c6dc 100644 --- a/src/Managing.Application/ManageBot/GetOnlineAgentNamesCommandHandler.cs +++ b/src/Managing.Application/ManageBot/GetOnlineAgentNamesCommandHandler.cs @@ -1,4 +1,4 @@ -using Managing.Application.Abstractions; +using Managing.Application.Abstractions.Services; using Managing.Application.ManageBot.Commands; using MediatR; @@ -9,18 +9,17 @@ namespace Managing.Application.ManageBot /// public class GetOnlineAgentNamesCommandHandler : IRequestHandler> { - private readonly IBotService _botService; + private readonly IAgentService _agentService; - public GetOnlineAgentNamesCommandHandler(IBotService botService) + public GetOnlineAgentNamesCommandHandler(IAgentService agentService) { - _botService = botService; + _agentService = agentService; } public async Task> Handle(GetOnlineAgentNamesCommand request, CancellationToken cancellationToken) { - var activeBots = await _botService.GetActiveBotsNamesAsync(); - return activeBots; + return await _agentService.GetAllOnlineAgents(); } } } \ No newline at end of file diff --git a/src/Managing.Application/Scenarios/ScenarioService.cs b/src/Managing.Application/Scenarios/ScenarioService.cs index 94ec165..d958295 100644 --- a/src/Managing.Application/Scenarios/ScenarioService.cs +++ b/src/Managing.Application/Scenarios/ScenarioService.cs @@ -116,8 +116,7 @@ namespace Managing.Application.Scenarios public async Task> GetScenariosByUserAsync(User user) { - var scenarios = await _tradingService.GetScenariosAsync(); - return scenarios.Where(s => s.User?.Name == user.Name); + return await _tradingService.GetScenariosByUserAsync(user); } public async Task CreateScenarioForUser(User user, string name, List strategies, diff --git a/src/Managing.Application/Trading/TradingService.cs b/src/Managing.Application/Trading/TradingService.cs index 3657ae6..c5b24dc 100644 --- a/src/Managing.Application/Trading/TradingService.cs +++ b/src/Managing.Application/Trading/TradingService.cs @@ -88,6 +88,11 @@ public class TradingService : ITradingService return await _tradingRepository.GetScenariosAsync(); } + public async Task> GetScenariosByUserAsync(User user) + { + return await _tradingRepository.GetScenariosByUserAsync(user); + } + public async Task> GetIndicatorsAsync() { return await _tradingRepository.GetStrategiesAsync(); diff --git a/src/Managing.Application/Users/UserService.cs b/src/Managing.Application/Users/UserService.cs index 1e4f931..f0cd2b3 100644 --- a/src/Managing.Application/Users/UserService.cs +++ b/src/Managing.Application/Users/UserService.cs @@ -95,15 +95,16 @@ public class UserService : IUserService } else { - // First login + // First login - create user first user = new User { 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 { Name = $"{name}-embedded", @@ -116,9 +117,6 @@ public class UserService : IUserService { account }; - - // Update user with the new account - await _userRepository.UpdateUser(user); } return user; @@ -165,8 +163,12 @@ public class UserService : IUserService else { 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; - await _userRepository.UpdateUser(user); + await _userRepository.SaveOrUpdateUserAsync(user); // Initialize the AgentGrain for this user try @@ -204,8 +206,12 @@ public class UserService : IUserService } 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; - await _userRepository.UpdateUser(user); + await _userRepository.SaveOrUpdateUserAsync(user); return user; } @@ -222,8 +228,12 @@ public class UserService : IUserService } 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; - await _userRepository.UpdateUser(user); + await _userRepository.SaveOrUpdateUserAsync(user); return user; } @@ -247,6 +257,4 @@ public class UserService : IUserService { return await _userRepository.GetAllUsersAsync(); } - - } \ No newline at end of file diff --git a/src/Managing.Domain/Users/User.cs b/src/Managing.Domain/Users/User.cs index 798c1b5..d557413 100644 --- a/src/Managing.Domain/Users/User.cs +++ b/src/Managing.Domain/Users/User.cs @@ -6,21 +6,15 @@ namespace Managing.Domain.Users; [GenerateSerializer] public class User { - [Id(0)] - public int Id { get; set; } + [Id(0)] public int Id { get; set; } - [Id(1)] - public string Name { get; set; } - - [Id(2)] - public List Accounts { get; set; } - - [Id(3)] - public string AgentName { get; set; } - - [Id(4)] - public string AvatarUrl { get; set; } - - [Id(5)] - public string TelegramChannel { get; set; } + [Id(1)] public string Name { get; set; } + + [Id(2)] public List Accounts { get; set; } + + [Id(3)] public string AgentName { get; set; } = string.Empty; + + [Id(4)] public string AvatarUrl { get; set; } = string.Empty; + + [Id(5)] public string TelegramChannel { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/Migrations/20250805133422_UniqueUsername.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20250805133422_UniqueUsername.Designer.cs new file mode 100644 index 0000000..6d84843 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250805133422_UniqueUsername.Designer.cs @@ -0,0 +1,1384 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20250805133422_UniqueUsername")] + partial class UniqueUsername + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AgentSummaryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActiveStrategiesCount") + .HasColumnType("integer"); + + b.Property("AgentName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Losses") + .HasColumnType("integer"); + + b.Property("Runtime") + .HasColumnType("timestamp with time zone"); + + b.Property("TotalPnL") + .HasColumnType("decimal(18,8)"); + + b.Property("TotalROI") + .HasColumnType("decimal(18,8)"); + + b.Property("TotalVolume") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Wins") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AgentName"); + + b.HasIndex("TotalPnL"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("AgentSummaries"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("text"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("UserName"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserName", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b => + { + b.Property("Identifier") + .ValueGeneratedOnAdd() + .HasMaxLength(255) + .HasColumnType("uuid"); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("StartupTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("TradeLosses") + .HasColumnType("integer"); + + b.Property("TradeWins") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.HasKey("Identifier"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("Bots"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Identifier") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Identifier"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Date"); + + b.HasIndex("Identifier", "Date", "UserName") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthMinersLeaderboardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinersData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.ToTable("SynthMinersLeaderboards"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthPredictionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinerUid") + .HasColumnType("integer"); + + b.Property("PredictionData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.Property("TimeLength") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.ToTable("SynthPredictions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Fee") + .HasColumnType("decimal(18,8)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelayTicks") + .HasColumnType("bigint"); + + b.Property("ExecutionCount") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastRunTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("WorkerType") + .IsUnique(); + + b.ToTable("Workers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", 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.AgentSummaryEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", 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.BundleBacktestRequestEntity", 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.GeneticRequestEntity", 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 => + { + 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.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250805133422_UniqueUsername.cs b/src/Managing.Infrastructure.Database/Migrations/20250805133422_UniqueUsername.cs new file mode 100644 index 0000000..13cf34c --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250805133422_UniqueUsername.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class UniqueUsername : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_Users_Name", + table: "Users", + column: "Name", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Users_Name", + table: "Users"); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250805135750_AccountUpdate.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20250805135750_AccountUpdate.Designer.cs new file mode 100644 index 0000000..b8a2a4a --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250805135750_AccountUpdate.Designer.cs @@ -0,0 +1,1386 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20250805135750_AccountUpdate")] + partial class AccountUpdate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AgentSummaryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActiveStrategiesCount") + .HasColumnType("integer"); + + b.Property("AgentName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Losses") + .HasColumnType("integer"); + + b.Property("Runtime") + .HasColumnType("timestamp with time zone"); + + b.Property("TotalPnL") + .HasColumnType("decimal(18,8)"); + + b.Property("TotalROI") + .HasColumnType("decimal(18,8)"); + + b.Property("TotalVolume") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Wins") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AgentName"); + + b.HasIndex("TotalPnL"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("AgentSummaries"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("text"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("UserName"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserName", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b => + { + b.Property("Identifier") + .ValueGeneratedOnAdd() + .HasMaxLength(255) + .HasColumnType("uuid"); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("StartupTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("TradeLosses") + .HasColumnType("integer"); + + b.Property("TradeWins") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.HasKey("Identifier"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("Bots"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Identifier") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Identifier"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Date"); + + b.HasIndex("Identifier", "Date", "UserName") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthMinersLeaderboardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinersData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.ToTable("SynthMinersLeaderboards"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthPredictionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinerUid") + .HasColumnType("integer"); + + b.Property("PredictionData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.Property("TimeLength") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.ToTable("SynthPredictions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Fee") + .HasColumnType("decimal(18,8)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelayTicks") + .HasColumnType("bigint"); + + b.Property("ExecutionCount") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastRunTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("WorkerType") + .IsUnique(); + + b.ToTable("Workers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", 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.AgentSummaryEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", 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.BundleBacktestRequestEntity", 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.GeneticRequestEntity", 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 => + { + 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.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + b.Navigation("TakeProfit2Trade"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + b.Navigation("Scenario"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250805135750_AccountUpdate.cs b/src/Managing.Infrastructure.Database/Migrations/20250805135750_AccountUpdate.cs new file mode 100644 index 0000000..f342987 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250805135750_AccountUpdate.cs @@ -0,0 +1,58 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class AccountUpdate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "UserId", + table: "Accounts", + type: "integer", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "integer", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "Key", + table: "Accounts", + type: "character varying(500)", + maxLength: 500, + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "character varying(500)", + oldMaxLength: 500, + oldNullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "UserId", + table: "Accounts", + type: "integer", + nullable: true, + oldClrType: typeof(int), + oldType: "integer"); + + migrationBuilder.AlterColumn( + name: "Key", + table: "Accounts", + type: "character varying(500)", + maxLength: 500, + nullable: true, + oldClrType: typeof(string), + oldType: "character varying(500)", + oldMaxLength: 500); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250805150905_UserKeys.Designer.cs b/src/Managing.Infrastructure.Database/Migrations/20250805150905_UserKeys.Designer.cs new file mode 100644 index 0000000..670fa2a --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250805150905_UserKeys.Designer.cs @@ -0,0 +1,1422 @@ +// +using System; +using Managing.Infrastructure.Databases.PostgreSql; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + [DbContext(typeof(ManagingDbContext))] + [Migration("20250805150905_UserKeys")] + partial class UserKeys + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exchange") + .IsRequired() + .HasColumnType("text"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Secret") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AgentSummaryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActiveStrategiesCount") + .HasColumnType("integer"); + + b.Property("AgentName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Losses") + .HasColumnType("integer"); + + b.Property("Runtime") + .HasColumnType("timestamp with time zone"); + + b.Property("TotalPnL") + .HasColumnType("decimal(18,8)"); + + b.Property("TotalROI") + .HasColumnType("decimal(18,8)"); + + b.Property("TotalVolume") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Wins") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("AgentName"); + + b.HasIndex("TotalPnL"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("AgentSummaries"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BacktestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ConfigJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasColumnType("decimal(18,8)"); + + b.Property("FinalPnl") + .HasColumnType("decimal(18,8)"); + + b.Property("GrowthPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("HodlPercentage") + .HasColumnType("decimal(18,8)"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Metadata") + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("PositionsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Score") + .HasColumnType("double precision"); + + b.Property("ScoreMessage") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("text"); + + b.Property("SignalsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("StatisticsJson") + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("WinRate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("RequestId"); + + b.HasIndex("Score"); + + b.HasIndex("UserId"); + + b.HasIndex("RequestId", "Score"); + + b.HasIndex("UserId", "Score"); + + b.ToTable("Backtests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BotEntity", b => + { + b.Property("Identifier") + .ValueGeneratedOnAdd() + .HasMaxLength(255) + .HasColumnType("uuid"); + + b.Property("CreateDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Fees") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.Property("StartupTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("TradeLosses") + .HasColumnType("integer"); + + b.Property("TradeWins") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("numeric(18,8)"); + + b.HasKey("Identifier"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("Bots"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.BundleBacktestRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BacktestRequestsJson") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CompletedBacktests") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrentBacktest") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("EstimatedTimeRemainingSeconds") + .HasColumnType("integer"); + + b.Property("FailedBacktests") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ProgressInfo") + .HasColumnType("text"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("ResultsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("TotalBacktests") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "CreatedAt"); + + b.ToTable("BundleBacktestRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.FundingRateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .HasColumnType("integer"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("OpenInterest") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Rate") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Exchange", "Date"); + + b.HasIndex("Ticker", "Exchange"); + + b.HasIndex("Ticker", "Exchange", "Date") + .IsUnique(); + + b.ToTable("FundingRates"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.GeneticRequestEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("decimal(18,8)"); + + b.Property("BestChromosome") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("BestFitness") + .HasColumnType("double precision"); + + b.Property("BestFitnessSoFar") + .HasColumnType("double precision"); + + b.Property("BestIndividual") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("CompletedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CrossoverMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("CurrentGeneration") + .HasColumnType("integer"); + + b.Property("EligibleIndicatorsJson") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("ElitismPercentage") + .HasColumnType("integer"); + + b.Property("EndDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("character varying(2000)"); + + b.Property("Generations") + .HasColumnType("integer"); + + b.Property("MaxTakeProfit") + .HasColumnType("double precision"); + + b.Property("MutationMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("MutationRate") + .HasColumnType("double precision"); + + b.Property("PopulationSize") + .HasColumnType("integer"); + + b.Property("ProgressInfo") + .HasMaxLength(4000) + .HasColumnType("character varying(4000)"); + + b.Property("RequestId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SelectionMethod") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("RequestId") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("UserId"); + + b.ToTable("GeneticRequests"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CyclePeriods") + .HasColumnType("integer"); + + b.Property("FastPeriods") + .HasColumnType("integer"); + + b.Property("MinimumHistory") + .HasColumnType("integer"); + + b.Property("Multiplier") + .HasColumnType("double precision"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Period") + .HasColumnType("integer"); + + b.Property("SignalPeriods") + .HasColumnType("integer"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("SlowPeriods") + .HasColumnType("integer"); + + b.Property("SmoothPeriods") + .HasColumnType("integer"); + + b.Property("StochPeriods") + .HasColumnType("integer"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Name"); + + b.ToTable("Indicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("StopLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("TakeProfit") + .HasColumnType("decimal(18,8)"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.Property("UserName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserName"); + + b.HasIndex("UserName", "Name"); + + b.ToTable("MoneyManagements"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.PositionEntity", b => + { + b.Property("Identifier") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Initiator") + .IsRequired() + .HasColumnType("text"); + + b.Property("MoneyManagementJson") + .HasColumnType("text"); + + b.Property("OpenTradeId") + .HasColumnType("integer"); + + b.Property("OriginDirection") + .IsRequired() + .HasColumnType("text"); + + b.Property("ProfitAndLoss") + .HasColumnType("decimal(18,8)"); + + b.Property("SignalIdentifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("StopLossTradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit1TradeId") + .HasColumnType("integer"); + + b.Property("TakeProfit2TradeId") + .HasColumnType("integer"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Identifier"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("OpenTradeId"); + + b.HasIndex("Status"); + + b.HasIndex("StopLossTradeId"); + + b.HasIndex("TakeProfit1TradeId"); + + b.HasIndex("TakeProfit2TradeId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Identifier"); + + b.ToTable("Positions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LoopbackPeriod") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Name"); + + b.ToTable("Scenarios"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioIndicatorEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IndicatorId") + .HasColumnType("integer"); + + b.Property("ScenarioId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("IndicatorId"); + + b.HasIndex("ScenarioId", "IndicatorId") + .IsUnique(); + + b.ToTable("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SignalEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CandleJson") + .HasColumnType("text"); + + b.Property("Confidence") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("Identifier") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IndicatorName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SignalType") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("Timeframe") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Identifier"); + + b.HasIndex("Status"); + + b.HasIndex("Ticker"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Date"); + + b.HasIndex("Identifier", "Date", "UserId") + .IsUnique(); + + b.ToTable("Signals"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SpotlightOverviewEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Identifier") + .HasColumnType("uuid"); + + b.Property("ScenarioCount") + .HasColumnType("integer"); + + b.Property("SpotlightsJson") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DateTime"); + + b.HasIndex("Identifier") + .IsUnique(); + + b.HasIndex("DateTime", "ScenarioCount"); + + b.ToTable("SpotlightOverviews"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthMinersLeaderboardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinersData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.ToTable("SynthMinersLeaderboards"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.SynthPredictionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Asset") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CacheKey") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBacktest") + .HasColumnType("boolean"); + + b.Property("MinerUid") + .HasColumnType("integer"); + + b.Property("PredictionData") + .IsRequired() + .HasColumnType("jsonb"); + + b.Property("SignalDate") + .HasColumnType("timestamp with time zone"); + + b.Property("TimeIncrement") + .HasColumnType("integer"); + + b.Property("TimeLength") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CacheKey") + .IsUnique(); + + b.ToTable("SynthPredictions"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TopVolumeTickerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Exchange") + .HasColumnType("integer"); + + b.Property("Rank") + .HasColumnType("integer"); + + b.Property("Ticker") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Volume") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("Exchange"); + + b.HasIndex("Ticker"); + + b.HasIndex("Date", "Rank"); + + b.HasIndex("Exchange", "Date"); + + b.ToTable("TopVolumeTickers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Direction") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExchangeOrderId") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Fee") + .HasColumnType("decimal(18,8)"); + + b.Property("Leverage") + .HasColumnType("decimal(18,8)"); + + b.Property("Message") + .HasColumnType("text"); + + b.Property("Price") + .HasColumnType("decimal(18,8)"); + + b.Property("Quantity") + .HasColumnType("decimal(18,8)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("Ticker") + .IsRequired() + .HasColumnType("text"); + + b.Property("TradeType") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Date"); + + b.HasIndex("ExchangeOrderId"); + + b.HasIndex("Status"); + + b.ToTable("Trades"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.TraderEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AverageLoss") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("AverageWin") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsBestTrader") + .HasColumnType("boolean"); + + b.Property("Pnl") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("Roi") + .HasPrecision(18, 8) + .HasColumnType("decimal(18,8)"); + + b.Property("TradeCount") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Winrate") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("IsBestTrader"); + + b.HasIndex("Pnl"); + + b.HasIndex("Roi"); + + b.HasIndex("Winrate"); + + b.HasIndex("Address", "IsBestTrader") + .IsUnique(); + + b.HasIndex("IsBestTrader", "Roi"); + + b.HasIndex("IsBestTrader", "Winrate"); + + b.ToTable("Traders"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AgentName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("AvatarUrl") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("TelegramChannel") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.WorkerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("DelayTicks") + .HasColumnType("bigint"); + + b.Property("ExecutionCount") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastRunTime") + .HasColumnType("timestamp with time zone"); + + b.Property("StartTime") + .HasColumnType("timestamp with time zone"); + + b.Property("WorkerType") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("WorkerType") + .IsUnique(); + + b.ToTable("Workers"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.AccountEntity", 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.AgentSummaryEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + 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 => + { + 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.BundleBacktestRequestEntity", 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.GeneticRequestEntity", 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 => + { + 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 => + { + 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.PositionEntity", b => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "OpenTrade") + .WithMany() + .HasForeignKey("OpenTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "StopLossTrade") + .WithMany() + .HasForeignKey("StopLossTradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit1Trade") + .WithMany() + .HasForeignKey("TakeProfit1TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.TradeEntity", "TakeProfit2Trade") + .WithMany() + .HasForeignKey("TakeProfit2TradeId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("OpenTrade"); + + b.Navigation("StopLossTrade"); + + b.Navigation("TakeProfit1Trade"); + + 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 => + { + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.IndicatorEntity", "Indicator") + .WithMany("ScenarioIndicators") + .HasForeignKey("IndicatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", "Scenario") + .WithMany("ScenarioIndicators") + .HasForeignKey("ScenarioId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Indicator"); + + 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 => + { + b.Navigation("ScenarioIndicators"); + }); + + modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.ScenarioEntity", b => + { + b.Navigation("ScenarioIndicators"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/20250805150905_UserKeys.cs b/src/Managing.Infrastructure.Database/Migrations/20250805150905_UserKeys.cs new file mode 100644 index 0000000..8c384e8 --- /dev/null +++ b/src/Managing.Infrastructure.Database/Migrations/20250805150905_UserKeys.cs @@ -0,0 +1,425 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Managing.Infrastructure.Databases.Migrations +{ + /// + public partial class UserKeys : Migration + { + /// + 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( + name: "UserId", + table: "Signals", + type: "integer", + nullable: true); + + migrationBuilder.AddColumn( + name: "UserId", + table: "Scenarios", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "UserId", + table: "Positions", + type: "integer", + nullable: true); + + migrationBuilder.AddColumn( + name: "UserId", + table: "Indicators", + type: "integer", + nullable: true); + + migrationBuilder.AddColumn( + 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); + } + + /// + 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( + name: "UserName", + table: "Signals", + type: "character varying(255)", + maxLength: 255, + nullable: true); + + migrationBuilder.AddColumn( + name: "UserName", + table: "Scenarios", + type: "character varying(255)", + maxLength: 255, + nullable: true); + + migrationBuilder.AddColumn( + name: "UserName", + table: "Positions", + type: "character varying(255)", + maxLength: 255, + nullable: true); + + migrationBuilder.AddColumn( + name: "UserName", + table: "Indicators", + type: "character varying(255)", + maxLength: 255, + nullable: true); + + migrationBuilder.AddColumn( + name: "UserName", + table: "BundleBacktestRequests", + type: "character varying(255)", + maxLength: 255, + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + 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" }); + } + } +} diff --git a/src/Managing.Infrastructure.Database/Migrations/ManagingDbContextModelSnapshot.cs b/src/Managing.Infrastructure.Database/Migrations/ManagingDbContextModelSnapshot.cs index d20018b..041b9b2 100644 --- a/src/Managing.Infrastructure.Database/Migrations/ManagingDbContextModelSnapshot.cs +++ b/src/Managing.Infrastructure.Database/Migrations/ManagingDbContextModelSnapshot.cs @@ -35,6 +35,7 @@ namespace Managing.Infrastructure.Databases.Migrations .HasColumnType("text"); b.Property("Key") + .IsRequired() .HasMaxLength(500) .HasColumnType("character varying(500)"); @@ -51,7 +52,7 @@ namespace Managing.Infrastructure.Databases.Migrations .IsRequired() .HasColumnType("text"); - b.Property("UserId") + b.Property("UserId") .HasColumnType("integer"); b.HasKey("Id"); @@ -192,10 +193,8 @@ namespace Managing.Infrastructure.Databases.Migrations b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); - b.Property("UserName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + b.Property("UserId") + .HasColumnType("integer"); b.Property("WinRate") .HasColumnType("integer"); @@ -209,11 +208,11 @@ namespace Managing.Infrastructure.Databases.Migrations b.HasIndex("Score"); - b.HasIndex("UserName"); + b.HasIndex("UserId"); b.HasIndex("RequestId", "Score"); - b.HasIndex("UserName", "Score"); + b.HasIndex("UserId", "Score"); b.ToTable("Backtests"); }); @@ -347,11 +346,6 @@ namespace Managing.Infrastructure.Databases.Migrations b.Property("UserId") .HasColumnType("integer"); - b.Property("UserName") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - b.HasKey("Id"); b.HasIndex("RequestId") @@ -361,9 +355,7 @@ namespace Managing.Infrastructure.Databases.Migrations b.HasIndex("UserId"); - b.HasIndex("UserName"); - - b.HasIndex("UserName", "CreatedAt"); + b.HasIndex("UserId", "CreatedAt"); b.ToTable("BundleBacktestRequests"); }); @@ -593,15 +585,14 @@ namespace Managing.Infrastructure.Databases.Migrations b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); - b.Property("UserName") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + b.Property("UserId") + .HasColumnType("integer"); b.HasKey("Id"); - b.HasIndex("UserName"); + b.HasIndex("UserId"); - b.HasIndex("UserName", "Name"); + b.HasIndex("UserId", "Name"); b.ToTable("Indicators"); }); @@ -715,9 +706,8 @@ namespace Managing.Infrastructure.Databases.Migrations b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); - b.Property("UserName") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + b.Property("UserId") + .HasColumnType("integer"); b.HasKey("Identifier"); @@ -736,9 +726,9 @@ namespace Managing.Infrastructure.Databases.Migrations b.HasIndex("TakeProfit2TradeId"); - b.HasIndex("UserName"); + b.HasIndex("UserId"); - b.HasIndex("UserName", "Identifier"); + b.HasIndex("UserId", "Identifier"); b.ToTable("Positions"); }); @@ -765,15 +755,14 @@ namespace Managing.Infrastructure.Databases.Migrations b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); - b.Property("UserName") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + b.Property("UserId") + .HasColumnType("integer"); b.HasKey("Id"); - b.HasIndex("UserName"); + b.HasIndex("UserId"); - b.HasIndex("UserName", "Name"); + b.HasIndex("UserId", "Name"); b.ToTable("Scenarios"); }); @@ -863,9 +852,8 @@ namespace Managing.Infrastructure.Databases.Migrations b.Property("UpdatedAt") .HasColumnType("timestamp with time zone"); - b.Property("UserName") - .HasMaxLength(255) - .HasColumnType("character varying(255)"); + b.Property("UserId") + .HasColumnType("integer"); b.HasKey("Id"); @@ -877,11 +865,11 @@ namespace Managing.Infrastructure.Databases.Migrations 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(); b.ToTable("Signals"); @@ -1213,6 +1201,9 @@ namespace Managing.Infrastructure.Databases.Migrations b.HasKey("Id"); + b.HasIndex("Name") + .IsUnique(); + b.ToTable("Users"); }); @@ -1256,7 +1247,8 @@ namespace Managing.Infrastructure.Databases.Migrations b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") .WithMany() .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.SetNull); + .OnDelete(DeleteBehavior.SetNull) + .IsRequired(); b.Navigation("User"); }); @@ -1272,6 +1264,17 @@ namespace Managing.Infrastructure.Databases.Migrations 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 => { b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") @@ -1303,6 +1306,16 @@ namespace Managing.Infrastructure.Databases.Migrations 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 => { b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") @@ -1335,6 +1348,11 @@ namespace Managing.Infrastructure.Databases.Migrations .HasForeignKey("TakeProfit2TradeId") .OnDelete(DeleteBehavior.SetNull); + b.HasOne("Managing.Infrastructure.Databases.PostgreSql.Entities.UserEntity", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + b.Navigation("OpenTrade"); b.Navigation("StopLossTrade"); @@ -1342,6 +1360,19 @@ namespace Managing.Infrastructure.Databases.Migrations b.Navigation("TakeProfit1Trade"); 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 => @@ -1363,6 +1394,16 @@ namespace Managing.Infrastructure.Databases.Migrations 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 => { b.Navigation("ScenarioIndicators"); diff --git a/src/Managing.Infrastructure.Database/PostgreSql/AgentSummaryRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/AgentSummaryRepository.cs index 9c396f7..9fd5600 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/AgentSummaryRepository.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/AgentSummaryRepository.cs @@ -229,4 +229,14 @@ public class AgentSummaryRepository : IAgentSummaryRepository User = PostgreSqlMappers.Map(entity.User) }; } + + public async Task> 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); + } } \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/AccountEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/AccountEntity.cs index d28993b..aac1ab1 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/Entities/AccountEntity.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/AccountEntity.cs @@ -1,20 +1,23 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using static Managing.Common.Enums; namespace Managing.Infrastructure.Databases.PostgreSql.Entities; +[Table("Accounts")] public class AccountEntity { - public int Id { get; set; } - public string Name { get; set; } - public TradingExchanges Exchange { get; set; } - public AccountType Type { get; set; } - public string? Key { get; set; } + [Key] public int Id { get; set; } + [Required] public string Name { get; set; } + [Required] public TradingExchanges Exchange { get; set; } + [Required] public AccountType Type { get; set; } + [Required] public string? Key { get; set; } public string? Secret { get; set; } - public int? UserId { get; set; } - + [Required] public int UserId { get; set; } + // Navigation properties public UserEntity? User { get; set; } - + // Store balances as JSON if needed, or skip them entirely // public string? BalancesJson { get; set; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/BacktestEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BacktestEntity.cs index 857969a..a5d0059 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/Entities/BacktestEntity.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BacktestEntity.cs @@ -56,8 +56,10 @@ public class BacktestEntity public string MoneyManagementJson { get; set; } = string.Empty; [Required] - [MaxLength(255)] - public string UserName { get; set; } = string.Empty; + public int UserId { get; set; } + + // Navigation property + public UserEntity? User { get; set; } [Column(TypeName = "jsonb")] public string? StatisticsJson { get; set; } diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/BotEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BotEntity.cs index b9499dd..15a3f3e 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/Entities/BotEntity.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BotEntity.cs @@ -15,7 +15,7 @@ public class BotEntity 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 DateTime CreateDate { get; set; } diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/BundleBacktestRequestEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BundleBacktestRequestEntity.cs index 9f2c495..e9fd9bc 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/Entities/BundleBacktestRequestEntity.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/BundleBacktestRequestEntity.cs @@ -15,10 +15,6 @@ public class BundleBacktestRequestEntity [MaxLength(255)] public string RequestId { get; set; } = string.Empty; - [Required] - [MaxLength(255)] - public string UserName { get; set; } = string.Empty; - // Foreign key to User entity public int? UserId { get; set; } diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/GeneticRequestEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/GeneticRequestEntity.cs index 0820ac5..0791539 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/Entities/GeneticRequestEntity.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/GeneticRequestEntity.cs @@ -1,7 +1,9 @@ +using System.ComponentModel.DataAnnotations.Schema; using static Managing.Common.Enums; namespace Managing.Infrastructure.Databases.PostgreSql.Entities; +[Table("GeneticRequests")] public class GeneticRequestEntity { public int Id { get; set; } diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/IndicatorEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/IndicatorEntity.cs index 6b69c3b..fab2491 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/Entities/IndicatorEntity.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/IndicatorEntity.cs @@ -28,12 +28,14 @@ public class IndicatorEntity public int? SmoothPeriods { get; set; } public int? CyclePeriods { get; set; } - [MaxLength(255)] - public string? UserName { get; set; } + public int? UserId { get; set; } public DateTime CreatedAt { 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 public virtual ICollection ScenarioIndicators { get; set; } = new List(); } \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/PositionEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/PositionEntity.cs index 5aac60e..c962abb 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/Entities/PositionEntity.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/PositionEntity.cs @@ -22,7 +22,7 @@ public class PositionEntity [MaxLength(255)] public string AccountName { get; set; } - [MaxLength(255)] public string? UserName { get; set; } + public int? UserId { get; set; } // Foreign keys to trades public int? OpenTradeId { get; set; } @@ -37,6 +37,8 @@ public class PositionEntity public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; // Navigation properties + public UserEntity? User { get; set; } + [ForeignKey("OpenTradeId")] public virtual TradeEntity? OpenTrade { get; set; } [ForeignKey("StopLossTradeId")] public virtual TradeEntity? StopLossTrade { get; set; } diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/ScenarioEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/ScenarioEntity.cs index f0bf250..3ee73e4 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/Entities/ScenarioEntity.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/ScenarioEntity.cs @@ -15,12 +15,15 @@ public class ScenarioEntity public int LoopbackPeriod { get; set; } - [MaxLength(255)] - public string? UserName { get; set; } + [Required] + public int UserId { get; set; } public DateTime CreatedAt { 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 public virtual ICollection ScenarioIndicators { get; set; } = new List(); } \ No newline at end of file diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/SignalEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/SignalEntity.cs index dcc747d..89b589f 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/Entities/SignalEntity.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/SignalEntity.cs @@ -26,8 +26,10 @@ public class SignalEntity [MaxLength(255)] public string IndicatorName { get; set; } - [MaxLength(255)] - public string? UserName { get; set; } + public int? UserId { get; set; } + + // Navigation property + public UserEntity? User { get; set; } // Candle data stored as JSON [Column(TypeName = "text")] diff --git a/src/Managing.Infrastructure.Database/PostgreSql/Entities/UserEntity.cs b/src/Managing.Infrastructure.Database/PostgreSql/Entities/UserEntity.cs index 4667cd3..99ebd24 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/Entities/UserEntity.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/Entities/UserEntity.cs @@ -1,9 +1,11 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; namespace Managing.Infrastructure.Databases.PostgreSql.Entities; [Table("Users")] +[Index(nameof(Name), IsUnique = true)] public class UserEntity { [Key] public int Id { get; set; } diff --git a/src/Managing.Infrastructure.Database/PostgreSql/ManagingDbContext.cs b/src/Managing.Infrastructure.Database/PostgreSql/ManagingDbContext.cs index 99c51e3..581ec44 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/ManagingDbContext.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/ManagingDbContext.cs @@ -130,7 +130,7 @@ public class ManagingDbContext : DbContext entity.HasKey(e => e.Id); entity.Property(e => e.Identifier).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.GrowthPercentage).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.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 entity.HasIndex(e => e.Identifier).IsUnique(); entity.HasIndex(e => e.RequestId); - entity.HasIndex(e => e.UserName); + entity.HasIndex(e => e.UserId); entity.HasIndex(e => e.Score); // 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 }); }); @@ -159,7 +165,7 @@ public class ManagingDbContext : DbContext { entity.HasKey(e => e.Id); 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.Status) .IsRequired() @@ -178,11 +184,11 @@ public class ManagingDbContext : DbContext // Create indexes for common queries entity.HasIndex(e => e.RequestId).IsUnique(); - entity.HasIndex(e => e.UserName); + entity.HasIndex(e => e.UserId); entity.HasIndex(e => e.Status); // 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 @@ -190,13 +196,19 @@ public class ManagingDbContext : DbContext { entity.HasKey(e => e.Id); 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 - entity.HasIndex(e => e.UserName); + entity.HasIndex(e => e.UserId); // Composite index for user scenarios - entity.HasIndex(e => new { e.UserName, e.Name }); + entity.HasIndex(e => new { e.UserId, e.Name }); }); // Configure Indicator entity @@ -207,13 +219,19 @@ public class ManagingDbContext : DbContext entity.Property(e => e.Type).IsRequired().HasConversion(); entity.Property(e => e.Timeframe).IsRequired().HasConversion(); entity.Property(e => e.SignalType).IsRequired().HasConversion(); - 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 - entity.HasIndex(e => e.UserName); + entity.HasIndex(e => e.UserId); // 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 @@ -249,19 +267,25 @@ public class ManagingDbContext : DbContext entity.Property(e => e.Type).IsRequired().HasConversion(); entity.Property(e => e.SignalType).IsRequired().HasConversion(); 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"); + // Configure relationship with User + entity.HasOne(e => e.User) + .WithMany() + .HasForeignKey(e => e.UserId) + .OnDelete(DeleteBehavior.SetNull); + // Create indexes entity.HasIndex(e => e.Identifier); - entity.HasIndex(e => e.UserName); + entity.HasIndex(e => e.UserId); entity.HasIndex(e => e.Date); entity.HasIndex(e => e.Ticker); entity.HasIndex(e => e.Status); // Composite indexes for common queries - entity.HasIndex(e => new { e.UserName, e.Date }); - entity.HasIndex(e => new { e.Identifier, e.Date, e.UserName }).IsUnique(); + entity.HasIndex(e => new { e.UserId, e.Date }); + entity.HasIndex(e => new { e.Identifier, e.Date, e.UserId }).IsUnique(); }); // Configure Position entity @@ -275,9 +299,15 @@ public class ManagingDbContext : DbContext entity.Property(e => e.Initiator).IsRequired().HasConversion(); entity.Property(e => e.SignalIdentifier).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"); + // Configure relationship with User + entity.HasOne(e => e.User) + .WithMany() + .HasForeignKey(e => e.UserId) + .OnDelete(DeleteBehavior.SetNull); + // Configure relationships with trades entity.HasOne(e => e.OpenTrade) .WithMany() @@ -301,12 +331,12 @@ public class ManagingDbContext : DbContext // Create indexes 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.Date); // Composite indexes - entity.HasIndex(e => new { e.UserName, e.Identifier }); + entity.HasIndex(e => new { e.UserId, e.Identifier }); }); // Configure Trade entity diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlAccountRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlAccountRepository.cs index ff1c8a8..a8c9801 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlAccountRepository.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlAccountRepository.cs @@ -131,28 +131,12 @@ public class PostgreSqlAccountRepository : IAccountRepository { var accountEntity = PostgreSqlMappers.Map(account); - // Handle User relationship - check if user exists or create new one - if (account.User != null) + // Handle User relationship - user should always have an ID at this point + if (account.User == null) { - var existingUser = await _context.Users - .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; - } + throw new Exception($"Cannot create account {account.Name} without a user"); } - // Balances are not stored in PostgreSQL, they remain in domain logic only - _context.Accounts.Add(accountEntity); await _context.SaveChangesAsync().ConfigureAwait(false); } diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBacktestRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBacktestRepository.cs index 389b495..dc99a50 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBacktestRepository.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlBacktestRepository.cs @@ -72,7 +72,8 @@ public class PostgreSqlBacktestRepository : IBacktestRepository { var entities = _context.Backtests .AsNoTracking() - .Where(b => b.UserName == user.Name) + .Include(b => b.User) + .Where(b => b.UserId == user.Id) .ToList(); return entities.Select(PostgreSqlMappers.Map); @@ -82,7 +83,8 @@ public class PostgreSqlBacktestRepository : IBacktestRepository { var entities = await _context.Backtests .AsNoTracking() - .Where(b => b.UserName == user.Name) + .Include(b => b.User) + .Where(b => b.UserId == user.Id) .ToListAsync() .ConfigureAwait(false); @@ -259,7 +261,8 @@ public class PostgreSqlBacktestRepository : IBacktestRepository { var entity = _context.Backtests .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; } @@ -268,7 +271,8 @@ public class PostgreSqlBacktestRepository : IBacktestRepository { var entity = await _context.Backtests .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); return entity != null ? PostgreSqlMappers.Map(entity) : null; @@ -278,7 +282,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository { var entity = _context.Backtests .AsTracking() - .FirstOrDefault(b => b.Identifier == id && b.UserName == user.Name); + .FirstOrDefault(b => b.Identifier == id && b.UserId == user.Id); if (entity != null) { @@ -291,7 +295,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository { var entity = await _context.Backtests .AsTracking() - .FirstOrDefaultAsync(b => b.Identifier == id && b.UserName == user.Name) + .FirstOrDefaultAsync(b => b.Identifier == id && b.UserId == user.Id) .ConfigureAwait(false); if (entity != null) @@ -305,7 +309,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository { var entities = _context.Backtests .AsTracking() - .Where(b => b.UserName == user.Name && ids.Contains(b.Identifier)) + .Where(b => b.UserId == user.Id && ids.Contains(b.Identifier)) .ToList(); if (entities.Any()) @@ -319,7 +323,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository { var entities = await _context.Backtests .AsTracking() - .Where(b => b.UserName == user.Name && ids.Contains(b.Identifier)) + .Where(b => b.UserId == user.Id && ids.Contains(b.Identifier)) .ToListAsync() .ConfigureAwait(false); @@ -334,7 +338,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository { var entities = _context.Backtests .AsTracking() - .Where(b => b.UserName == user.Name) + .Where(b => b.UserId == user.Id) .ToList(); if (entities.Any()) @@ -380,7 +384,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository var baseQuery = _context.Backtests .AsNoTracking() - .Where(b => b.UserName == user.Name); + .Where(b => b.UserId == user.Id); var afterQueryMs = stopwatch.ElapsedMilliseconds; var totalCount = baseQuery.Count(); @@ -452,7 +456,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository var baseQuery = _context.Backtests .AsNoTracking() - .Where(b => b.UserName == user.Name); + .Where(b => b.UserId == user.Id); var afterQueryMs = stopwatch.ElapsedMilliseconds; var totalCount = await baseQuery.CountAsync().ConfigureAwait(false); @@ -556,7 +560,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository var entities = _context.BundleBacktestRequests .AsNoTracking() .Include(b => b.User) - .Where(b => b.UserName == user.Name) + .Where(b => b.UserId == user.Id) .OrderByDescending(b => b.CreatedAt) .ToList(); @@ -568,7 +572,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository var entities = await _context.BundleBacktestRequests .AsNoTracking() .Include(b => b.User) - .Where(b => b.UserName == user.Name) + .Where(b => b.UserId == user.Id) .OrderByDescending(b => b.CreatedAt) .ToListAsync() .ConfigureAwait(false); @@ -581,7 +585,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository var entity = _context.BundleBacktestRequests .AsNoTracking() .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; } @@ -591,7 +595,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository var entity = await _context.BundleBacktestRequests .AsNoTracking() .Include(b => b.User) - .FirstOrDefaultAsync(b => b.RequestId == id && b.UserName == user.Name) + .FirstOrDefaultAsync(b => b.RequestId == id && b.UserId == user.Id) .ConfigureAwait(false); return entity != null ? PostgreSqlMappers.Map(entity) : null; @@ -682,7 +686,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository { var entity = _context.BundleBacktestRequests .AsTracking() - .FirstOrDefault(b => b.RequestId == id && b.UserName == user.Name); + .FirstOrDefault(b => b.RequestId == id && b.UserId == user.Id); if (entity != null) { @@ -695,7 +699,7 @@ public class PostgreSqlBacktestRepository : IBacktestRepository { var entity = await _context.BundleBacktestRequests .AsTracking() - .FirstOrDefaultAsync(b => b.RequestId == id && b.UserName == user.Name) + .FirstOrDefaultAsync(b => b.RequestId == id && b.UserId == user.Id) .ConfigureAwait(false); if (entity != null) diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs index 73bdb63..6436683 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlMappers.cs @@ -49,7 +49,7 @@ public static class PostgreSqlMappers Type = account.Type, Key = account.Key, 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, TakeProfit = moneyManagement.TakeProfit, Leverage = moneyManagement.Leverage, - UserName = moneyManagement.User?.Name, - User = moneyManagement.User != null ? Map(moneyManagement.User) : null + UserId = moneyManagement.User?.Id ?? 0 }; } @@ -204,7 +203,7 @@ public static class PostgreSqlMappers var entity = new GeneticRequestEntity { RequestId = geneticRequest.RequestId, - User = geneticRequest.User != null ? Map(geneticRequest.User) : null, + UserId = geneticRequest.User?.Id ?? 0, CreatedAt = geneticRequest.CreatedAt, CompletedAt = geneticRequest.CompletedAt, UpdatedAt = DateTime.UtcNow, @@ -268,7 +267,8 @@ public static class PostgreSqlMappers var config = JsonConvert.DeserializeObject(entity.ConfigJson); var positionsList = JsonConvert.DeserializeObject>(entity.PositionsJson) ?? new List(); var positions = positionsList.ToDictionary(p => p.Identifier, p => p); - var signalsList = JsonConvert.DeserializeObject>(entity.SignalsJson) ?? new List(); + var signalsList = JsonConvert.DeserializeObject>(entity.SignalsJson) ?? + new List(); var signals = signalsList.ToDictionary(s => s.Identifier, s => s); var statistics = !string.IsNullOrEmpty(entity.StatisticsJson) ? JsonConvert.DeserializeObject(entity.StatisticsJson) @@ -283,7 +283,7 @@ public static class PostgreSqlMappers HodlPercentage = entity.HodlPercentage, StartDate = entity.StartDate, EndDate = entity.EndDate, - User = new User { Name = entity.UserName }, + User = entity.User != null ? Map(entity.User) : null, Statistics = statistics, Fees = entity.Fees, Score = entity.Score, @@ -313,7 +313,7 @@ public static class PostgreSqlMappers StartDate = backtest.StartDate, EndDate = backtest.EndDate, 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, Fees = backtest.Fees, Score = backtest.Score, @@ -339,7 +339,7 @@ public static class PostgreSqlMappers 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, CompletedAt = entity.CompletedAt, Status = entity.Status, @@ -378,8 +378,7 @@ public static class PostgreSqlMappers var entity = new BundleBacktestRequestEntity { RequestId = bundleRequest.RequestId, - UserName = bundleRequest.User?.Name ?? string.Empty, - UserId = null, // Will be set by the repository when saving + UserId = bundleRequest.User?.Id ?? 0, CreatedAt = bundleRequest.CreatedAt, CompletedAt = bundleRequest.CompletedAt, Status = bundleRequest.Status, @@ -431,7 +430,7 @@ public static class PostgreSqlMappers 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() // Will be populated separately when needed }; } @@ -444,7 +443,7 @@ public static class PostgreSqlMappers { Name = scenario.Name, LoopbackPeriod = scenario.LoopbackPeriod ?? 1, - UserName = scenario.User?.Name + UserId = scenario.User?.Id ?? 0 }; } @@ -465,7 +464,7 @@ public static class PostgreSqlMappers SmoothPeriods = entity.SmoothPeriods, StochPeriods = entity.StochPeriods, 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, StochPeriods = indicatorBase.StochPeriods, CyclePeriods = indicatorBase.CyclePeriods, - UserName = indicatorBase.User?.Name + UserId = indicatorBase.User?.Id ?? 0 }; } @@ -511,7 +510,7 @@ public static class PostgreSqlMappers entity.Type, entity.SignalType, entity.IndicatorName, - entity.UserName != null ? new User { Name = entity.UserName } : null) + entity.User != null ? Map(entity.User) : null) { Status = entity.Status }; @@ -533,7 +532,7 @@ public static class PostgreSqlMappers Type = signal.IndicatorType, SignalType = signal.SignalType, IndicatorName = signal.IndicatorName, - UserName = signal.User?.Name, + UserId = signal.User?.Id ?? 0, CandleJson = signal.Candle != null ? JsonConvert.SerializeObject(signal.Candle) : null }; } @@ -559,7 +558,7 @@ public static class PostgreSqlMappers moneyManagement, entity.Initiator, entity.Date, - entity.UserName != null ? new User { Name = entity.UserName } : null) + entity.User != null ? Map(entity.User) : null) { Status = entity.Status, SignalIdentifier = entity.SignalIdentifier @@ -596,7 +595,7 @@ public static class PostgreSqlMappers Initiator = position.Initiator, SignalIdentifier = position.SignalIdentifier, AccountName = position.AccountName, - UserName = position.User?.Name, + UserId = position.User?.Id ?? 0, MoneyManagementJson = position.MoneyManagement != null ? JsonConvert.SerializeObject(position.MoneyManagement) : null @@ -703,7 +702,6 @@ public static class PostgreSqlMappers { Identifier = bot.Identifier, UserId = bot.User.Id, - User = bot.User != null ? Map(bot.User) : null, Status = bot.Status, CreateDate = bot.CreateDate, Name = bot.Name, diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs index 78ef396..c48768a 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlTradingRepository.cs @@ -40,6 +40,7 @@ public class PostgreSqlTradingRepository : ITradingRepository { var scenario = await _context.Scenarios .AsNoTracking() + .Include(s => s.User) .Include(s => s.ScenarioIndicators) .ThenInclude(si => si.Indicator) .FirstOrDefaultAsync(s => s.Name == name) @@ -80,14 +81,36 @@ public class PostgreSqlTradingRepository : ITradingRepository }); } + public async Task> 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) { + var userId = scenario.User?.Id ?? 0; + // Check if scenario already exists for the same user var existingScenario = await _context.Scenarios .AsNoTracking() - .FirstOrDefaultAsync(s => s.Name == scenario.Name && - ((scenario.User == null && s.UserName == null) || - (scenario.User != null && s.UserName == scenario.User.Name))); + .FirstOrDefaultAsync(s => s.Name == scenario.Name && s.UserId == userId); if (existingScenario != null) { @@ -104,11 +127,10 @@ public class PostgreSqlTradingRepository : ITradingRepository { foreach (var indicator in scenario.Indicators) { + var indicatorUserId = indicator.User?.Id ?? 0; var indicatorEntity = await _context.Indicators .AsNoTracking() - .FirstOrDefaultAsync(i => i.Name == indicator.Name && - ((indicator.User == null && i.UserName == null) || - (indicator.User != null && i.UserName == indicator.User.Name))); + .FirstOrDefaultAsync(i => i.Name == indicator.Name && i.UserId == indicatorUserId); if (indicatorEntity != null) { @@ -134,7 +156,7 @@ public class PostgreSqlTradingRepository : ITradingRepository if (entity != null) { entity.LoopbackPeriod = scenario.LoopbackPeriod ?? 1; - entity.UserName = scenario.User?.Name; + entity.UserId = scenario.User?.Id ?? 0; entity.UpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); @@ -196,12 +218,11 @@ public class PostgreSqlTradingRepository : ITradingRepository public async Task InsertIndicatorAsync(IndicatorBase indicatorBase) { + var indicatorUserId = indicatorBase.User?.Id ?? 0; // Check if indicator already exists for the same user var existingIndicator = await _context.Indicators .AsNoTracking() - .FirstOrDefaultAsync(i => i.Name == indicatorBase.Name && - ((indicatorBase.User == null && i.UserName == null) || - (indicatorBase.User != null && i.UserName == indicatorBase.User.Name))); + .FirstOrDefaultAsync(i => i.Name == indicatorBase.Name && i.UserId == indicatorUserId); if (existingIndicator != null) { @@ -233,7 +254,7 @@ public class PostgreSqlTradingRepository : ITradingRepository entity.SmoothPeriods = indicatorBase.SmoothPeriods; entity.StochPeriods = indicatorBase.StochPeriods; entity.CyclePeriods = indicatorBase.CyclePeriods; - entity.UserName = indicatorBase.User?.Name; + entity.UserId = indicatorBase.User?.Id ?? 0; entity.UpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); @@ -249,6 +270,7 @@ public class PostgreSqlTradingRepository : ITradingRepository { var position = await _context.Positions .AsNoTracking() + .Include(p => p.User) .Include(p => p.OpenTrade) .Include(p => p.StopLossTrade) .Include(p => p.TakeProfit1Trade) @@ -268,6 +290,7 @@ public class PostgreSqlTradingRepository : ITradingRepository { var positions = await _context.Positions .AsNoTracking() + .Include(p => p.User) .Include(p => p.OpenTrade) .Include(p => p.StopLossTrade) .Include(p => p.TakeProfit1Trade) @@ -288,6 +311,7 @@ public class PostgreSqlTradingRepository : ITradingRepository { var positions = await _context.Positions .AsNoTracking() + .Include(p => p.User) .Include(p => p.OpenTrade) .Include(p => p.StopLossTrade) .Include(p => p.TakeProfit1Trade) @@ -301,12 +325,11 @@ public class PostgreSqlTradingRepository : ITradingRepository public async Task InsertPositionAsync(Position position) { + var positionUserId = position.User?.Id ?? 0; // Check if position already exists for the same user var existingPosition = await _context.Positions .AsNoTracking() - .FirstOrDefaultAsync(p => p.Identifier == position.Identifier && - ((position.User == null && p.UserName == null) || - (position.User != null && p.UserName == position.User.Name))); + .FirstOrDefaultAsync(p => p.Identifier == position.Identifier && p.UserId == positionUserId); if (existingPosition != null) { @@ -385,10 +408,11 @@ public class PostgreSqlTradingRepository : ITradingRepository public async Task> GetSignalsByUserAsync(User user) { + var userId = user?.Id ?? 0; var signals = await _context.Signals .AsNoTracking() - .Where(s => (user == null && s.UserName == null) || - (user != null && s.UserName == user.Name)) + .Include(s => s.User) + .Where(s => s.UserId == userId) .ToListAsync() .ConfigureAwait(false); @@ -402,11 +426,11 @@ public class PostgreSqlTradingRepository : ITradingRepository public async Task GetSignalByIdentifierAsync(string identifier, User user = null) { + var userId = user?.Id ?? 0; var signal = await _context.Signals .AsNoTracking() - .FirstOrDefaultAsync(s => s.Identifier == identifier && - ((user == null && s.UserName == null) || - (user != null && s.UserName == user.Name))) + .Include(s => s.User) + .FirstOrDefaultAsync(s => s.Identifier == identifier && s.UserId == userId) .ConfigureAwait(false); return PostgreSqlMappers.Map(signal); @@ -414,13 +438,13 @@ public class PostgreSqlTradingRepository : ITradingRepository public async Task InsertSignalAsync(Signal signal) { + var signalUserId = signal.User?.Id ?? 0; // Check if signal already exists with the same identifier, date, and user var existingSignal = _context.Signals .AsNoTracking() .FirstOrDefault(s => s.Identifier == signal.Identifier && s.Date == signal.Date && - ((s.UserName == null && signal.User == null) || - (s.UserName != null && signal.User != null && s.UserName == signal.User.Name))); + s.UserId == signalUserId); if (existingSignal != null) { @@ -435,21 +459,21 @@ public class PostgreSqlTradingRepository : ITradingRepository public async Task GetStrategyByNameUserAsync(string name, User user) { + var userId = user?.Id ?? 0; var indicator = await _context.Indicators .AsNoTracking() - .FirstOrDefaultAsync(i => i.Name == name && - ((user == null && i.UserName == null) || - (user != null && i.UserName == user.Name))); + .Include(i => i.User) + .FirstOrDefaultAsync(i => i.Name == name && i.UserId == userId); return PostgreSqlMappers.Map(indicator); } public async Task GetScenarioByNameUserAsync(string scenarioName, User user) { + var userId = user?.Id ?? 0; var scenario = await _context.Scenarios .AsNoTracking() - .FirstOrDefaultAsync(s => s.Name == scenarioName && - ((user == null && s.UserName == null) || - (user != null && s.UserName == user.Name))); + .Include(s => s.User) + .FirstOrDefaultAsync(s => s.Name == scenarioName && s.UserId == userId); return PostgreSqlMappers.Map(scenario); } diff --git a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlUserRepository.cs b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlUserRepository.cs index 1dd54b4..92811f6 100644 --- a/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlUserRepository.cs +++ b/src/Managing.Infrastructure.Database/PostgreSql/PostgreSqlUserRepository.cs @@ -41,12 +41,12 @@ public class PostgreSqlUserRepository : IUserRepository try { await EnsureConnectionOpenAsync(); - + var userEntity = await _context.Users .AsNoTracking() .FirstOrDefaultAsync(u => u.AgentName == agentName) .ConfigureAwait(false); - + return PostgreSqlMappers.Map(userEntity); } catch (Exception) @@ -62,12 +62,12 @@ public class PostgreSqlUserRepository : IUserRepository try { await EnsureConnectionOpenAsync(); - + var userEntity = await _context.Users .AsNoTracking() .FirstOrDefaultAsync(u => u.Name == name) .ConfigureAwait(false); - + return PostgreSqlMappers.Map(userEntity); } catch (Exception) @@ -78,25 +78,17 @@ 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> GetAllUsersAsync() { try { await EnsureConnectionOpenAsync(); - + var userEntities = await _context.Users .AsNoTracking() .ToListAsync() .ConfigureAwait(false); - + return userEntities.Select(PostgreSqlMappers.Map); } catch (Exception) @@ -107,31 +99,45 @@ public class PostgreSqlUserRepository : IUserRepository } } - public async Task UpdateUser(User user) + public async Task SaveOrUpdateUserAsync(User user) { try { - var userEntity = await _context.Users - .AsTracking() // Explicitly enable tracking for update operations + var existingUser = await _context.Users + .AsTracking() .FirstOrDefaultAsync(u => u.Name == user.Name) .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); } catch (Exception e) { Console.WriteLine(e); - throw new Exception("Cannot update user"); + throw new Exception("Cannot save or update user"); } } -} \ No newline at end of file +} \ No newline at end of file