Add admin page for bundle

This commit is contained in:
2025-11-10 11:50:20 +07:00
parent ecf07a7863
commit 0861e9a8d2
18 changed files with 2071 additions and 49 deletions

View File

@@ -948,4 +948,357 @@ public class PostgreSqlBacktestRepository : IBacktestRepository
return entities.Select(PostgreSqlMappers.Map);
}
public (IEnumerable<BundleBacktestRequest> BundleRequests, int TotalCount) GetBundleBacktestRequestsPaginated(
int page,
int pageSize,
BundleBacktestRequestSortableColumn sortBy = BundleBacktestRequestSortableColumn.CreatedAt,
string sortOrder = "desc",
BundleBacktestRequestsFilter? filter = null)
{
var baseQuery = _context.BundleBacktestRequests
.AsNoTracking()
.Include(b => b.User)
.AsQueryable();
// Apply filters
if (filter != null)
{
if (!string.IsNullOrWhiteSpace(filter.NameContains))
{
var nameLike = $"%{filter.NameContains.Trim()}%";
baseQuery = baseQuery.Where(b => EF.Functions.ILike(b.Name, nameLike));
}
if (filter.Status.HasValue)
{
baseQuery = baseQuery.Where(b => b.Status == filter.Status.Value);
}
if (filter.UserId.HasValue)
{
baseQuery = baseQuery.Where(b => b.UserId == filter.UserId.Value);
}
if (!string.IsNullOrWhiteSpace(filter.UserNameContains))
{
var userNameLike = $"%{filter.UserNameContains.Trim()}%";
baseQuery = baseQuery.Where(b => b.User != null && EF.Functions.ILike(b.User.Name, userNameLike));
}
if (filter.TotalBacktestsMin.HasValue)
{
baseQuery = baseQuery.Where(b => b.TotalBacktests >= filter.TotalBacktestsMin.Value);
}
if (filter.TotalBacktestsMax.HasValue)
{
baseQuery = baseQuery.Where(b => b.TotalBacktests <= filter.TotalBacktestsMax.Value);
}
if (filter.CompletedBacktestsMin.HasValue)
{
baseQuery = baseQuery.Where(b => b.CompletedBacktests >= filter.CompletedBacktestsMin.Value);
}
if (filter.CompletedBacktestsMax.HasValue)
{
baseQuery = baseQuery.Where(b => b.CompletedBacktests <= filter.CompletedBacktestsMax.Value);
}
if (filter.ProgressPercentageMin.HasValue)
{
var minProgress = filter.ProgressPercentageMin.Value;
baseQuery = baseQuery.Where(b => b.TotalBacktests > 0 &&
(double)b.CompletedBacktests / b.TotalBacktests * 100 >= minProgress);
}
if (filter.ProgressPercentageMax.HasValue)
{
var maxProgress = filter.ProgressPercentageMax.Value;
baseQuery = baseQuery.Where(b => b.TotalBacktests > 0 &&
(double)b.CompletedBacktests / b.TotalBacktests * 100 <= maxProgress);
}
if (filter.CreatedAtFrom.HasValue)
{
baseQuery = baseQuery.Where(b => b.CreatedAt >= filter.CreatedAtFrom.Value);
}
if (filter.CreatedAtTo.HasValue)
{
baseQuery = baseQuery.Where(b => b.CreatedAt <= filter.CreatedAtTo.Value);
}
}
var totalCount = baseQuery.Count();
// Apply sorting
IQueryable<BundleBacktestRequestEntity> sortedQuery = sortBy switch
{
BundleBacktestRequestSortableColumn.RequestId => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.RequestId)
: baseQuery.OrderBy(b => b.RequestId),
BundleBacktestRequestSortableColumn.Name => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.Name)
: baseQuery.OrderBy(b => b.Name),
BundleBacktestRequestSortableColumn.Status => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.Status)
: baseQuery.OrderBy(b => b.Status),
BundleBacktestRequestSortableColumn.CreatedAt => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.CreatedAt)
: baseQuery.OrderBy(b => b.CreatedAt),
BundleBacktestRequestSortableColumn.CompletedAt => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.CompletedAt ?? DateTime.MinValue)
: baseQuery.OrderBy(b => b.CompletedAt ?? DateTime.MaxValue),
BundleBacktestRequestSortableColumn.TotalBacktests => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.TotalBacktests)
: baseQuery.OrderBy(b => b.TotalBacktests),
BundleBacktestRequestSortableColumn.CompletedBacktests => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.CompletedBacktests)
: baseQuery.OrderBy(b => b.CompletedBacktests),
BundleBacktestRequestSortableColumn.FailedBacktests => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.FailedBacktests)
: baseQuery.OrderBy(b => b.FailedBacktests),
BundleBacktestRequestSortableColumn.ProgressPercentage => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.TotalBacktests > 0 ? (double)b.CompletedBacktests / b.TotalBacktests : 0)
: baseQuery.OrderBy(b => b.TotalBacktests > 0 ? (double)b.CompletedBacktests / b.TotalBacktests : 0),
BundleBacktestRequestSortableColumn.UserId => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.UserId ?? int.MaxValue)
: baseQuery.OrderBy(b => b.UserId ?? int.MinValue),
BundleBacktestRequestSortableColumn.UserName => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.User != null ? b.User.Name : string.Empty)
: baseQuery.OrderBy(b => b.User != null ? b.User.Name : string.Empty),
BundleBacktestRequestSortableColumn.UpdatedAt => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.UpdatedAt)
: baseQuery.OrderBy(b => b.UpdatedAt),
_ => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.CreatedAt)
: baseQuery.OrderBy(b => b.CreatedAt)
};
var entities = sortedQuery
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToList();
var mappedRequests = entities.Select(PostgreSqlMappers.Map);
return (mappedRequests, totalCount);
}
public async Task<(IEnumerable<BundleBacktestRequest> BundleRequests, int TotalCount)> GetBundleBacktestRequestsPaginatedAsync(
int page,
int pageSize,
BundleBacktestRequestSortableColumn sortBy = BundleBacktestRequestSortableColumn.CreatedAt,
string sortOrder = "desc",
BundleBacktestRequestsFilter? filter = null)
{
var baseQuery = _context.BundleBacktestRequests
.AsNoTracking()
.Include(b => b.User)
.AsQueryable();
// Apply filters
if (filter != null)
{
if (!string.IsNullOrWhiteSpace(filter.NameContains))
{
var nameLike = $"%{filter.NameContains.Trim()}%";
baseQuery = baseQuery.Where(b => EF.Functions.ILike(b.Name, nameLike));
}
if (filter.Status.HasValue)
{
baseQuery = baseQuery.Where(b => b.Status == filter.Status.Value);
}
if (filter.UserId.HasValue)
{
baseQuery = baseQuery.Where(b => b.UserId == filter.UserId.Value);
}
if (!string.IsNullOrWhiteSpace(filter.UserNameContains))
{
var userNameLike = $"%{filter.UserNameContains.Trim()}%";
baseQuery = baseQuery.Where(b => b.User != null && EF.Functions.ILike(b.User.Name, userNameLike));
}
if (filter.TotalBacktestsMin.HasValue)
{
baseQuery = baseQuery.Where(b => b.TotalBacktests >= filter.TotalBacktestsMin.Value);
}
if (filter.TotalBacktestsMax.HasValue)
{
baseQuery = baseQuery.Where(b => b.TotalBacktests <= filter.TotalBacktestsMax.Value);
}
if (filter.CompletedBacktestsMin.HasValue)
{
baseQuery = baseQuery.Where(b => b.CompletedBacktests >= filter.CompletedBacktestsMin.Value);
}
if (filter.CompletedBacktestsMax.HasValue)
{
baseQuery = baseQuery.Where(b => b.CompletedBacktests <= filter.CompletedBacktestsMax.Value);
}
if (filter.ProgressPercentageMin.HasValue)
{
var minProgress = filter.ProgressPercentageMin.Value;
baseQuery = baseQuery.Where(b => b.TotalBacktests > 0 &&
(double)b.CompletedBacktests / b.TotalBacktests * 100 >= minProgress);
}
if (filter.ProgressPercentageMax.HasValue)
{
var maxProgress = filter.ProgressPercentageMax.Value;
baseQuery = baseQuery.Where(b => b.TotalBacktests > 0 &&
(double)b.CompletedBacktests / b.TotalBacktests * 100 <= maxProgress);
}
if (filter.CreatedAtFrom.HasValue)
{
baseQuery = baseQuery.Where(b => b.CreatedAt >= filter.CreatedAtFrom.Value);
}
if (filter.CreatedAtTo.HasValue)
{
baseQuery = baseQuery.Where(b => b.CreatedAt <= filter.CreatedAtTo.Value);
}
}
var totalCount = await baseQuery.CountAsync().ConfigureAwait(false);
// Apply sorting
IQueryable<BundleBacktestRequestEntity> sortedQuery = sortBy switch
{
BundleBacktestRequestSortableColumn.RequestId => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.RequestId)
: baseQuery.OrderBy(b => b.RequestId),
BundleBacktestRequestSortableColumn.Name => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.Name)
: baseQuery.OrderBy(b => b.Name),
BundleBacktestRequestSortableColumn.Status => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.Status)
: baseQuery.OrderBy(b => b.Status),
BundleBacktestRequestSortableColumn.CreatedAt => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.CreatedAt)
: baseQuery.OrderBy(b => b.CreatedAt),
BundleBacktestRequestSortableColumn.CompletedAt => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.CompletedAt ?? DateTime.MinValue)
: baseQuery.OrderBy(b => b.CompletedAt ?? DateTime.MaxValue),
BundleBacktestRequestSortableColumn.TotalBacktests => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.TotalBacktests)
: baseQuery.OrderBy(b => b.TotalBacktests),
BundleBacktestRequestSortableColumn.CompletedBacktests => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.CompletedBacktests)
: baseQuery.OrderBy(b => b.CompletedBacktests),
BundleBacktestRequestSortableColumn.FailedBacktests => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.FailedBacktests)
: baseQuery.OrderBy(b => b.FailedBacktests),
BundleBacktestRequestSortableColumn.ProgressPercentage => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.TotalBacktests > 0 ? (double)b.CompletedBacktests / b.TotalBacktests : 0)
: baseQuery.OrderBy(b => b.TotalBacktests > 0 ? (double)b.CompletedBacktests / b.TotalBacktests : 0),
BundleBacktestRequestSortableColumn.UserId => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.UserId ?? int.MaxValue)
: baseQuery.OrderBy(b => b.UserId ?? int.MinValue),
BundleBacktestRequestSortableColumn.UserName => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.User != null ? b.User.Name : string.Empty)
: baseQuery.OrderBy(b => b.User != null ? b.User.Name : string.Empty),
BundleBacktestRequestSortableColumn.UpdatedAt => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.UpdatedAt)
: baseQuery.OrderBy(b => b.UpdatedAt),
_ => sortOrder == "desc"
? baseQuery.OrderByDescending(b => b.CreatedAt)
: baseQuery.OrderBy(b => b.CreatedAt)
};
var entities = await sortedQuery
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync()
.ConfigureAwait(false);
var mappedRequests = entities.Select(PostgreSqlMappers.Map);
return (mappedRequests, totalCount);
}
public async Task<BundleBacktestRequestSummary> GetBundleBacktestRequestsSummaryAsync()
{
// Use ADO.NET directly for aggregation queries to avoid EF Core mapping issues
var connection = _context.Database.GetDbConnection();
await connection.OpenAsync();
try
{
var statusCounts = new List<BundleBacktestRequestStatusCountResult>();
var totalRequests = 0;
// Query 1: Status summary
// Note: Status is stored as text in PostgreSQL, not as integer
var statusSummarySql = @"
SELECT ""Status"", COUNT(*) as Count
FROM ""BundleBacktestRequests""
GROUP BY ""Status""
ORDER BY ""Status""";
using (var command = connection.CreateCommand())
{
command.CommandText = statusSummarySql;
using (var reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
var statusString = reader.GetString(0);
var count = reader.GetInt32(1);
// Parse the string status to enum
if (Enum.TryParse<BundleBacktestRequestStatus>(statusString, ignoreCase: true, out var status))
{
statusCounts.Add(new BundleBacktestRequestStatusCountResult
{
Status = status,
Count = count
});
}
}
}
}
// Query 2: Total count
var totalCountSql = @"
SELECT COUNT(*) as Count
FROM ""BundleBacktestRequests""";
using (var command = connection.CreateCommand())
{
command.CommandText = totalCountSql;
var result = await command.ExecuteScalarAsync();
totalRequests = result != null ? Convert.ToInt32(result) : 0;
}
return new BundleBacktestRequestSummary
{
StatusCounts = statusCounts.Select(s => new BundleBacktestRequestStatusCount
{
Status = s.Status,
Count = s.Count
}).ToList(),
TotalRequests = totalRequests
};
}
finally
{
await connection.CloseAsync();
}
}
private class BundleBacktestRequestStatusCountResult
{
public BundleBacktestRequestStatus Status { get; set; }
public int Count { get; set; }
}
}